Inhaltsskripte sind Dateien, die im Kontext von Webseiten ausgeführt werden. Mithilfe des 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 übergeben.
Funktionen von Inhaltsskripten verstehen
Content-Scripts können auf Erweiterungsdateien zugreifen, nachdem sie als über das Web zugängliche Ressourcen deklariert wurden. Sie 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()
Inhaltsskripte können nicht direkt auf andere APIs zugreifen. Sie können aber indirekt auf sie zugreifen, indem sie Nachrichten mit anderen Teilen der Erweiterung austauschen.
In isolierten Welten arbeiten
Inhaltsskripte befinden sich in einer isolierten Welt. Dadurch kann ein Inhaltsskript Änderungen an seiner JavaScript-Umgebung vornehmen, ohne mit der Seite oder den Inhaltsskripten anderer Erweiterungen in Konflikt zu stehen.
Eine Erweiterung kann auf einer Webseite mit Code wie dem folgenden Beispiel ausgeführt werden.
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 Skripts einfügen beschriebenen Techniken 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);
Durch diese Änderung werden beide Benachrichtigungen nacheinander angezeigt, wenn auf die Schaltfläche geklickt wird.
Skripts einfügen
Inhaltsskripte können statisch deklariert, dynamisch deklariert oder programmatisch eingeschleust werden.
Mit statischen Deklarationen einfügen
In der Datei „manifest.json“ können Skriptdeklarationen für statische Inhalte für Skripts verwendet werden, die automatisch auf einer Reihe bekannter Seiten ausgeführt werden sollen.
Statisch deklarierte Skripts werden im Manifest unter dem Schlüssel "content_scripts"
registriert.
Sie können JavaScript-Dateien, CSS-Dateien oder beides enthalten. Für alle automatisch ausgeführten Inhaltsskripts müssen Abgleichsmuster 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 |
Array von Strings | Erforderlich. Gibt an, auf welche Seiten dieses Inhaltsskript eingeschleust wird. Details zur Syntax dieser Strings finden Sie unter Übereinstimmungsmuster. Informationen zum Ausschließen von URLs finden Sie unter Übereinstimmungsmuster und -globs. |
css |
Array von Strings | Optional. Die Liste der CSS-Dateien, die in übereinstimmende Seiten eingeschleust werden sollen. Diese werden in der Reihenfolge eingeschleust, in der sie in diesem Array aufgeführt sind, bevor DOM für die Seite erstellt oder angezeigt wird. |
js |
|
Optional. Die Liste der JavaScript-Dateien, die in übereinstimmende Seiten eingeschleust werden sollen. Die Dateien werden in der Reihenfolge eingeschleust, 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 abgeschnitten. |
run_at |
RunAt | Optional. Gibt an, wann das Skript in die Seite eingefügt werden soll. Die Standardeinstellung ist document_idle . |
match_about_blank |
boolean | Optional. Gibt an, ob das Skript in einen about:blank -Frame injiziert werden soll, wenn der übergeordnete oder öffnende Frame mit einem der in matches deklarierten Muster übereinstimmt. Die Standardeinstellung ist "false". |
match_origin_as_fallback |
boolean |
Optional. Gibt an, ob das Skript Frames injizieren soll, die von einem übereinstimmenden Ursprung erstellt wurden, aber deren URL oder Ursprung möglicherweise nicht direkt mit dem Muster übereinstimmt. Dazu gehören Frames mit unterschiedlichen Schemas, z. B. about: , data: , blob: und filesystem: . Weitere Informationen finden Sie unter In zusammengehörige Frames einfügen.
|
world |
ExecutionWorld |
Optional. Die JavaScript-Welt, in der ein Skript ausgeführt werden kann. Die Standardeinstellung ist ISOLATED . Weitere Informationen finden Sie unter In isolierten Welten arbeiten.
|
Mit dynamischen Deklarationen einfügen
Skripts für dynamische Inhalte sind nützlich, wenn die Übereinstimmungsmuster für Inhaltsskripte nicht bekannt sind oder wenn Inhaltsskripte nicht immer auf bekannten Hosts eingeschleust werden sollen.
Die in Chrome 96 eingeführten dynamischen Deklarationen ähneln den statischen Deklarationen, das Content-Script-Objekt wird jedoch bei Chrome mithilfe von Methoden im chrome.scripting
-Namespace statt in manifest.json registriert. Mit der Scripting API können Entwickler von Erweiterungen außerdem Folgendes tun:
- Registrieren Sie Inhaltsskripte.
- Liste der registrierten Inhaltsskripte abrufen
- Aktualisieren Sie die Liste der registrierten Inhaltsskripte.
- Entfernen Sie registrierte Inhaltsskripte.
Wie statische Deklarationen können dynamische Deklarationen JavaScript-Dateien, CSS-Dateien oder beides umfassen.
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 einfügen
Verwenden Sie programmatische Einschleusung für Inhaltsskripte, die als Reaktion auf Ereignisse oder zu bestimmten Gelegenheiten ausgeführt werden müssen.
Um ein Inhaltsskript programmatisch einzufügen, benötigt Ihre Erweiterung Hostberechtigungen für die Seite, auf der die Skripts eingefügt werden sollen. Hostberechtigungen können entweder durch Anfrage im Manifest der Erweiterung oder vorübergehend mit "activeTab"
gewährt werden.
Im Folgenden sehen 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"
}
}
Inhaltsskripte können als Dateien eingeschleust 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"]
});
});
Oder ein Funktionstext kann als Inhaltsskript eingeschleust und 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 Text der Funktion in sich geschlossen sein. Verweise auf Variablen außerhalb der Funktion führen dazu, dass das Inhaltsskript ein ReferenceError
auslöst.
Beim Einschleusen als Funktion können Sie auch Argumente an die Funktion ü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 Globs ausschließen
Wenn Sie den bestimmten Seitenabgleich anpassen möchten, schließen Sie die folgenden Felder in eine deklarative Registrierung ein.
Name | Typ | Beschreibung |
---|---|---|
exclude_matches |
Array von Strings | Optional. Schließt Seiten aus, auf denen dieses Inhaltsskript andernfalls eingeschleust werden würde. Weitere Informationen zur Syntax dieser Strings finden Sie unter Übereinstimmungsmuster. |
include_globs |
Array von Strings | Optional. Wird nach matches angewendet, um nur die URLs einzuschließen, die auch mit diesem glob übereinstimmen. Damit soll das Greasemonkey-Keyword @include emuliert werden. |
exclude_globs |
Array von String | Optional. Wird nach matches angewendet, um URLs auszuschließen, die diesem Glob entsprechen. Dient zur Emulation des Greasemonkey-Keywords @exclude . |
Das Inhaltsskript wird in eine Seite eingeschleust, wenn die beiden folgenden Bedingungen zutreffen:
- Die entsprechende URL stimmt mit jedem
matches
- und beliebigeninclude_globs
-Muster überein. - Die URL stimmt nicht auch mit einem
exclude_matches
- oderexclude_globs
-Muster überein. Da die Propertymatches
erforderlich ist, könnenexclude_matches
,include_globs
undexclude_globs
nur verwendet werden, um festzulegen, welche Seiten betroffen sind.
Die folgende Erweiterung schleust das Inhaltsskript in https://www.nytimes.com/health
ein, aber nicht 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-Properties folgen einer anderen, flexibleren Syntax als Übereinstimmungsmuster. Zulässige glob-Strings sind URLs, die Platzhaltersterne und Fragezeichen enthalten können. Das Sternchen (*
) entspricht jedem String beliebiger Länge, einschließlich des leeren Strings, während das Fragezeichen (?
) jedem einzelnen Zeichen entspricht.
Beispielsweise entspricht der glob-Befehl https://???.example.com/foo/\*
einem der folgenden Beispiele:
https://www.example.com/foo/bar
https://the.example.com/foo/
Sie stimmt jedoch nicht mit Folgendem überein:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
Diese Erweiterung schleust das Inhaltsskript in https://www.nytimes.com/arts/index.html
und https://www.nytimes.com/jobs/index.htm*
ein, aber nicht 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"]
}
],
...
}
Diese Erweiterung schleust das Inhaltsskript in https://history.nytimes.com
und https://.nytimes.com/history
ein, aber nicht in https://science.nytimes.com
oder https://www.nytimes.com/science
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Eines, alle oder einige davon können einbezogen werden, um den richtigen 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"]
}
],
...
}
Ausführungszeit
Mit dem Feld run_at
wird festgelegt, wann JavaScript-Dateien in die Webseite eingefügt werden. Der bevorzugte und der Standardwert ist "document_idle"
. Weitere mögliche Werte finden Sie unter dem Typ 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 für das Einfügen von Skripts zwischen "document_end" und unmittelbar nach dem Auslösen des window.onload -Ereignisses aus. Der genaue Zeitpunkt der Einfügung hängt davon ab, wie komplex das Dokument ist und wie lange es dauert, bis das Dokument geladen wird. Die Funktion ist im Hinblick auf die Seitenladegeschwindigkeit optimiert.Content-Scripts, die unter "document_idle" ausgeführt werden, müssen nicht auf das window.onload -Ereignis warten. Sie werden garantiert ausgeführt, nachdem das DOM abgeschlossen ist. Wenn ein Script definitiv nach window.onload ausgeführt werden muss, kann die Erweiterung mithilfe der document.readyState -Property prüfen, ob onload bereits ausgelöst wurde. |
document_start |
String | Skripts werden nach allen Dateien von css eingeschleust, aber bevor ein anderes DOM erstellt oder ein anderes Skript ausgeführt wird. |
document_end |
String | Skripts werden unmittelbar nach Abschluss des DOM eingeschleust, jedoch bevor Unterressourcen wie Bilder und Frames geladen wurden. |
Frames angeben
Mit dem Feld "all_frames"
kann die Erweiterung angeben, ob JavaScript- und CSS-Dateien in alle Frames eingefügt werden sollen, die den angegebenen URL-Anforderungen entsprechen, oder nur im obersten Frame eines Tabs.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Name | Typ | Beschreibung |
---|---|---|
all_frames |
boolean | Optional. Die Standardeinstellung ist false , d. h., nur der oberste Frame wird abgeglichen.Wenn true angegeben ist, werden alle Frames eingeschleust, auch wenn der Frame nicht der oberste Frame auf dem Tab ist. Jeder Frame wird unabhängig auf URL-Anforderungen geprüft. Es wird nicht in untergeordnete Frames eingeschleust, wenn die URL-Anforderungen nicht erfüllt sind. |
In zugehörige Frames einfügen
Erweiterungen können Skripts in Frames ausführen, die mit einem übereinstimmenden Frame zusammenhängen, aber selbst nicht übereinstimmen. Dies ist häufig bei Frames mit URLs der Fall, die von einem übereinstimmenden Frame erstellt wurden, aber deren URLs selbst nicht den angegebenen Mustern des Skripts entsprechen.
Dies ist der Fall, wenn eine Erweiterung Frames mit URLs einschleusen möchte, die die Schemas about:
, data:
, blob:
und filesystem:
haben. In diesen Fällen stimmt die URL nicht mit dem Muster des Inhaltsskripts überein (und im Fall von about:
und data:
wird die übergeordnete URL oder der Ursprung überhaupt nicht in die URL aufgenommen, wie bei about:blank
oder data:text/html,<html>Hello, World!</html>
).
Diese Frames können jedoch trotzdem mit dem erstellenden Frame verknüpft werden.
Zum Einschleusen in diese Frames 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 angegeben und auf true
gesetzt ist, nutzt Chrome den Ursprung des Initiators des Frames, um zu bestimmen, ob der Frame übereinstimmt, und nicht die URL des Frames selbst. Dieser Wert kann sich auch vom origin des Ziel-Frames unterscheiden (z.B. data:
URLs haben keinen Ursprung.
Der Initiator des Frames ist der Frame, der den Ziel-Frame erstellt oder in ihm navigiert hat. Dies ist in der Regel das direkte übergeordnete Element oder der Öffnende. Dies ist jedoch möglicherweise nicht der Fall, wie es bei einem Frame der Fall ist, der in einem iFrame innerhalb eines iFrames navigiert.
Da dadurch der origin-Frame des Initiator-Frames verglichen wird, kann sich der Initiator-Frame an einem beliebigen Pfad von diesem Ursprung aus befinden. Zur Klarstellung sei erwähnt, dass in Chrome alle Inhaltsskripte, bei denen "match_origin_as_fallback"
auf true
gesetzt ist, auch den Pfad *
angeben müssen.
Wenn sowohl "match_origin_as_fallback"
als auch "match_about_blank"
angegeben sind, hat "match_origin_as_fallback"
Priorität.
Kommunikation mit der Einbettungsseite
Obwohl die Ausführungsumgebungen von Inhaltsskripts und die Seiten, auf denen sie gehostet werden, voneinander isoliert sind, haben sie denselben Zugriff auf das DOM der Seite. Wenn die Seite mit dem Inhaltsskript oder mit der Erweiterung über das Inhaltsskript kommunizieren möchte, muss dies über das freigegebene DOM erfolgen.
Ein Beispiel dafür ist 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);
Auf der erweiterungsfreien Seite „beispiel.html“ werden Nachrichten an sich selbst gepostet. Diese Nachricht wird vom Inhaltsskript abgefangen, geprüft und dann an die Erweiterung gesendet. Auf diese Weise stellt die Seite eine Kommunikationsverbindung zum Erweiterungsprozess her. Umgekehrt ist es auch möglich,
Auf Erweiterungsdateien zugreifen
Wenn Sie über ein Inhaltsskript auf eine Erweiterungsdatei zugreifen möchten, können Sie chrome.runtime.getURL()
aufrufen, um die absolute URL Ihres Erweiterungs-Assets zu erhalten, wie im folgenden Beispiel gezeigt (content.js
):
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. Hier ein Beispiel (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 müssen in der Datei manifest.json
als über das Web zugängliche Ressourcen deklariert sein:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Schutzfunktionen
Obwohl isolierte Welten eine Sicherheitsebene bieten, können Inhaltsskripte Sicherheitslücken in einer Erweiterung und auf der Webseite verursachen. Wenn das Inhaltsskript Inhalte von einer separaten Website empfängt, z. B. indem fetch()
aufgerufen wird, achten Sie darauf, Inhalte vor dem Einschleusen gegen Cross-Site-Scripting-Angriffe zu filtern. Kommunizieren Sie nur über HTTPS, um "man-in-the-middle"-Angriffe zu vermeiden.
Filtern Sie auf jeden Fall nach schädlichen Webseiten. 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);
Stattdessen sollten Sie sicherere APIs bevorzugen, für die keine Skripts ausgeführt werden:
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);