Content scripts are files that run in the context of web pages. Using the standard Document Object Model (DOM), they are able to read details of the web pages the browser visits, make changes to them, and pass information to their parent extension.
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.
You can also access other files in your extension from a content script, using APIs like fetch() . To do this, you need to declare them as web-accessible resources . Note that this also exposes the resources to any first-party or third-party scripts running on the same site.
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.
Statically declared scripts are registered in the manifest under the "content_scripts" key. They can include JavaScript files, CSS files, or both. All auto-run content scripts must specify match patterns .
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 | Required. Specifies which pages this content script will be injected into. See Match Patterns for details on the syntax of these strings and Match patterns and globs for information on how to exclude URLs. |
css | array van strings | Optional. The list of CSS files to be injected into matching pages. These are injected in the order they appear in this array, before any DOM is constructed or displayed for the page. |
js | | Optional. The list of JavaScript files to be injected into matching pages. Files are injected in the order they appear in this array. Each string in this list must contain a relative path to a resource in the extension's root directory. Leading slashes (`/`) are automatically trimmed. |
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 | Optioneel. De JavaScript-omgeving waarin een script moet worden uitgevoerd. Standaard is dit ISOLATED . Zie ook Werken in geïsoleerde omgevingen . |
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.
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.
Introduced in Chrome 96, dynamic declarations are similar to static declarations , but the content script object is registered with Chrome using methods in the chrome.scripting namespace rather than in manifest.json . The Scripting API also allows extension developers to:
- Register content scripts.
- 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.
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" .
Hieronder vindt 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"
}
}
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,
});
});
Be aware that the injected function is a copy of the function referenced in the chrome.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 .
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. - 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.
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 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.
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.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 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
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 chrome.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
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Injecteer in gerelateerde 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.
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"]
}
],
...
}
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 * .
Als zowel "match_origin_as_fallback" als "match_about_blank" zijn opgegeven, heeft "match_origin_as_fallback" voorrang.
Communicatie met de inbeddingspagina
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.
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);
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
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';
When a content script is injected into the main world, the CSP of the page applies.
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);