Inhaltsscripts sind Dateien, die im Kontext von Webseiten ausgeführt werden. Mit dem standardmäßigen Document Object Model (DOM) können sie Details der Webseiten lesen, die der Browser besucht, Änderungen daran vornehmen und Informationen an die übergeordnete Erweiterung weitergeben.
Funktionen von Inhaltsscripts
Inhaltsscripts können direkt auf die folgenden Erweiterungs-APIs zugreifen:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Content-Scripts können nicht direkt auf andere APIs zugreifen. Sie können jedoch indirekt darauf zugreifen, indem sie Nachrichten mit anderen Teilen Ihrer Erweiterung austauschen.
Mithilfe von APIs wie fetch()
können Sie auch über ein Inhaltsskript auf andere Dateien in Ihrer Erweiterung zugreifen. Dazu müssen Sie sie als webzugängliche Ressourcen deklarieren. Beachten Sie, dass die Ressourcen dadurch auch für alle selbst gehosteten oder Drittanbieter-Scripts freigegeben werden, die auf derselben Website ausgeführt werden.
In isolierten Welten arbeiten
Content-Scripts sind in einer isolierten Umgebung ausgeführt. So kann ein Content-Script Änderungen an seiner JavaScript-Umgebung vornehmen, ohne mit den Content-Scripts der Seite oder anderer Erweiterungen in Konflikt zu stehen.
Eine Erweiterung kann auf einer Webseite mit Code ausgeführt werden, der dem folgenden Beispiel ähnelt.
webPage.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>
Diese Erweiterung könnte das folgende Inhaltsskript mit einer der im Abschnitt Scripts einschleusen beschriebenen Methoden einschleusen.
content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
Nach dieser Änderung werden beide Benachrichtigungen nacheinander angezeigt, wenn auf die Schaltfläche geklickt wird.
Skripts einschleusen
Content-Scripts können statisch deklariert, dynamisch deklariert oder programmatisch eingefügt werden.
Mit statischen Deklarationen einfügen
Verwenden Sie Scriptdeklarationen für statische Inhalte in manifest.json für Scripts, die automatisch auf einer bekannten Gruppe von Seiten ausgeführt werden sollen.
Statisch deklarierte Scripts werden im Manifest unter dem Schlüssel "content_scripts"
registriert.
Sie können JavaScript- oder CSS-Dateien oder beides enthalten. Für alle automatisch ausgeführten Inhaltsscripts müssen Übereinstimmungsmuster angegeben werden.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
Name | Typ | Beschreibung |
---|---|---|
matches |
String-Array | Erforderlich. Gibt an, in welche Seiten dieses Inhalts-Script eingefügt werden soll. Weitere Informationen zur Syntax dieser Strings finden Sie unter Übereinstimmungsmuster. Unter Übereinstimmungsmuster und Glob-Strings erfahren Sie, wie Sie URLs ausschließen. |
css |
String-Array | Optional. Die Liste der CSS-Dateien, die in übereinstimmende Seiten eingefügt werden sollen. Sie werden in der Reihenfolge, in der sie in diesem Array erscheinen, eingefügt, bevor ein DOM für die Seite erstellt oder angezeigt wird. |
js |
|
Optional. Die Liste der JavaScript-Dateien, die in übereinstimmende Seiten eingefügt werden sollen. Dateien werden in der Reihenfolge eingefügt, in der sie in diesem Array angezeigt werden. Jeder String in dieser Liste muss einen relativen Pfad zu einer Ressource im Stammverzeichnis der Erweiterung enthalten. Vorangestellte Schrägstriche (/) werden automatisch entfernt. |
run_at |
RunAt | Optional. Gibt an, wann das Script in die Seite eingefügt werden soll. Die Standardeinstellung ist document_idle . |
match_about_blank |
boolean | Optional. Gibt an, ob das Script in einen about:blank -Frame eingefügt werden soll, dessen übergeordnetes Element oder Öffner-Frame mit einem der in matches angegebenen Muster übereinstimmt. Die Standardeinstellung ist "false". |
match_origin_as_fallback |
boolean |
Optional. Gibt an, ob das Script in Frames eingefügt werden soll, die von einem übereinstimmenden Ursprung erstellt wurden, deren URL oder Ursprung jedoch nicht direkt mit dem Muster übereinstimmt. Dazu gehören Frames mit verschiedenen Schemas wie about: , data: , blob: und filesystem: . Siehe auch In zugehörige Frames einfügen.
|
world |
ExecutionWorld |
Optional. Die JavaScript-Umgebung, in der ein Script ausgeführt werden soll. Die Standardeinstellung ist ISOLATED . Siehe auch In isolierten Welten arbeiten.
|
Mit dynamischen Deklarationen einfügen
Dynamische Inhaltsscripts sind nützlich, wenn die Abgleichmuster für Inhaltsscripts nicht gut bekannt sind oder wenn Inhaltsscripts nicht immer auf bekannten Hosts eingefügt werden sollen.
Dynamische Deklarationen wurden in Chrome 96 eingeführt und ähneln statischen Deklarationen. Das Inhaltsscript-Objekt wird jedoch nicht in manifest.json, sondern mithilfe von Methoden im chrome.scripting
-Namespace in Chrome registriert. Mit der Scripting API können Erweiterungs-Entwickler außerdem:
- Registrieren Sie Inhaltsscripts.
- Liste der registrierten Content-Scripts abrufen
- Aktualisieren Sie die Liste der registrierten Inhaltsscripts.
- Entfernen Sie registrierte Content-Scripts.
Wie bei statischen Deklarationen können auch dynamische Deklarationen JavaScript- oder CSS-Dateien oder beides enthalten.
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 einschleusen
Verwenden Sie die programmatische Insertion für Inhaltsscripts, die als Reaktion auf Ereignisse oder zu bestimmten Anlässen ausgeführt werden müssen.
Damit ein Content-Script programmatisch eingefügt werden kann, benötigt Ihre Erweiterung Hostberechtigungen für die Seite, in die Scripts eingefügt werden sollen. Hostberechtigungen können entweder gewährt werden, indem Sie sie im Manifest Ihrer Erweiterung anfordern, oder vorübergehend mit "activeTab"
.
Im Folgenden finden Sie verschiedene Versionen einer activeTab-basierten Erweiterung.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Inhaltsscripts können als Dateien eingefügt werden.
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"]
});
});
Alternativ kann ein Funktionskörper eingefügt und als Inhaltsskript ausgeführt werden.
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Die eingeschleuste Funktion ist eine Kopie der Funktion, auf die im chrome.scripting.executeScript()
-Aufruf verwiesen wird, und nicht die ursprüngliche Funktion selbst. Daher muss der Funktionsblock in sich geschlossen sein. Verweise auf Variablen außerhalb der Funktion führen dazu, dass das Inhaltsskript eine ReferenceError
auslöst.
Wenn Sie die Funktion einfügen, können Sie ihr auch Argumente übergeben.
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" ],
});
});
Übereinstimmungen und Glob-Strings ausschließen
Wenn Sie die Zuordnung bestimmter Seiten anpassen möchten, fügen Sie die folgenden Felder in eine deklarative Registrierung ein.
Name | Typ | Beschreibung |
---|---|---|
exclude_matches |
String-Array | Optional. Ausschlüsse von Seiten, in die dieses Inhaltsskript sonst eingefügt würde. Weitere Informationen zur Syntax dieser Strings finden Sie unter Übereinstimmungsmuster. |
include_globs |
String-Array | Optional. Wird nach matches angewendet, um nur URLs einzubeziehen, die auch mit diesem Globus übereinstimmen. Dies soll das Greasemonkey-Keyword @include emulieren. |
exclude_globs |
String-Array | Optional. Wird nach matches angewendet, um URLs auszuschließen, die diesem Globus entsprechen. Emuliert das Greasemonkey-Keyword @exclude . |
Das Inhaltsskript wird in eine Seite eingefügt, wenn die beiden folgenden Bedingungen erfüllt sind:
- Die URL stimmt mit jedem
matches
-Muster und jedeminclude_globs
-Muster überein. - Die URL stimmt auch nicht mit einem
exclude_matches
- oderexclude_globs
-Muster überein. Da die Propertymatches
erforderlich ist, könnenexclude_matches
,include_globs
undexclude_globs
nur verwendet werden, um einzuschränken, welche Seiten betroffen sind.
Mit der folgenden Erweiterung wird das Inhaltsskript in https://www.nytimes.com/health
, aber nicht in https://www.nytimes.com/business
eingefügt .
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 folgen einer anderen, flexibleren Syntax als Übereinstimmungsmuster. Zulässige Glob-Strings sind URLs, die Platzhaltersternchen und Fragezeichen enthalten können. Das Sternchen (*
) entspricht einem beliebigen String beliebiger Länge, einschließlich des leeren Strings, während das Fragezeichen (?
) einem einzelnen Zeichen entspricht.
Der Globus https://???.example.com/foo/\*
stimmt beispielsweise mit folgenden Elementen überein:
https://www.example.com/foo/bar
https://the.example.com/foo/
Sie stimmen jedoch nicht mit den folgenden überein:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
Mit dieser Erweiterung wird das Inhaltsskript in https://www.nytimes.com/arts/index.html
und https://www.nytimes.com/jobs/index.htm*
, aber nicht in https://www.nytimes.com/sports/index.html
eingefügt:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"include_globs": ["*nytimes.com/???s/*"],
"js": ["contentScript.js"]
}
],
...
}
Mit dieser Erweiterung wird das Inhaltsskript in https://history.nytimes.com
und https://.nytimes.com/history
, aber nicht in https://science.nytimes.com
oder https://www.nytimes.com/science
eingefügt:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Sie können eine, alle oder einige dieser Optionen verwenden, um den gewünschten Umfang zu erreichen.
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"]
}
],
...
}
Laufzeit
Im Feld run_at
wird festgelegt, wann JavaScript-Dateien in die Webseite eingefügt werden. Der bevorzugte und Standardwert ist "document_idle"
. Weitere mögliche Werte finden Sie unter RunAt.
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" ],
}]);
Name | Typ | Beschreibung |
---|---|---|
document_idle |
String | Bevorzugt. Verwenden Sie nach Möglichkeit "document_idle" .Der Browser wählt einen Zeitpunkt zum Einschleusen von Scripts zwischen "document_end" und unmittelbar nach dem Auslösen des Ereignisses window.onload aus. Der genaue Zeitpunkt der Einfügung hängt davon ab, wie komplex das Dokument ist und wie lange das Laden dauert. Er wird für die Seitenladegeschwindigkeit optimiert.Bei "document_idle" ausgeführte Inhaltsscripts müssen nicht auf das Ereignis window.onload warten, da sie garantiert nach Abschluss des DOM ausgeführt werden. Wenn ein Script unbedingt nach window.onload ausgeführt werden muss, kann die Erweiterung mithilfe der Property document.readyState prüfen, ob onload bereits ausgelöst wurde. |
document_start |
String | Scripts werden nach allen Dateien aus css , aber vor dem Erstellen eines anderen DOM oder dem Ausführen eines anderen Scripts eingefügt. |
document_end |
String | Scripts werden unmittelbar nach Abschluss des DOMs, aber vor dem Laden von untergeordneten Ressourcen wie Bildern und Frames eingefügt. |
Frames angeben
Für deklarative Inhaltsscripts, die im Manifest angegeben sind, kann die Erweiterung im Feld "all_frames"
angeben, ob JavaScript- und CSS-Dateien in alle Frames eingefügt werden sollen, die den angegebenen URL-Anforderungen entsprechen, oder nur in den obersten Frame auf einem Tab:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Wenn Sie Content-Scripts programmatisch mit chrome.scripting.registerContentScripts(...)
registrieren, können Sie mit dem Parameter allFrames
angeben, ob das Content-Script in alle Frames eingefügt werden soll, die den angegebenen URL-Anforderungen entsprechen, oder nur in den obersten Frame auf einem Tab. Diese Option kann nur mit „tabId“ verwendet werden und nicht, wenn „frameIds“ oder „documentIds“ angegeben sind:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
In zugehörige Frames einfügen
Für Erweiterungen kann es sinnvoll sein, Scripts in Frames auszuführen, die mit einem übereinstimmenden Frame verknüpft sind, aber nicht selbst übereinstimmen. Häufig tritt dieses Problem bei Frames mit URLs auf, die von einem übereinstimmenden Frame erstellt wurden, deren URLs aber nicht mit den im Script angegebenen Mustern übereinstimmen.
Das ist der Fall, wenn eine Erweiterung Frames mit URLs einschleusen möchte, die about:
-, data:
-, blob:
- und filesystem:
-Schemas haben. In diesen Fällen stimmt die URL nicht mit dem Muster des Inhaltsscripts überein. Bei about:
und data:
ist die übergeordnete URL oder der Ursprung nicht einmal in der URL enthalten, wie bei about:blank
oder data:text/html,<html>Hello, World!</html>
. Diese Frames können jedoch weiterhin dem Frame zugeordnet werden, in dem sie erstellt wurden.
Um diese Frames einzuschleusen, können Erweiterungen das Attribut "match_origin_as_fallback"
in einer Inhaltsskriptspezifikation im Manifest angeben.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Wenn „origin“ angegeben und auf true
festgelegt ist, prüft Chrome nicht die URL des Frames selbst, sondern den Ursprung des Initiators des Frames, um festzustellen, ob der Frame übereinstimmt. Dieser Wert kann sich auch vom Ursprung des Zielframes unterscheiden (z.B. data:
-URLs haben keinen Ursprung).
Der Initiator des Frames ist der Frame, der den Zielframe erstellt oder aufgerufen hat. In der Regel ist dies das direkte übergeordnete Element oder der Öffner. Das ist aber nicht immer der Fall, z. B. wenn ein Frame einen iFrame innerhalb eines iFrames öffnet.
Da hier der Ursprung des Initiator-Frames verglichen wird, kann sich der Initiator-Frame auf jedem Pfad von diesem Ursprung befinden. Zur Verdeutlichung: In Chrome müssen für alle Inhaltsscripts, für die "match_origin_as_fallback"
auf true
festgelegt ist, auch ein Pfad von *
angegeben werden.
Wenn sowohl "match_origin_as_fallback"
als auch "match_about_blank"
angegeben sind, hat "match_origin_as_fallback"
Vorrang.
Kommunikation mit der Seite, auf der die Einbettung erfolgt
Obwohl die Ausführungsumgebungen von Inhaltsscripts und die Seiten, auf denen sie gehostet werden, voneinander isoliert sind, teilen sie sich den Zugriff auf das DOM der Seite. Wenn die Seite mit dem Inhaltsskript oder über das Inhaltsskript mit der Erweiterung kommunizieren möchte, muss dies über das gemeinsame DOM erfolgen.
Ein Beispiel mit 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);
example.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);
Die Seite ohne Erweiterung, example.html, sendet Nachrichten an sich selbst. Diese Nachricht wird vom Inhaltsskript abgefangen und geprüft und dann an den Erweiterungsprozess gesendet. So wird über die Seite eine Kommunikationsverbindung zum Verlängerungsprozess hergestellt. Das Umgekehrte ist auf ähnliche Weise möglich.
Auf Erweiterungsdateien zugreifen
Wenn du über ein Inhaltsscript auf eine Erweiterungsdatei zugreifen möchtest, kannst du chrome.runtime.getURL()
aufrufen, um die absolute URL deines Erweiterungs-Assets zu erhalten, wie im folgenden Beispiel (content.js
) gezeigt:
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
Wenn Sie Schriftarten oder Bilder in einer CSS-Datei verwenden möchten, können Sie mit @@extension_id
eine URL erstellen, wie im folgenden Beispiel (content.css
) gezeigt:
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 müssen in der manifest.json
-Datei als webzugängliche Ressourcen deklariert werden:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Schutzfunktionen
Isolierte Welten bieten zwar einen gewissen Schutz, aber die Verwendung von Inhaltsscripts kann Sicherheitslücken in einer Erweiterung und auf der Webseite verursachen. Wenn das Inhaltsskript Inhalte von einer anderen Website empfängt, z. B. durch Aufruf von fetch()
, müssen die Inhalte vor dem Einschleusen auf Cross-Site-Scripting-Angriffe gefiltert werden. Kommunizieren Sie nur über HTTPS, um "man-in-the-middle" zu vermeiden.
Achten Sie darauf, nach schädlichen Webseiten zu filtern. Die folgenden Muster sind beispielsweise gefährlich und in Manifest V3 nicht zulässig:
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);
Verwenden Sie stattdessen sicherere APIs, die keine Scripts ausführen:
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);