Inhoudsscripts

Contentscripts zijn bestanden die worden uitgevoerd in de context van webpagina's. Met behulp van het standaard Document Object Model (DOM) kunnen ze details lezen van de webpagina's die de browser bezoekt, er wijzigingen in aanbrengen en informatie doorgeven aan de bijbehorende extensie.

Inzicht in de mogelijkheden van content scripts

Contentscripts hebben rechtstreeks toegang tot de volgende extensie-API's:

Contentscripts hebben geen directe toegang tot andere API's. Ze kunnen er echter wel indirect toegang toe krijgen door berichten uit te wisselen met andere onderdelen van uw extensie.

Je kunt ook vanuit een contentscript toegang krijgen tot andere bestanden in je extensie, met behulp van API's zoals fetch() . Hiervoor moet je ze declareren als webtoegankelijke resources . Houd er rekening mee dat dit de resources ook beschikbaar maakt voor alle scripts van jezelf of van derden die op dezelfde site draaien.

Werken in geïsoleerde werelden

Contentscripts bestaan ​​in een geïsoleerde omgeving, waardoor een contentscript wijzigingen kan aanbrengen in zijn JavaScript-omgeving zonder conflicten te veroorzaken met de contentscripts van de pagina of andere extensies.

Een extensie kan in een webpagina worden uitgevoerd met code die lijkt op het volgende voorbeeld.

webpagina.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

Die extensie zou het volgende inhoudsscript kunnen injecteren met behulp van een van de technieken die worden beschreven in de sectie 'Scripts injecteren' .

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

Door deze wijziging verschijnen beide meldingen na elkaar wanneer er op de knop wordt geklikt.

Scripts injecteren

Scripts voor content kunnen statisch , dynamisch of programmatisch worden gedeclareerd.

Injecteren met statische declaraties

Gebruik declaraties voor statische contentscripts in manifest.json voor scripts die automatisch moeten worden uitgevoerd op een bekende set pagina's.

Statisch gedeclareerde scripts worden in het manifest geregistreerd onder de sleutel "content_scripts" . Dit kunnen JavaScript-bestanden, CSS-bestanden of beide zijn. Alle automatisch uitgevoerde contentscripts moeten matchpatronen specificeren.

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

Naam Type Beschrijving
matches array van strings Vereist. Specificeert in welke pagina's dit contentscript wordt geïnjecteerd. Zie Matchpatronen voor details over de syntaxis van deze tekenreeksen en Matchpatronen en globs voor informatie over het uitsluiten van URL's.
css array van strings Optioneel. De lijst met CSS-bestanden die in de overeenkomende pagina's moeten worden geïnjecteerd. Deze worden geïnjecteerd in de volgorde waarin ze in deze lijst voorkomen, voordat er DOM voor de pagina wordt opgebouwd of weergegeven.
js array van strings Optioneel. De lijst met JavaScript-bestanden die in de overeenkomende pagina's moeten worden geïnjecteerd. De bestanden worden geïnjecteerd in de volgorde waarin ze in deze lijst voorkomen. Elke tekenreeks in deze lijst moet een relatief pad naar een bron in de hoofdmap van de extensie bevatten. Voorloopslashes (`/`) worden automatisch verwijderd.
run_at RunAt Optioneel. Specificeert wanneer het script in de pagina moet worden geïnjecteerd. Standaard is dit document_idle .
match_about_blank booleaans Optioneel. Geeft aan of het script moet worden geïnjecteerd in een about:blank frame waarvan het ouder- of openingsframe overeenkomt met een van de patronen die zijn gedeclareerd in matches . Standaardwaarde is false.
match_origin_as_fallback booleaans Optioneel. Of het script moet injecteren in frames die zijn gemaakt door een overeenkomende oorsprong, maar waarvan de URL of oorsprong mogelijk niet direct overeenkomt met het patroon. Dit omvat frames met verschillende schema's, zoals about: data: blob: en filesystem: Zie ook Injecteren in gerelateerde frames .
world ExecutionWorld Optioneel. De JavaScript-omgeving waarin een script moet worden uitgevoerd. Standaard is dit ISOLATED . Zie ook Werken in geïsoleerde omgevingen .

Binnen een bepaalde fase van de documentlevenscyclus worden inhoudsscripts die statisch in het manifest zijn gedeclareerd als eerste geïnjecteerd, vóór inhoudsscripts die op een andere manier zijn geregistreerd. Ze worden geïnjecteerd in de volgorde waarin ze in het manifest zijn gespecificeerd.

Injecteren met dynamische declaraties

Dynamische contentscripts zijn handig wanneer de overeenkomstpatronen voor contentscripts niet goed bekend zijn of wanneer contentscripts niet altijd op bekende hosts geïnjecteerd hoeven te worden.

Dynamische declaraties, geïntroduceerd in Chrome 96, lijken op statische declaraties , maar het content script-object wordt bij Chrome geregistreerd met behulp van methoden in de namespace chrome.scripting in plaats van in manifest.json . De Scripting API stelt ontwikkelaars van extensies ook in staat om:

Net als statische declaraties kunnen dynamische declaraties JavaScript-bestanden, CSS-bestanden of beide bevatten.

service-worker.js

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

service-worker.js

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

service-worker.js

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

service-worker.js

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

Injecteer programmatisch

Gebruik programmatische injectie voor scripts die moeten worden uitgevoerd als reactie op gebeurtenissen of op specifieke momenten.

Om programmatisch een script in te voegen, heeft uw extensie hostrechten nodig voor de pagina waarin de scripts moeten worden geïnjecteerd. Hostrechten kunnen worden verleend door ze aan te vragen in het manifest van uw extensie of door tijdelijk "activeTab" te gebruiken.

Hieronder volgen verschillende versies van een op ActiveTab gebaseerde extensie.

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

Scripts met inhoud kunnen als bestanden worden ingevoegd.

content-script.js


document.body.style.backgroundColor = "orange";

service-worker.js:

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

Of een functiebody kan worden geïnjecteerd en uitgevoerd als een inhoudsscript.

service-worker.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

Houd er rekening mee dat de geïnjecteerde functie een kopie is van de functie waarnaar wordt verwezen in de aanroep chrome.scripting.executeScript() , en niet de oorspronkelijke functie zelf. Daarom moet de functiebody volledig op zichzelf staan; verwijzingen naar variabelen buiten de functie zullen ervoor zorgen dat het script een ReferenceError genereert.

Bij injectie als functie kun je ook argumenten aan de functie doorgeven.

service-worker.js

function injectedFunction(color) {
  document.body.style.backgroundColor = color;
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

Sluit matches en globs uit.

Om specifieke paginamatching aan te passen, kunt u de volgende velden in een declaratieve registratie opnemen.

Naam Type Beschrijving
exclude_matches array van strings Optioneel. Sluit pagina's uit waar dit contentscript anders zou worden ingevoegd. Zie 'Overeenkomstpatronen' voor details over de syntaxis van deze tekenreeksen.
include_globs array van strings Optioneel. Wordt toegepast na matches om alleen die URL's op te nemen die ook overeenkomen met deze glob. Dit is bedoeld om het @include -trefwoord van Greasemonkey na te bootsen.
exclude_globs array van strings Optioneel. Wordt toegepast na matches om URL's uit te sluiten die overeenkomen met deze glob. Bedoeld om het Greasemonkey-trefwoord @exclude na te bootsen.

Het contentscript wordt in een pagina geïnjecteerd als aan beide volgende voorwaarden is voldaan:

  • De URL komt overeen met elk matches patroon en elk include_globs -patroon.
  • De URL komt ook niet overeen met een exclude_matches of exclude_globs patroon. Omdat de eigenschap matches verplicht is, kunnen exclude_matches , include_globs en exclude_globs alleen worden gebruikt om te beperken welke pagina's worden beïnvloed.

De volgende extensie voegt het contentscript toe aan https://www.nytimes.com/health , maar niet aan https://www.nytimes.com/business .

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

Glob-eigenschappen volgen een andere, flexibelere syntaxis dan match-patronen . Acceptabele glob-strings zijn URL's die "jokertekens" zoals asterisken en vraagtekens kunnen bevatten. De asterisk ( * ) komt overeen met elke string van elke lengte, inclusief een lege string, terwijl het vraagteken ( ? ) overeenkomt met elk willekeurig teken.

De glob https://???.example.com/foo/\* komt bijvoorbeeld overeen met elk van de volgende:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

Het komt echter niet overeen met het volgende:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

Deze extensie voegt het contentscript toe aan https://www.nytimes.com/arts/index.html en https://www.nytimes.com/jobs/index.htm* , maar niet aan https://www.nytimes.com/sports/index.html :

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Deze extensie voegt het contentscript toe aan https://history.nytimes.com en https://.nytimes.com/history , maar niet aan https://science.nytimes.com of https://www.nytimes.com/science :

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Een, alle of een deel van deze elementen kunnen worden opgenomen om de juiste reikwijdte te bereiken.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Looptijd

Het veld run_at bepaalt wanneer JavaScript-bestanden in de webpagina worden geïnjecteerd. De voorkeurswaarde en standaardwaarde is "document_idle" . Zie het type `RunAt` voor andere mogelijke waarden.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
Naam Type Beschrijving
document_idle snaar Voorkeur. Gebruik "document_idle" waar mogelijk.

De browser kiest een moment om scripts te injecteren tussen "document_end" en direct nadat de window.onload -gebeurtenis is geactiveerd. Het exacte injectiemoment hangt af van de complexiteit van het document en de laadtijd, en is geoptimaliseerd voor een snelle laadtijd van de pagina.

Contentscripts die worden uitgevoerd wanneer "document_idle" , hoeven niet te luisteren naar de window.onload -gebeurtenis; ze worden gegarandeerd uitgevoerd nadat de DOM volledig is geladen. Als een script absoluut na window.onload moet worden uitgevoerd, kan de extensie controleren of onload al is geactiveerd door de document.readyState eigenschap te gebruiken.
document_start snaar Scripts worden geïnjecteerd na alle css bestanden, maar vóórdat andere DOM-elementen worden opgebouwd of andere scripts worden uitgevoerd.
document_end snaar Scripts worden direct na de volledige DOM-opbouw geïnjecteerd, maar voordat subbronnen zoals afbeeldingen en frames zijn geladen.

Specificeer frames

Voor declaratieve contentscripts die in het manifest zijn gespecificeerd, maakt het veld "all_frames" het mogelijk voor de extensie om aan te geven of JavaScript- en CSS-bestanden in alle frames die aan de opgegeven URL-vereisten voldoen, of alleen in het bovenste frame van een tabblad, moeten worden geïnjecteerd.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Bij het programmatisch registreren van contentscripts met behulp van chrome.scripting.registerContentScripts(...) kan de parameter allFrames worden gebruikt om aan te geven of het contentscript in alle frames moet worden geïnjecteerd die voldoen aan de opgegeven URL-vereisten, of alleen in het bovenste frame van een tabblad. Dit kan alleen worden gebruikt met `tabId` en niet als `frameIds` of `documentIds` zijn opgegeven.

service-worker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

Extensies willen mogelijk scripts uitvoeren in frames die gerelateerd zijn aan een overeenkomend frame, maar zelf niet overeenkomen. Een veelvoorkomend scenario hiervoor is bij frames met URL's die zijn aangemaakt door een overeenkomend frame, maar waarvan de URL's zelf niet overeenkomen met de door het script opgegeven patronen.

Dit is het geval wanneer een extensie frames wil injecteren met URL's die de schema's about: data: blob: en filesystem: bevatten. In deze gevallen komt de URL niet overeen met het patroon van het contentscript (en in het geval van about: en data: wordt de parent-URL of origin zelfs helemaal niet in de URL opgenomen, zoals in about:blank of data:text/html,<html>Hello, World!</html> ). Deze frames kunnen echter nog steeds worden gekoppeld aan het frame dat ze heeft aangemaakt.

Om in deze frames te injecteren, kunnen extensies de eigenschap "match_origin_as_fallback" specificeren in een content script-specificatie in het manifest.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Indien gespecificeerd en ingesteld op true , zal Chrome de oorsprong van de initiator van het frame gebruiken om te bepalen of het frame overeenkomt, in plaats van de URL van het frame zelf. Houd er rekening mee dat dit ook anders kan zijn dan de oorsprong van het doelframe (bijvoorbeeld, data: URL's hebben een null-oorsprong).

Het initiërende frame is het frame dat het doelframe heeft aangemaakt of ernaartoe heeft genavigeerd. Hoewel dit meestal het directe bovenliggende frame of het frame dat het doelframe heeft geopend is, hoeft dit niet altijd het geval te zijn (zoals in het geval van een frame dat naar een iframe binnen een iframe navigeert).

Omdat dit de oorsprong van het initiërende frame vergelijkt, kan het initiërende frame zich op elk pad vanaf die oorsprong bevinden. Om deze implicatie duidelijk te maken, vereist Chrome dat alle inhoudsscripts die zijn gespecificeerd met "match_origin_as_fallback" ingesteld op true , ook een pad van * . specificeren.

Als zowel "match_origin_as_fallback" als "match_about_blank" zijn opgegeven, heeft "match_origin_as_fallback" voorrang.

Communicatie met de inbeddingspagina

Hoewel de uitvoeringsomgevingen van contentscripts en de pagina's waarop ze worden gehost van elkaar gescheiden zijn, delen ze de toegang tot de DOM van de pagina. Als de pagina met het contentscript of met de extensie via het contentscript wil communiceren, moet dit via de gedeelde DOM gebeuren.

Een voorbeeld hiervan kan worden bereikt met behulp van window.postMessage() :

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

voorbeeld.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

De pagina example.html, die geen onderdeel uitmaakt van de extensie, verstuurt berichten naar zichzelf. Deze berichten worden onderschept en gecontroleerd door het content-script en vervolgens doorgestuurd naar het extensieproces. Op deze manier legt de pagina een communicatielijn met het extensieproces. Het omgekeerde is op vergelijkbare wijze mogelijk.

Toegang tot extensiebestanden

Om vanuit een contentscript toegang te krijgen tot een extensiebestand, kunt u chrome.runtime.getURL() aanroepen om de absolute URL van uw extensiebestand te verkrijgen, zoals in het volgende voorbeeld wordt getoond ( content.js ):

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

Om lettertypen of afbeeldingen in een CSS-bestand te gebruiken, kunt u @@extension_id gebruiken om een ​​URL samen te stellen, zoals in het volgende voorbeeld ( content.css ):

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

Alle assets moeten in het manifest.json -bestand worden gedeclareerd als webtoegankelijke resources :

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

Inhoudsbeveiligingsbeleid

Scripts die in geïsoleerde omgevingen worden uitgevoerd, hebben het volgende Content Security Policy (CSP):

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Net als bij andere extensiecontexten, verhindert dit het gebruik van eval() en het laden van externe scripts.

Voor niet-verpakte extensies bevat het CSP ook localhost:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Wanneer een contentscript in de hoofdwereld wordt geïnjecteerd, is het contentscriptbeleid (CSP) van de pagina van toepassing.

Blijf veilig

Hoewel geïsoleerde werelden een beschermingslaag bieden, kan het gebruik van contentscripts kwetsbaarheden creëren in een extensie en de webpagina. Als het contentscript content ontvangt van een aparte website, bijvoorbeeld door de functie fetch() aan te roepen, zorg er dan voor dat de content wordt gefilterd op cross-site scripting- aanvallen voordat deze wordt geïnjecteerd. Communiceer uitsluitend via HTTPS om man-in-the-middle- aanvallen te voorkomen.

Zorg ervoor dat u filtert op schadelijke webpagina's. De volgende patronen zijn bijvoorbeeld gevaarlijk en niet toegestaan ​​in Manifest V3:

Niet doen

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
Niet doen

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

Kies in plaats daarvan voor veiligere API's die geen scripts uitvoeren:

Doen

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
Doen

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);