Contentscripts zijn bestanden die in de context van webpagina's worden uitgevoerd. 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 hun bovenliggende extensie.
Begrijp de mogelijkheden van contentscripts
Inhoudsscripts 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 wel indirect toegang toe krijgen door berichten uit te wisselen met andere onderdelen van uw extensie.
Je kunt ook toegang krijgen tot andere bestanden in je extensie via een contentscript, met behulp van API's zoals fetch()
. Hiervoor moet je ze declareren als webtoegankelijke bronnen . Houd er rekening mee dat de bronnen hierdoor ook toegankelijk worden voor alle first-party of third-party scripts die op dezelfde site draaien.
Werken in geïsoleerde werelden
Inhoudsscripts bevinden zich in een geïsoleerde wereld, waardoor ze wijzigingen kunnen aanbrengen in de JavaScript-omgeving zonder dat er conflicten ontstaan met de inhoudsscripts van de pagina of andere extensies.
Een extensie kan op 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>
Deze extensie kan het volgende inhoudsscript injecteren met behulp van een van de technieken die worden beschreven in het gedeelte 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);
Dankzij deze wijziging worden beide meldingen na elkaar weergegeven wanneer u op de knop klikt.
Scripts injecteren
Inhoudsscripts kunnen statisch , dynamisch of programmatisch worden gedeclareerd.
Injecteren met statische declaraties
Gebruik statische inhoudsscriptdeclaraties 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"
. Ze kunnen JavaScript-bestanden, CSS-bestanden of beide bevatten. 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 | reeks strings | Verplicht. Geeft aan in welke pagina's dit contentscript wordt ingevoegd. Zie Matchpatronen voor meer informatie over de syntaxis van deze strings en Matchpatronen en globs voor informatie over het uitsluiten van URL's. |
css | reeks strings | Optioneel. De lijst met CSS-bestanden die in de overeenkomende pagina's moeten worden ingevoegd. Deze worden ingevoegd in de volgorde waarin ze in deze array voorkomen, voordat er een DOM voor de pagina wordt geconstrueerd of weergegeven. |
js | | Optioneel. De lijst met JavaScript-bestanden die in overeenkomende pagina's moeten worden ingevoegd. Bestanden worden ingevoegd in de volgorde waarin ze in deze matrix voorkomen. Elke tekenreeks in deze lijst moet een relatief pad naar een bron in de hoofdmap van de extensie bevatten. Voorafgaande slashes (`/`) worden automatisch weggelaten. |
run_at | RunAt | Optioneel. Geeft aan wanneer het script in de pagina moet worden geïnjecteerd. Standaard is dit document_idle . |
match_about_blank | Booleaanse | Optioneel. Of het script moet injecteren in een about:blank frame wanneer het bovenliggende of openerframe overeenkomt met een van de patronen die zijn gedeclareerd in matches . Standaardwaarde is false. |
match_origin_as_fallback | Booleaanse | Optioneel. Of het script frames moet injecteren 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 | Uitvoeringswereld | Optioneel. De JavaScript-wereld waarin een script moet worden uitgevoerd. Standaard is dit ISOLATED . Zie ook Werken in geïsoleerde werelden . |
Injecteren met dynamische declaraties
Dynamische inhoudsscripts zijn handig wanneer de matchpatronen voor inhoudsscripts niet goed bekend zijn of wanneer inhoudsscripts niet altijd op bekende hosts moeten worden geïnjecteerd.
Dynamische declaraties, geïntroduceerd in Chrome 96, zijn vergelijkbaar met statische declaraties , maar het content script-object wordt bij Chrome geregistreerd met behulp van methoden in de chrome.scripting
-naamruimte in plaats van in manifest.json . De Scripting API stelt extensieontwikkelaars ook in staat om:
- Registreer inhoudsscripts.
- Ontvang een lijst met geregistreerde inhoudsscripts.
- Werk de lijst met geregistreerde inhoudsscripts bij.
- Geregistreerde inhoudsscripts verwijderen .
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"));
Programmatisch injecteren
Gebruik programmatische injectie voor inhoudsscripts die moeten worden uitgevoerd als reactie op gebeurtenissen of bij specifieke gelegenheden.
Om een inhoudsscript programmatisch te injecteren, heeft uw extensie hostrechten nodig voor de pagina waarop de scripts worden geïnjecteerd. Hostrechten kunnen worden verleend door ze aan te vragen als onderdeel van het manifest van uw extensie of door tijdelijk "activeTab"
te gebruiken.
Hieronder ziet u 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-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 chrome.scripting.executeScript()
-aanroep, niet de originele functie zelf. Daarom moet de body van de functie op zichzelf staan; 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-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 de opgegeven paginamatching aan te passen, neemt u de volgende velden op in een declaratieve registratie.
Naam | Type | Beschrijving |
---|---|---|
exclude_matches | reeks strings | Optioneel. Sluit pagina's uit waarin dit contentscript anders zou worden geïnjecteerd. Zie Matchpatronen voor meer informatie over de syntaxis van deze strings. |
include_globs | reeks strings | Optioneel. Wordt toegepast na matches om alleen URL's op te nemen die ook met deze glob overeenkomen. Dit is bedoeld om het Greasemonkey-trefwoord @include na te bootsen. |
exclude_globs | reeks 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 inhoudsscript wordt in een pagina geïnjecteerd als aan beide volgende voorwaarden wordt voldaan:
- De URL komt overeen met elk
matches
-patroon en elkinclude_globs
patroon. - De URL komt ook niet overeen met een
exclude_matches
ofexclude_globs
-patroon. Omdat de eigenschapmatches
vereist is, kunnenexclude_matches
,include_globs
enexclude_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-worker.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-strings zijn URL's die "wildcard"-asterisken en vraagtekens mogen bevatten. De asterisk ( *
) komt overeen met elke string van elke lengte, inclusief de lege string, terwijl het vraagteken ( ?
) overeenkomt met elk afzonderlijk teken.
De glob https://???.example.com/foo/\*
komt bijvoorbeeld overeen met een 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 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"]
}
],
...
}
Om de juiste reikwijdte te bereiken, kan één, alle of enkele van deze elementen worden opgenomen.
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 voorkeurs- 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 waar mogelijk "document_idle" .De browser kiest een tijdstip om scripts te injecteren tussen "document_end" en direct nadat de window.onload -gebeurtenis is geactiveerd. Het exacte moment van injecteren hangt af van de complexiteit van het document en de laadtijd, en is geoptimaliseerd voor de laadsnelheid van de pagina.Inhoudsscripts die op "document_idle" draaien, hoeven niet te luisteren naar de window.onload -gebeurtenis; ze worden gegarandeerd uitgevoerd nadat de DOM is voltooid. Als een script absoluut na window.onload moet worden uitgevoerd, kan de extensie controleren of onload al is geactiveerd met behulp van de eigenschap document.readyState . |
document_start | snaar | Scripts worden ingevoegd nadat er bestanden van css zijn ingevoegd, maar voordat er een andere DOM is opgebouwd of een ander script is uitgevoerd. |
document_end | snaar | Scripts worden direct ingevoegd nadat de DOM compleet is, maar voordat subresources zoals afbeeldingen en frames geladen zijn. |
Geef frames op
Voor declaratieve inhoudsscripts die in het manifest zijn gespecificeerd, kan de extensie met het veld "all_frames"
opgeven of JavaScript- en CSS-bestanden moeten worden ingevoegd in alle frames die voldoen aan de opgegeven URL-vereisten of alleen in het bovenste frame van een tabblad:
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 moet worden geïnjecteerd in alle frames die voldoen aan de opgegeven URL-vereisten, of alleen in het bovenste frame van een tabblad. Dit kan alleen worden gebruikt met tabId en kan niet worden gebruikt als frameIds of documentIds zijn opgegeven:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Injecteren in gerelateerde frames
Extensies willen mogelijk scripts uitvoeren in frames die gerelateerd zijn aan een overeenkomend frame, maar zelf niet overeenkomen. Een veelvoorkomend scenario hierbij is bij frames met URL's die zijn gemaakt door een overeenkomend frame, maar waarvan de URL's zelf niet overeenkomen met de opgegeven patronen in 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 contentscript (en in het geval van about:
en data:
wordt de bovenliggende URL of oorsprong 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 aan het frame worden gekoppeld dat is gemaakt.
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"]
}
],
...
}
Wanneer 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 kan verschillen van de oorsprong van het doelframe (bijvoorbeeld: data:
URL's hebben een null-oorsprong).
De initiator van het frame is het frame dat het doelframe heeft gemaakt of erdoorheen is genavigeerd. Hoewel dit meestal de directe ouder of opener is, hoeft dit niet het geval te zijn (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 contentscripts die zijn opgegeven 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, heeft "match_origin_as_fallback"
prioriteit.
Communicatie met de insluitpagina
Hoewel de uitvoeringsomgevingen van contentscripts en de pagina's die deze hosten van elkaar gescheiden zijn, delen ze toegang tot de DOM van de pagina. Als de pagina wil communiceren met het contentscript, of met de extensie via het contentscript, moet dit via de gedeelde DOM gebeuren.
Een voorbeeld hiervan kan worden bereikt met behulp van 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 contentscript en vervolgens geplaatst in het extensieproces. Op deze manier creëert de pagina een communicatielijn met het extensieproces. Het omgekeerde is mogelijk via vergelijkbare 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-asset op te halen, zoals getoond 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 te construeren zoals 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 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
Voor inhoudsscripts die in geïsoleerde werelden worden uitgevoerd, geldt het volgende Content Security Policy (CSP):
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Vergelijkbaar met de beperkingen die gelden voor andere extensiecontexten, voorkomt dit het gebruik van eval()
en het laden van externe scripts.
Voor uitgepakte extensies bevat de 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 inhoudsscript in de hoofdwereld wordt geïnjecteerd, wordt de CSP van de pagina toegepast.
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 van een aparte website ontvangt, bijvoorbeeld door fetch()
aan te roepen, zorg er dan voor dat u de content 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 schadelijke webpagina's. De volgende patronen zijn bijvoorbeeld gevaarlijk en niet toegestaan in Manifest V3:
inhoud-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
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:
inhoud-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
inhoud-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);