Inhoudsscripts zijn bestanden die worden uitgevoerd in de context van webpagina's. Met behulp van het standaard Document Object Model (DOM) kunnen ze details lezen van de webpagina's die de browser bezoekt, er wijzigingen in aanbrengen en informatie doorgeven aan de bovenliggende extensie.
Begrijp de mogelijkheden van inhoudsscripts
Inhoudsscripts hebben rechtstreeks toegang tot de volgende extensie-API's:
-
dom
-
i18n
-
storage
-
runtime.connect()
-
runtime.getManifest()
-
runtime.getURL()
-
runtime.id
-
runtime.onConnect
-
runtime.onMessage
-
runtime.sendMessage()
Inhoudsscripts hebben geen directe toegang tot andere API's. Maar ze hebben er indirect toegang toe door berichten uit te wisselen met andere delen van uw extensie.
U kunt ook toegang krijgen tot andere bestanden in uw extensie vanuit een inhoudsscript, met behulp van API's zoals fetch()
. Om dit te doen, moet u ze declareren als webtoegankelijke bronnen . Houd er rekening mee dat hierdoor de bronnen ook worden blootgesteld aan scripts van eigen of derde partijen die op dezelfde site worden uitgevoerd.
Werk in geïsoleerde werelden
Inhoudsscripts leven in een geïsoleerde wereld, waardoor een inhoudsscript wijzigingen kan aanbrengen in de JavaScript-omgeving zonder conflicten met de inhoudsscripts van de pagina of andere extensies.
Een extensie kan worden uitgevoerd op een webpagina met code die lijkt op het volgende voorbeeld.
webpagina.html
<html>
<button id="mybutton">click me</button>
<script>
var greeting = "hello, ";
var button = document.getElementById("mybutton");
button.person_name = "Bob";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
</script>
</html>
Die extensie zou het volgende inhoudsscript kunnen injecteren met behulp van een van de technieken die worden beschreven in de sectie Scripts injecteren .
inhoud-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
Met deze wijziging verschijnen beide waarschuwingen op volgorde wanneer op de knop wordt geklikt.
Scripts injecteren
Inhoudsscripts kunnen statisch , dynamisch worden gedeclareerd of programmatisch worden geïnjecteerd .
Injecteer met statische declaraties
Gebruik scriptdeclaraties voor statische inhoud in manifest.json voor scripts die automatisch op een bekende reeks pagina's moeten worden uitgevoerd.
Statisch gedeclareerde scripts worden in het manifest geregistreerd onder de sleutel "content_scripts"
. Ze kunnen JavaScript-bestanden, CSS-bestanden of beide bevatten. Alle automatisch uitgevoerde inhoudsscripts moeten overeenkomstpatronen specificeren.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
Naam | Type | Beschrijving |
---|---|---|
matches | reeks strings | Vereist. Specificeert op welke pagina's dit inhoudsscript wordt geïnjecteerd. Zie Matchpatronen voor details over de syntaxis van deze tekenreeksen en Matchpatronen en globs voor informatie over het uitsluiten van URL's. |
css | reeks strings | Optioneel. De lijst met CSS-bestanden die in overeenkomende pagina's moeten worden geïnjecteerd. Deze worden geïnjecteerd in de volgorde waarin ze in deze array verschijnen, voordat er een DOM voor de pagina wordt gemaakt of weergegeven. |
js | | Optioneel. De lijst met JavaScript-bestanden die in overeenkomende pagina's moeten worden geïnjecteerd. Bestanden worden geïnjecteerd in de volgorde waarin ze in deze array verschijnen. Elke tekenreeks in deze lijst moet een relatief pad bevatten naar een bron in de hoofdmap van de extensie. Voorloopslashes (`/`) worden automatisch bijgesneden. |
run_at | UitvoerenBij | Optioneel. Specificeert wanneer het script in de pagina moet worden geïnjecteerd. Standaard ingesteld op document_idle . |
match_about_blank | Booleaans | Optioneel. Of het script moet worden geïnjecteerd in een about:blank frame waarbij het bovenliggende of openerframe overeenkomt met een van de patronen die zijn gedeclareerd in matches . Standaard ingesteld op false. |
match_origin_as_fallback | Booleaans | Optioneel. Of het script moet worden geïnjecteerd in frames die zijn gemaakt door een overeenkomende oorsprong, maar waarvan de URL of oorsprong mogelijk niet direct overeenkomt met het patroon. Deze omvatten frames met verschillende schema's, zoals about: , data: , blob: en filesystem: . Zie ook Injecteren in gerelateerde frames . |
world | ExecutieWereld | Optioneel. De JavaScript-wereld waarin een script kan worden uitgevoerd. Standaard ingesteld op ISOLATED . Zie ook Werken in geïsoleerde werelden . |
Injecteer met dynamische declaraties
Dynamische inhoudsscripts zijn handig wanneer de overeenkomstpatronen voor inhoudsscripts niet goed bekend zijn of wanneer inhoudsscripts niet altijd op bekende hosts moeten worden geïnjecteerd.
Geïntroduceerd in Chrome 96, zijn dynamische declaraties vergelijkbaar met statische declaraties , maar het contentscriptobject wordt geregistreerd bij Chrome met behulp van methoden in de chrome.scripting
naamruimte in plaats van in manifest.json . Met de Scripting API kunnen extensie-ontwikkelaars ook:
- Registreer inhoudsscripts.
- Ontvang een lijst met geregistreerde inhoudsscripts.
- Werk de lijst met geregistreerde inhoudsscripts bij.
- Verwijder geregistreerde inhoudsscripts.
Net als statische declaraties kunnen dynamische declaraties JavaScript-bestanden, CSS-bestanden of beide bevatten.
service-werker.js
chrome.scripting
.registerContentScripts([{
id: "session-script",
js: ["content.js"],
persistAcrossSessions: false,
matches: ["*://example.com/*"],
runAt: "document_start",
}])
.then(() => console.log("registration complete"))
.catch((err) => console.warn("unexpected error", err))
service-werker.js
chrome.scripting
.updateContentScripts([{
id: "session-script",
excludeMatches: ["*://admin.example.com/*"],
}])
.then(() => console.log("registration updated"));
service-werker.js
chrome.scripting
.getRegisteredContentScripts()
.then(scripts => console.log("registered content scripts", scripts));
service-werker.js
chrome.scripting
.unregisterContentScripts({ ids: ["session-script"] })
.then(() => console.log("un-registration complete"));
Programmatisch injecteren
Gebruik programmatische injectie voor contentscripts die moeten worden uitgevoerd als reactie op gebeurtenissen of bij specifieke gelegenheden.
Om een inhoudsscript programmatisch te injecteren, heeft uw extensie hostmachtigingen nodig voor de pagina waarin deze scripts probeert te injecteren. Hostmachtigingen kunnen worden verleend door deze aan te vragen als onderdeel van het manifest van uw extensie of door tijdelijk "activeTab"
te gebruiken.
Hieronder volgen verschillende versies van een op activeTab gebaseerde extensie.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Inhoudsscripts kunnen als bestanden worden geïnjecteerd.
inhoud-script.js
document.body.style.backgroundColor = "orange";
service-werker.js:
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-script.js"]
});
});
Of een functiehoofdtekst kan worden geïnjecteerd en uitgevoerd als een inhoudsscript.
service-werker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Houd er rekening mee dat de geïnjecteerde functie een kopie is van de functie waarnaar wordt verwezen in de aanroep chrome.scripting.executeScript()
, en niet de originele functie zelf. Als gevolg hiervan moet het lichaam van de functie op zichzelf staand zijn; verwijzingen naar variabelen buiten de functie zorgen ervoor dat het inhoudsscript een ReferenceError
genereert.
Wanneer u als functie injecteert, kunt u ook argumenten aan de functie doorgeven.
service-werker.js
function injectedFunction(color) {
document.body.style.backgroundColor = color;
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
args : [ "orange" ],
});
});
Exclusief lucifers en klodders
Om de opgegeven paginamatching aan te passen, neemt u de volgende velden op in een declaratieve registratie.
Naam | Type | Beschrijving |
---|---|---|
exclude_matches | reeks strings | Optioneel. Exclusief pagina's waarin dit inhoudsscript anders zou worden geïnjecteerd. Zie Matchpatronen voor details over de syntaxis van deze tekenreeksen. |
include_globs | reeks strings | Optioneel. Toegepast na matches om alleen die URL's op te nemen die ook overeenkomen met deze glob. Dit is bedoeld om het trefwoord @include Greasemonkey te emuleren. |
exclude_globs | reeks tekenreeksen | Optioneel. Toegepast na matches om URL's uit te sluiten die overeenkomen met deze glob. Bedoeld om het trefwoord @exclude Greasemonkey te emuleren. |
Het inhoudsscript wordt in een pagina geïnjecteerd als aan beide volgende voorwaarden wordt voldaan:
- De URL komt overeen met elk
matches
en elkinclude_globs
-patroon. - De URL komt ook niet overeen met het patroon
exclude_matches
of 'exclude_globs
. 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-werker.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
excludeMatches : [ "*://*/*business*" ],
js : [ "contentScript.js" ],
}]);
Glob-eigenschappen volgen een andere, flexibelere syntaxis dan matchpatronen . Acceptabele glob-tekenreeksen zijn URL's die 'wildcard'-sterretjes en vraagtekens kunnen bevatten. Het sterretje ( *
) komt overeen met elke tekenreeks van elke lengte, inclusief de lege tekenreeks, terwijl het vraagteken ( ?
) overeenkomt met elk afzonderlijk teken.
De glob https://???.example.com/foo/\*
komt bijvoorbeeld overeen met een van de volgende waarden:
-
https://www.example.com/foo/bar
-
https://the.example.com/foo/
Het komt echter niet overeen met het volgende:
-
https://my.example.com/foo/bar
-
https://example.com/foo/
-
https://www.example.com/foo
Deze extensie injecteert het inhoudsscript in https://www.nytimes.com/arts/index.html
en https://www.nytimes.com/jobs/index.htm*
, maar niet in https://www.nytimes.com/sports/index.html
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"include_globs": ["*nytimes.com/???s/*"],
"js": ["contentScript.js"]
}
],
...
}
Deze extensie injecteert het inhoudsscript in https://history.nytimes.com
en https://.nytimes.com/history
, maar niet in https://science.nytimes.com
of https://www.nytimes.com/science
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Eén, alle of enkele hiervan kunnen worden opgenomen om de juiste reikwijdte te bereiken.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_matches": ["*://*/*business*"],
"include_globs": ["*nytimes.com/???s/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Looptijd
Het run_at
veld bepaalt wanneer JavaScript-bestanden in de webpagina worden geïnjecteerd. De voorkeurs- en standaardwaarde is "document_idle"
. Zie het RunAt- type voor andere mogelijke waarden.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
service-werker.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
runAt : "document_idle",
js : [ "contentScript.js" ],
}]);
Naam | Type | Beschrijving |
---|---|---|
document_idle | snaar | Voorkeur. Gebruik waar mogelijk "document_idle" .De browser kiest een tijdstip voor het injecteren van scripts tussen "document_end" en onmiddellijk nadat de gebeurtenis window.onload wordt geactiveerd. Het exacte moment van injectie hangt af van hoe complex het document is en hoe lang het duurt om te laden, en is geoptimaliseerd voor de laadsnelheid van de pagina.Inhoudsscripts die worden uitgevoerd op "document_idle" hoeven niet te luisteren naar de gebeurtenis window.onload ; ze worden gegarandeerd uitgevoerd nadat de DOM is voltooid. Als een script zeker moet worden uitgevoerd na window.onload , kan de extensie controleren of onload al is geactiveerd door de eigenschap document.readyState te gebruiken. |
document_start | snaar | Scripts worden na bestanden uit css geïnjecteerd, maar voordat een andere DOM wordt geconstrueerd of een ander script wordt uitgevoerd. |
document_end | snaar | Scripts worden onmiddellijk geïnjecteerd nadat de DOM is voltooid, maar voordat subbronnen zoals afbeeldingen en frames zijn geladen. |
Geef kaders op
Voor declaratieve inhoudsscripts die in het manifest zijn gespecificeerd, kan de extensie met het veld "all_frames"
specificeren of JavaScript- en CSS-bestanden moeten worden geïnjecteerd in alle frames die voldoen aan de opgegeven URL-vereisten of alleen in het bovenste frame op een tabblad:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Wanneer u inhoudsscripts programmatisch registreert met behulp van chrome.scripting.registerContentScripts(...)
, kan de parameter allFrames
worden gebruikt om op te geven of het inhoudsscript moet worden geïnjecteerd in alle frames die voldoen aan de opgegeven URL-vereisten of alleen in het bovenste frame op een tabblad. Dit kan alleen worden gebruikt met tabId en kan niet worden gebruikt als frameIds of documentIds zijn opgegeven:
service-werker.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 waarin dit het geval is, is voor frames met URL's die zijn gemaakt door een overeenkomend frame, maar waarvan de URL's zelf niet overeenkomen met de opgegeven patronen van het script.
Dit is het geval wanneer een extensie frames wil injecteren met URL's die de schema's about:
, data:
, blob:
en filesystem:
bevatten. In deze gevallen komt de URL niet overeen met het patroon van het inhoudsscript (en, in het geval van about:
en data:
, neem je zelfs helemaal niet de bovenliggende URL of oorsprong op in de URL, zoals in about:blank
of data:text/html,<html>Hello, World!</html>
). Deze frames kunnen echter nog steeds worden gekoppeld aan het gemaakte frame.
Om in deze frames te injecteren, kunnen extensies de eigenschap "match_origin_as_fallback"
opgeven in een inhoudsscriptspecificatie in het manifest.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Indien opgegeven en ingesteld op true
, kijkt Chrome naar de oorsprong van de initiator van het frame om te bepalen of het frame overeenkomt, in plaats van naar de URL van het frame zelf. Houd er rekening mee dat dit ook anders kan zijn dan de oorsprong van het doelframe (bijvoorbeeld data:
URL's hebben een nul-oorsprong).
De initiator van het frame is het frame dat het doelframe heeft gemaakt of er doorheen heeft genavigeerd. Hoewel dit gewoonlijk de directe ouder of opener is, is dit mogelijk niet het geval (zoals in het geval van een frame dat door een iframe binnen een iframe navigeert).
Omdat hiermee de oorsprong van het initiatorframe wordt vergeleken, kan het initiatorframe zich op elk pad vanaf die oorsprong bevinden. Om deze implicatie duidelijk te maken, vereist Chrome dat alle inhoudsscripts die zijn gespecificeerd met "match_origin_as_fallback"
ingesteld op true
, ook een pad van *
specificeren.
Wanneer zowel "match_origin_as_fallback"
als "match_about_blank"
zijn opgegeven, krijgt "match_origin_as_fallback"
prioriteit.
Communicatie met de insluitingspagina
Hoewel de uitvoeringsomgevingen van inhoudsscripts en de pagina's die deze hosten van elkaar zijn geïsoleerd, delen ze de toegang tot de DOM van de pagina. Als de pagina wil communiceren met het inhoudsscript, of met de extensie via het inhoudsscript, moet dit gebeuren via de gedeelde DOM.
Een voorbeeld kan worden bereikt met window.postMessage()
:
inhoud-script.js
var port = chrome.runtime.connect();
window.addEventListener("message", (event) => {
// We only accept messages from ourselves
if (event.source !== window) {
return;
}
if (event.data.type && (event.data.type === "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
}, false);
voorbeeld.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);
De niet-extensiepagina, example.html, plaatst berichten naar zichzelf. Dit bericht wordt onderschept en geïnspecteerd door het inhoudsscript en vervolgens in het extensieproces geplaatst. Op deze manier brengt de pagina een communicatielijn tot stand met het extensieproces. Het omgekeerde is mogelijk via soortgelijke middelen.
Toegang tot extensiebestanden
Om toegang te krijgen tot een extensiebestand vanuit een inhoudsscript, kunt u chrome.runtime.getURL()
aanroepen om de absolute URL van uw extensie-item op te halen, zoals weergegeven in het volgende voorbeeld ( content.js
):
inhoud-script.js
let image = chrome.runtime.getURL("images/my_image.png")
Om lettertypen of afbeeldingen in een CSS-bestand te gebruiken, kunt u @@extension_id
gebruiken om een URL samen te stellen, zoals weergegeven in het volgende voorbeeld ( content.css
):
inhoud.css
body {
background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}
@font-face {
font-family: 'Stint Ultra Expanded';
font-style: normal;
font-weight: 400;
src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}
Alle assets moeten worden gedeclareerd als webtoegankelijke bronnen in het bestand manifest.json
:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Blijf veilig
Hoewel geïsoleerde werelden een beschermingslaag bieden, kan het gebruik van inhoudsscripts kwetsbaarheden in een extensie en de webpagina veroorzaken. Als het inhoudsscript inhoud ontvangt van een afzonderlijke website, bijvoorbeeld door fetch()
aan te roepen, zorg er dan voor dat u de inhoud filtert tegen cross-site scripting- aanvallen voordat u deze injecteert. Communiceer alleen via HTTPS om "man-in-the-middle" -aanvallen te voorkomen.
Zorg ervoor dat u filtert op kwaadaardige webpagina's. De volgende patronen zijn bijvoorbeeld gevaarlijk en niet toegestaan in Manifest V3:
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);