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 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:
- 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
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
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
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" ],
}]);
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 = 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:
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);