Inhaltsskripte

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:

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 Array von Strings 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:

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 beliebigen include_globs-Muster überein.
  • Die URL stimmt nicht auch mit einem exclude_matches- oder exclude_globs-Muster überein. Da die Property matches erforderlich ist, können exclude_matches, include_globs und exclude_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.

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:

Don'ts

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
Don'ts

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:

Das sollten Sie tun:

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
Das sollten Sie tun:

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);