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:
-
dom -
i18n -
storage -
runtime.connect() -
runtime.getManifest() -
runtime.getURL() -
runtime.id -
runtime.onConnect -
runtime.onMessage -
runtime.sendMessage()
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 | | 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-scriptobject wordt bij Chrome geregistreerd met behulp van methoden in de namespace browser.scripting in plaats van in `manifest.json` . De Scripting API biedt extensieontwikkelaars ook de volgende mogelijkheden:
- Registreer contentscripts.
- Vraag een lijst op van geregistreerde contentscripts.
- Werk de lijst met geregistreerde contentscripts bij.
- Verwijder geregistreerde contentscripts.
Net als statische declaraties kunnen dynamische declaraties JavaScript-bestanden, CSS-bestanden of beide bevatten.
service-worker.js
browser.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
browser.scripting
.updateContentScripts([{
id: "session-script",
excludeMatches: ["*://admin.example.com/*"],
}])
.then(() => console.log("registration updated"));
service-worker.js
browser.scripting
.getRegisteredContentScripts()
.then(scripts => console.log("registered content scripts", scripts));
service-worker.js
browser.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:
browser.action.onClicked.addListener((tab) => {
browser.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";
}
browser.action.onClicked.addListener((tab) => {
browser.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 browser.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;
}
browser.action.onClicked.addListener((tab) => {
browser.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
matchespatroon en elkinclude_globs-patroon. - De URL komt ook niet overeen met een
exclude_matchesofexclude_globspatroon. Omdat de eigenschapmatchesverplicht is, kunnenexclude_matches,include_globsenexclude_globsalleen 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
browser.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
browser.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 browser.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
browser.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Injecteer in gerelateerde frames
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 = browser.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 browser.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 = browser.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:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
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:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);
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:
-
dom -
i18n -
storage -
runtime.connect() -
runtime.getManifest() -
runtime.getURL() -
runtime.id -
runtime.onConnect -
runtime.onMessage -
runtime.sendMessage()
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 | | 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-scriptobject wordt bij Chrome geregistreerd met behulp van methoden in de namespace browser.scripting in plaats van in `manifest.json` . De Scripting API biedt extensieontwikkelaars ook de volgende mogelijkheden:
- Registreer contentscripts.
- Vraag een lijst op van geregistreerde contentscripts.
- Werk de lijst met geregistreerde contentscripts bij.
- Verwijder geregistreerde contentscripts.
Net als statische declaraties kunnen dynamische declaraties JavaScript-bestanden, CSS-bestanden of beide bevatten.
service-worker.js
browser.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
browser.scripting
.updateContentScripts([{
id: "session-script",
excludeMatches: ["*://admin.example.com/*"],
}])
.then(() => console.log("registration updated"));
service-worker.js
browser.scripting
.getRegisteredContentScripts()
.then(scripts => console.log("registered content scripts", scripts));
service-worker.js
browser.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:
browser.action.onClicked.addListener((tab) => {
browser.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";
}
browser.action.onClicked.addListener((tab) => {
browser.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 browser.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;
}
browser.action.onClicked.addListener((tab) => {
browser.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
matchespatroon en elkinclude_globs-patroon. - De URL komt ook niet overeen met een
exclude_matchesofexclude_globspatroon. Omdat de eigenschapmatchesverplicht is, kunnenexclude_matches,include_globsenexclude_globsalleen 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
browser.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
browser.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 browser.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
browser.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Injecteer in gerelateerde frames
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 = browser.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 browser.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 = browser.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:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
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:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);
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:
-
dom -
i18n -
storage -
runtime.connect() -
runtime.getManifest() -
runtime.getURL() -
runtime.id -
runtime.onConnect -
runtime.onMessage -
runtime.sendMessage()
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 | | 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 | Optional. Whether the script should inject in frames that were created by a matching origin, but whose URL or origin may not directly match the pattern. These include frames with different schemes, such as about: , data: , blob: , and filesystem: . See also Injecting in related frames . |
world | ExecutionWorld | Optional. The JavaScript world for a script to execute within. Defaults to ISOLATED . See also Work in isolated worlds . |
Within a given stage of the document lifecycle, content scripts declared statically in the manifest are the first to be injected, before content scripts registered in any other way. They are injected in the order in which they are specified in the manifest.
Inject with dynamic declarations
Dynamic content scripts are useful when the match patterns for content scripts are not well known or when content scripts shouldn't always be injected on known hosts.
Introduced in Chrome 96, dynamic declarations are similar to static declarations , but the content script object is registered with Chrome using methods in the browser.scripting namespace rather than in manifest.json . The Scripting API also allows extension developers to:
- Register content scripts.
- Get a list of registered content scripts.
- Update the list of registered content scripts.
- Remove registered content scripts.
Like static declarations, dynamic declarations can include JavaScript files, CSS files, or both.
service-worker.js
browser.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
browser.scripting
.updateContentScripts([{
id: "session-script",
excludeMatches: ["*://admin.example.com/*"],
}])
.then(() => console.log("registration updated"));
service-worker.js
browser.scripting
.getRegisteredContentScripts()
.then(scripts => console.log("registered content scripts", scripts));
service-worker.js
browser.scripting
.unregisterContentScripts({ ids: ["session-script"] })
.then(() => console.log("un-registration complete"));
Inject programmatically
Use programmatic injection for content scripts that need to run in response to events or on specific occasions.
To inject a content script programmatically, your extension needs host permissions for the page it's trying to inject scripts into. Host permissions can either be granted by requesting them as part of your extension's manifest or temporarily using "activeTab" .
The following are different versions of an activeTab-based extension.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Content scripts can be injected as files.
content-script.js
document.body.style.backgroundColor = "orange";
service-worker.js:
browser.action.onClicked.addListener((tab) => {
browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-script.js"]
});
});
Or, a function body can be injected and executed as a content script.
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
browser.action.onClicked.addListener((tab) => {
browser.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Be aware that the injected function is a copy of the function referenced in the browser.scripting.executeScript() call, not the original function itself. As a result, the function's body must be self contained; references to variables outside of the function will cause the content script to throw a ReferenceError .
When injecting as a function, you can also pass arguments to the function.
service-worker.js
function injectedFunction(color) {
document.body.style.backgroundColor = color;
}
browser.action.onClicked.addListener((tab) => {
browser.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
args : [ "orange" ],
});
});
Exclude matches and globs
To customize specified page matching, include the following fields in a declarative registration.
| Naam | Type | Beschrijving |
|---|---|---|
exclude_matches | array of strings | Optional. Excludes pages that this content script would otherwise be injected into. See Match Patterns for details of the syntax of these strings. |
include_globs | array of strings | Optional. Applied after matches to include only those URLs that also match this glob. This is intended to emulate the @include Greasemonkey keyword. |
exclude_globs | array of string | Optional. Applied after matches to exclude URLs that match this glob. Intended to emulate the @exclude Greasemonkey keyword. |
The content script will be injected into a page if both of the following are true:
- Its URL matches any
matchespattern and anyinclude_globspattern. - The URL doesn't also match an
exclude_matchesorexclude_globspattern. Because thematchesproperty is required,exclude_matches,include_globs, andexclude_globscan only be used to limit which pages will be affected.
The following extension injects the content script into https://www.nytimes.com/health but not into 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
browser.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
excludeMatches : [ "*://*/*business*" ],
js : [ "contentScript.js" ],
}]);
Glob properties follow a different, more flexible syntax than match patterns . Acceptable glob strings are URLs that may contain "wildcard" asterisks and question marks. The asterisk ( * ) matches any string of any length, including the empty string, while the question mark ( ? ) matches any single character.
For example, the glob https://???.example.com/foo/\* matches any of the following:
-
https://www.example.com/foo/bar -
https://the.example.com/foo/
However, it does not match the following:
-
https://my.example.com/foo/bar -
https://example.com/foo/ -
https://www.example.com/foo
This extension injects the content script into https://www.nytimes.com/arts/index.html and https://www.nytimes.com/jobs/index.htm* , but not into 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"]
}
],
...
}
This extension injects the content script into https://history.nytimes.com and https://.nytimes.com/history , but not into https://science.nytimes.com or https://www.nytimes.com/science :
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
One, all, or some of these can be included to achieve the correct scope.
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"]
}
],
...
}
Run time
The run_at field controls when JavaScript files are injected into the web page. The preferred and default value is "document_idle" . See the RunAt type for other possible values.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
service-worker.js
browser.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
runAt : "document_idle",
js : [ "contentScript.js" ],
}]);
| Naam | Type | Beschrijving |
|---|---|---|
document_idle | snaar | Preferred. Use "document_idle" whenever possible.The browser chooses a time to inject scripts between "document_end" and immediately after the window.onload event fires. The exact moment of injection depends on how complex the document is and how long it is taking to load, and is optimized for page load speed.Content scripts running at "document_idle" don't need to listen for the window.onload event, they are guaranteed to run after the DOM is complete. If a script definitely needs to run after window.onload , the extension can check if onload has already fired by using the document.readyState property. |
document_start | snaar | Scripts are injected after any files from css , but before any other DOM is constructed or any other script is run. |
document_end | snaar | Scripts are injected immediately after the DOM is complete, but before subresources like images and frames have loaded. |
Specify frames
For declarative content scripts specified in the manifest, the "all_frames" field allows the extension to specify if JavaScript and CSS files should be injected into all frames matching the specified URL requirements or only into the topmost frame in a tab:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
When programmatically registering content scripts using browser.scripting.registerContentScripts(...) , the allFrames parameter can be used to specify if the content script should be injected into all frames matching the specified URL requirements or only into the topmost frame in a tab. This can only be used with tabId, and cannot be used if frameIds or documentIds are specified:
service-worker.js
browser.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Inject in to related frames
Extensions may want to run scripts in frames that are related to a matching frame, but don't themselves match. A common scenario when this is the case is for frames with URLs that were created by a matching frame, but whose URLs don't themselves match the script's specified patterns.
This is the case when an extension wants to inject in frames with URLs that have about: , data: , blob: , and filesystem: schemes. In these cases, the URL won't match the content script's pattern (and, in the case of about: and data: , don't even include the parent URL or origin in the URL at all, as in about:blank or data:text/html,<html>Hello, World!</html> ). However, these frames can still be associated with the creating frame.
To inject into these frames, extensions can specify the "match_origin_as_fallback" property on a content script specification in the manifest.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
When specified and set to true , Chrome will look at the origin of the initiator of the frame to determine whether the frame matches, rather than at the URL of the frame itself. Note that this might also be different than the target frame's origin (eg, data: URLs have a null origin).
The initiator of the frame is the frame that created or navigated the target frame. While this is commonly the direct parent or opener, it may not be (as in the case of a frame navigating an iframe within an iframe).
Because this compares the origin of the initiator frame, the initiator frame could be on at any path from that origin. To make this implication clear, Chrome requires any content scripts specified with "match_origin_as_fallback" set to true to also specify a path of * .
When both "match_origin_as_fallback" and "match_about_blank" are specified, "match_origin_as_fallback" takes priority.
Communication with the embedding page
Although the execution environments of content scripts and the pages that host them are isolated from each other, they share access to the page's DOM. If the page wishes to communicate with the content script, or with the extension through the content script, it must do so through the shared DOM.
An example can be accomplished using window.postMessage() :
content-script.js
var port = browser.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);
example.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);
The non-extension page, example.html, posts messages to itself. This message is intercepted and inspected by the content script and then posted to the extension process. In this way, the page establishes a line of communication to the extension process. The reverse is possible through similar means.
Access extension files
To access an extension file from a content script, you can call browser.runtime.getURL() to get the absolute URL of your extension asset as shown in the following example ( content.js ):
content-script.js
let image = browser.runtime.getURL("images/my_image.png")
To use fonts or images in a CSS file, you can use @@extension_id to construct a URL as shown in the following example ( 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');
}
All assets must be declared as web accessible resources in the manifest.json file:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Inhoudsbeveiligingsbeleid
Content scripts running in isolated worlds have the following Content Security Policy (CSP):
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Similar to the restrictions applied to other extension contexts, this prevents the use of eval() as well as loading external scripts.
For unpacked extensions, the CSP also includes localhost:
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
When a content script is injected into the main world, the CSP of the page applies.
Stay secure
While isolated worlds provide a layer of protection, using content scripts can create vulnerabilities in an extension and the web page. If the content script receives content from a separate website, such as by calling fetch() , be careful to filter content against cross-site scripting attacks before injecting it. Only communicate over HTTPS in order to avoid "man-in-the-middle" attacks.
Be sure to filter for malicious web pages. For example, the following patterns are dangerous, and disallowed in Manifest V3:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
content-script.js
const elmt_id = ... // WARNING! elmt_id might be '); ... evil script ... //'! window.setTimeout("animate(" + elmt_id + ")", 200);
Instead, prefer safer APIs that don't run scripts:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);