Inhoudsscripts

Inhoudsscripts 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 bovenliggende extensie.

Begrijp de mogelijkheden van inhoudsscripts

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

Inhoudsscripts hebben geen directe toegang tot andere API's. Maar ze hebben er indirect toegang toe door berichten uit te wisselen met andere delen van uw extensie.

U kunt ook toegang krijgen tot andere bestanden in uw extensie vanuit een inhoudsscript, met behulp van API's zoals fetch() . Om dit te doen, moet u ze declareren als webtoegankelijke bronnen . Houd er rekening mee dat hierdoor de bronnen ook worden blootgesteld aan scripts van eigen of derde partijen die op dezelfde site worden uitgevoerd.

Werk in geïsoleerde werelden

Inhoudsscripts leven in een geïsoleerde wereld, waardoor een inhoudsscript wijzigingen kan aanbrengen in de JavaScript-omgeving zonder conflicten met de inhoudsscripts van de pagina of andere extensies.

Een extensie kan worden uitgevoerd op een webpagina 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 .

inhoud-script.js

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

Met deze wijziging verschijnen beide waarschuwingen op volgorde wanneer op de knop wordt geklikt.

Scripts injecteren

Inhoudsscripts kunnen statisch , dynamisch worden gedeclareerd of programmatisch worden geïnjecteerd .

Injecteer met statische declaraties

Gebruik scriptdeclaraties voor statische inhoud in manifest.json voor scripts die automatisch moeten worden uitgevoerd op een bekende reeks pagina's.

Statisch gedeclareerde scripts worden in het manifest geregistreerd onder de sleutel "content_scripts" . Ze kunnen JavaScript-bestanden, CSS-bestanden of beide bevatten. Alle automatisch uitgevoerde inhoudsscripts moeten overeenkomstpatronen specificeren.

manifest.json

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

Naam Type Beschrijving
matches reeks strings Vereist. Specificeert op welke pagina's dit inhoudsscript 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 reeks strings Optioneel. De lijst met CSS-bestanden die in overeenkomende pagina's moeten worden geïnjecteerd. Deze worden geïnjecteerd in de volgorde waarin ze in deze array verschijnen, voordat er een DOM voor de pagina wordt gemaakt of weergegeven.
js reeks strings Optioneel. De lijst met JavaScript-bestanden die in overeenkomende pagina's moeten worden geïnjecteerd. Bestanden worden geïnjecteerd in de volgorde waarin ze in deze array verschijnen. Elke tekenreeks in deze lijst moet een relatief pad bevatten naar een bron in de hoofdmap van de extensie. Voorloopslashes (`/`) worden automatisch bijgesneden.
run_at UitvoerenBij Optioneel. Specificeert wanneer het script in de pagina moet worden geïnjecteerd. Standaard ingesteld op document_idle .
match_about_blank Booleaans Optioneel. Of het script moet worden geïnjecteerd in een about:blank frame waarbij het bovenliggende of openerframe overeenkomt met een van de patronen die zijn gedeclareerd in matches . Standaard ingesteld op false.
match_origin_as_fallback Booleaans Optioneel. Of het script moet worden geïnjecteerd in frames die zijn gemaakt door een overeenkomende oorsprong, maar waarvan de URL of oorsprong mogelijk niet direct overeenkomt met het patroon. Deze omvatten frames met verschillende schema's, zoals about: , data: , blob: en filesystem: . Zie ook Injecteren in gerelateerde frames .
world ExecutieWereld Optioneel. De JavaScript-wereld waarin een script kan worden uitgevoerd. Standaard ingesteld op ISOLATED . Zie ook Werken in geïsoleerde werelden .

Injecteer met dynamische declaraties

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

Geïntroduceerd in Chrome 96, zijn dynamische declaraties vergelijkbaar met statische declaraties , maar het contentscriptobject wordt geregistreerd bij Chrome met behulp van methoden in de chrome.scripting naamruimte in plaats van in manifest.json . Met de Scripting API kunnen extensie-ontwikkelaars ook:

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

service-werker.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-werker.js

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

service-werker.js

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

service-werker.js

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

Programmatisch injecteren

Gebruik programmatische injectie voor contentscripts die moeten worden uitgevoerd als reactie op gebeurtenissen of bij specifieke gelegenheden.

Om een ​​inhoudsscript programmatisch te injecteren, heeft uw extensie hostmachtigingen nodig voor de pagina waarin deze scripts probeert te injecteren. Hostmachtigingen kunnen worden verleend door deze aan te vragen als onderdeel van 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"
  }
}

Inhoudsscripts kunnen als bestanden worden geïnjecteerd.

inhoud-script.js


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

service-werker.js:

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

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

service-werker.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 originele functie zelf. Als gevolg hiervan moet het lichaam van de functie op zichzelf staand zijn; verwijzingen naar variabelen buiten de functie zorgen ervoor dat het inhoudsscript een ReferenceError genereert.

Wanneer u als functie injecteert, kunt u ook argumenten aan de functie doorgeven.

service-werker.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" ],
  });
});

Exclusief lucifers en klodders

Om de opgegeven paginamatching aan te passen, neemt u de volgende velden op in een declaratieve registratie.

Naam Type Beschrijving
exclude_matches reeks strings Optioneel. Exclusief pagina's waarin dit inhoudsscript anders zou worden geïnjecteerd. Zie Matchpatronen voor details over de syntaxis van deze tekenreeksen.
include_globs reeks strings Optioneel. Toegepast na matches om alleen die URL's op te nemen die ook overeenkomen met deze glob. Dit is bedoeld om het trefwoord @include Greasemonkey te emuleren.
exclude_globs reeks tekenreeksen Optioneel. Toegepast na matches om URL's uit te sluiten die overeenkomen met deze glob. Bedoeld om het trefwoord @exclude Greasemonkey te emuleren.

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

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

De volgende extensie injecteert het inhoudsscript in https://www.nytimes.com/health , maar niet in https://www.nytimes.com/business .

manifest.json

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

service-werker.js

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

Glob-eigenschappen volgen een andere, flexibelere syntaxis dan matchpatronen . Acceptabele glob-tekenreeksen zijn URL's die 'wildcard'-sterretjes en vraagtekens kunnen bevatten. Het sterretje ( * ) komt overeen met elke tekenreeks van elke lengte, inclusief de lege tekenreeks, terwijl het vraagteken ( ? ) overeenkomt met elk afzonderlijk teken.

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

  • 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 injecteert het inhoudsscript in https://www.nytimes.com/arts/index.html en https://www.nytimes.com/jobs/index.htm* , maar niet in 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 injecteert het inhoudsscript in https://history.nytimes.com en https://.nytimes.com/history , maar niet in 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"]
    }
  ],
  ...
}

Eén, alle of enkele hiervan 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 run_at veld bepaalt wanneer JavaScript-bestanden in de webpagina worden geïnjecteerd. De voorkeurs- en standaardwaarde is "document_idle" . Zie het RunAt- type voor andere mogelijke waarden.

manifest.json

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

service-werker.js

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

De browser kiest een tijdstip voor het injecteren van scripts tussen "document_end" en onmiddellijk nadat de gebeurtenis window.onload wordt geactiveerd. Het exacte moment van injectie hangt af van hoe complex het document is en hoe lang het duurt om te laden, en is geoptimaliseerd voor de laadsnelheid van de pagina.

Inhoudsscripts die worden uitgevoerd op "document_idle" hoeven niet te luisteren naar de gebeurtenis window.onload ; ze worden gegarandeerd uitgevoerd nadat de DOM is voltooid. Als een script zeker moet worden uitgevoerd na window.onload , kan de extensie controleren of onload al is geactiveerd door de eigenschap document.readyState te gebruiken.
document_start snaar Scripts worden na bestanden uit css geïnjecteerd, maar voordat een andere DOM wordt geconstrueerd of een ander script wordt uitgevoerd.
document_end snaar Scripts worden onmiddellijk geïnjecteerd nadat de DOM is voltooid, maar voordat subbronnen zoals afbeeldingen en frames zijn geladen.

Geef kaders op

Met het veld "all_frames" kan de extensie specificeren of JavaScript- en CSS-bestanden moeten worden geïnjecteerd in alle frames die voldoen aan de opgegeven URL-vereisten of alleen in het bovenste frame op een tabblad.

manifest.json

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

service-werker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);
Naam Type Beschrijving
all_frames Booleaans Optioneel. Standaard ingesteld op false , wat betekent dat alleen het bovenste frame overeenkomt.

Als true is opgegeven, worden alle frames geïnjecteerd, zelfs als het frame niet het bovenste frame op het tabblad is. Elk frame wordt onafhankelijk gecontroleerd op URL-vereisten. Het wordt niet in onderliggende frames geïnjecteerd als niet aan de URL-vereisten wordt voldaan.

Extensies willen mogelijk scripts uitvoeren in frames die gerelateerd zijn aan een overeenkomend frame, maar zelf niet overeenkomen. Een veelvoorkomend scenario waarin dit het geval is, is voor frames met URL's die zijn gemaakt door een overeenkomend frame, maar waarvan de URL's zelf niet overeenkomen met de opgegeven patronen van het script.

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 inhoudsscript (en, in het geval van about: en data: , neem je zelfs helemaal niet de bovenliggende URL of oorsprong op in de URL, zoals in about:blank of data:text/html,<html>Hello, World!</html> ). Deze frames kunnen echter nog steeds worden gekoppeld aan het gemaakte frame.

Om in deze frames te injecteren, kunnen extensies de eigenschap "match_origin_as_fallback" opgeven in een inhoudsscriptspecificatie in het manifest.

manifest.json

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

Indien opgegeven en ingesteld op true , kijkt Chrome naar de oorsprong van de initiator van het frame om te bepalen of het frame overeenkomt, in plaats van naar 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 nul-oorsprong).

De initiator van het frame is het frame dat het doelframe heeft gemaakt of er doorheen heeft genavigeerd. Hoewel dit gewoonlijk de directe ouder of opener is, is dit mogelijk niet het geval (zoals in het geval van een frame dat door een iframe binnen een iframe navigeert).

Omdat hiermee de oorsprong van het initiatorframe wordt vergeleken, kan het initiatorframe 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.

Wanneer zowel "match_origin_as_fallback" als "match_about_blank" zijn opgegeven, krijgt "match_origin_as_fallback" prioriteit.

Communicatie met de insluitingspagina

Hoewel de uitvoeringsomgevingen van inhoudsscripts en de pagina's die deze hosten van elkaar geïsoleerd zijn, delen ze de toegang tot de DOM van de pagina. Als de pagina wil communiceren met het inhoudsscript, of met de extensie via het inhoudsscript, moet dit gebeuren via de gedeelde DOM.

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

inhoud-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 niet-extensiepagina, example.html, plaatst berichten naar zichzelf. Dit bericht wordt onderschept en geïnspecteerd door het inhoudsscript en vervolgens in het extensieproces geplaatst. Op deze manier brengt de pagina een communicatielijn tot stand met het extensieproces. Het omgekeerde is mogelijk via soortgelijke middelen.

Toegang tot extensiebestanden

Om toegang te krijgen tot een extensiebestand vanuit een inhoudsscript, kunt u chrome.runtime.getURL() aanroepen om de absolute URL van uw extensie-item op te halen, zoals weergegeven in het volgende voorbeeld ( content.js ):

inhoud-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 weergegeven in het volgende voorbeeld ( content.css ):

inhoud.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 worden gedeclareerd als webtoegankelijke bronnen in het bestand manifest.json :

manifest.json

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

Blijf veilig

Hoewel geïsoleerde werelden een beschermingslaag bieden, kan het gebruik van inhoudsscripts kwetsbaarheden in een extensie en de webpagina veroorzaken. Als het inhoudsscript inhoud ontvangt van een afzonderlijke website, bijvoorbeeld door fetch() aan te roepen, zorg er dan voor dat u de inhoud filtert tegen cross-site scripting- aanvallen voordat u deze injecteert. Communiceer alleen via HTTPS om "man-in-the-middle" -aanvallen te voorkomen.

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

Niet doen

inhoud-script.js

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

inhoud-script.js

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

Geef in plaats daarvan de voorkeur aan veiligere API's die geen scripts uitvoeren:

Doen

inhoud-script.js

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

inhoud-script.js

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