Secuencias de comandos de contenido

Las secuencias de comandos de contenido son archivos que se ejecutan en el contexto de páginas web. Con el estándar Document Object Model (DOM), pueden leer detalles de las páginas web que visita el navegador, realizar los cambios y pasar información a su extensión superior.

Comprende las capacidades de las secuencias de comandos de contenido

Las secuencias de comandos de contenido pueden acceder directamente a las siguientes APIs de extensión:

Las secuencias de comandos de contenido no pueden acceder directamente a otras APIs. Sin embargo, pueden acceder a ellos de forma indirecta intercambiando mensajes con otras partes de tu extensión.

También puedes acceder a otros archivos de tu extensión desde una secuencia de comandos de contenido con APIs como fetch(). Para hacerlo, deberás declararlos como recursos accesibles a través de la Web. Ten en cuenta que esto también expone los recursos a cualquier secuencias de comandos propias o de terceros que se ejecuten en el mismo sitio.

Trabajar en mundos aislados

Los guiones de contenido viven en un mundo aislado, lo que permite que un script de contenido realice cambios en su Entorno de JavaScript sin entrar en conflicto con la página ni otras extensiones los guiones de contenido.

Una extensión puede ejecutarse en una página web con un código similar al siguiente ejemplo.

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>

Esa extensión podría insertar la siguiente secuencia de comandos de contenido usando una de las técnicas descritas en el Sección Inyecta secuencias de comandos.

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

Con este cambio, ambas alertas aparecerán en secuencia cuando se haga clic en el botón.

Cómo insertar secuencias de comandos

Las secuencias de comandos de contenido se pueden declarar de forma estática, declarar de forma dinámica o inyectada de manera programática.

Cómo inyectar con declaraciones estáticas

Usa declaraciones de secuencias de comandos de contenido estático en manifest.json para las secuencias de comandos que se deben enviar automáticamente. se ejecutan en un conjunto de páginas conocido.

Las secuencias de comandos declaradas de forma estática se registran en el manifiesto con la clave "content_scripts". Pueden incluir archivos JavaScript, CSS o ambos. Todas las secuencias de comandos de contenido de ejecución automática deben especificar coincide con los patrones.

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

Nombre Tipo Descripción
matches array de cadenas Obligatorio. Especifica en qué páginas se insertará esta secuencia de comandos de contenido. Consulta Patrones de coincidencia para obtener detalles sobre la sintaxis de estas cadenas y Ajustar patrones y globs para obtener información sobre cómo excluir URLs.
css array de cadenas Opcional. La lista de archivos CSS que se insertarán en páginas coincidentes. Son Se insertan en el orden en que aparecen en este array, antes de que se construya o se muestre cualquier DOM. para la página.
js array de cadenas Opcional. La lista de archivos JavaScript que se insertarán en las páginas coincidentes. Archivos se insertan en el orden en que aparecen en este array. Cada cadena de esta lista debe contener una ruta de acceso relativa a un recurso en el directorio raíz de la extensión. Las barras iniciales (`/`) se se cortó automáticamente.
run_at RunAt Opcional. Especifica cuándo se debe insertar la secuencia de comandos en la página. La configuración predeterminada es document_idle
match_about_blank boolean Opcional. Si la secuencia de comandos debe insertarse en un marco about:blank donde el marco superior o de apertura coincide con uno de los patrones declarados en matches La configuración predeterminada es "false".
match_origin_as_fallback boolean Opcional. Si la secuencia de comandos debe insertar o no marcos que no fueron creado por un origen coincidente, pero cuya URL u origen pueden no estar directamente coinciden con el patrón. Estas incluyen marcos con diferentes esquemas, como about:, data:, blob: y filesystem: Consulta también Inyección en marcos relacionados:
world ExecutionWorld Opcional. El mundo de JavaScript en el que se ejecutará una secuencia de comandos. La configuración predeterminada es ISOLATED. Consulta también Trabaja en mundos aislados.

Cómo insertar con declaraciones dinámicas

Las secuencias de comandos de contenido dinámico son útiles cuando los patrones de coincidencia de las secuencias de comandos de contenido son o cuando las secuencias de comandos de contenido no siempre deberían insertarse en hosts conocidos.

Las declaraciones dinámicas se introdujeron en Chrome 96 y son similares a las estáticas. de secuencia de comandos, pero el objeto de secuencia de comandos de contenido se registra en Chrome usando en el espacio de nombres chrome.scripting y no en manifest.json. La API de Scripting también permite a los desarrolladores de extensiones a:

  • Registrar las secuencias de comandos del contenido
  • Obtén una lista de las secuencias de comandos de contenido registradas.
  • Actualiza la lista de secuencias de comandos de contenido registradas.
  • Quita las secuencias de comandos de contenido registradas.

Al igual que las declaraciones estáticas, las declaraciones dinámicas pueden incluir archivos JavaScript, CSS o ambos.

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"));

Cómo inyectar de manera programática

Usa la inyección programática para secuencias de comandos de contenido que deban ejecutarse en respuesta a eventos o en en algunas ocasiones.

Para insertar una secuencia de comandos de contenido de manera programática, tu extensión necesita permisos de host para lo siguiente: la página en la que intenta insertar secuencias de comandos. Los permisos de host se otorgan solicitándolas como parte del manifiesto de tu extensión o temporalmente usando "activeTab".

Las siguientes son versiones diferentes de una extensión basada en activeTab.

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

Las secuencias de comandos de contenido se pueden insertar como archivos.

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

O bien, el cuerpo de una función se puede insertar y ejecutar como una secuencia de comandos de contenido.

service-worker.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

Ten en cuenta que la función inyectada es una copia de la función a la que se hace referencia en la chrome.scripting.executeScript(), no la función original en sí. Como resultado, el espacio de nombres El cuerpo debe ser independiente. las referencias a variables fuera de la función causarán que el contenido secuencia de comandos para arrojar una ReferenceError

Cuando inyectas como una función, también puedes pasar argumentos a la función.

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

Excluir coincidencias y globs

Para personalizar las coincidencias de páginas especificadas, incluye los siguientes campos en un archivo de registro.

Nombre Tipo Descripción
exclude_matches array de cadenas Opcional. Excluye las páginas en las que se insertaría esta secuencia de comandos de contenido de otro modo a los que puedes acceder. Consulta Patrones de coincidencia para obtener detalles sobre la sintaxis de estas cadenas de texto.
include_globs array de cadenas Opcional. Se aplica después del matches para incluir solo las URLs que también coincidir con este glob. Esto tiene como objetivo emular la @include. Palabra clave Greasemonkey.
exclude_globs array de cadenas Opcional. Se aplica después del matches para excluir las URLs que coincidan con esta glob. Su objetivo es emular la @exclude. Palabra clave Greasemonkey.

La secuencia de comandos de contenido se insertará en una página si se cumplen estas dos condiciones:

  • Su URL coincide con cualquier patrón matches y include_globs.
  • La URL tampoco coincide con un patrón exclude_matches o exclude_globs. Debido a que la propiedad matches es obligatoria, exclude_matches, include_globs y exclude_globs solo se puede usar para limitar las páginas que se verán afectadas.

La siguiente extensión inserta la secuencia de comandos de contenido en https://www.nytimes.com/health pero no en 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" ],
}]);

Las propiedades glob tienen una sintaxis diferente y más flexible que los patrones de coincidencia. glob aceptable las cadenas son URLs que pueden contener "comodín" asteriscos y signos de interrogación. El asterisco (*) coincide con cualquier cadena de cualquier longitud, incluida la cadena vacía, mientras que el signo de interrogación (?) coincide cualquier carácter único.

Por ejemplo, el glob https://???.example.com/foo/\* coincide con cualquiera de las siguientes opciones:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

Sin embargo, no coincide con lo siguiente:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

Esta extensión inserta la secuencia de comandos de contenido en https://www.nytimes.com/arts/index.html y https://www.nytimes.com/jobs/index.htm*, pero no en 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"]
    }
  ],
  ...
}

Esta extensión inserta la secuencia de comandos de contenido en https://history.nytimes.com y https://.nytimes.com/history, pero no en https://science.nytimes.com o https://www.nytimes.com/science:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Se puede incluir una, todas o algunas de ellas para lograr el alcance correcto.

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"]
    }
  ],
  ...
}

Tiempo de ejecución

El campo run_at controla cuándo se insertan los archivos JavaScript en la página web. Las opciones preferidas y el valor predeterminado es "document_idle". Consulta el tipo RunAt para ver otras opciones posibles. de salida.

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" ],
}]);
Nombre Tipo Descripción
document_idle string Preferido. Usa "document_idle" siempre que sea posible.

El navegador Elige un momento para insertar secuencias de comandos entre "document_end" e inmediatamente después window.onload se activa el evento. El momento exacto de la inserción depende de qué tan complejo sea el documento y qué tan tarda en cargarse y está optimizado para acelerar la velocidad de carga de la página.

Secuencias de comandos de contenido que se ejecuta a las "document_idle", no necesitan escuchar el window.onload, se garantiza que se ejecutarán una vez que se complete el DOM. Si un de comandos debe ejecutarse después de window.onload, la extensión puede onload ya se activó con document.readyState propiedad.
document_start string Las secuencias de comandos se insertan después de cualquier archivo de css, pero antes de que se realice cualquier otro DOM se construye o se ejecuta cualquier otra secuencia de comandos.
document_end string Las secuencias de comandos se insertan inmediatamente después de que se completa el DOM, pero antes de que los subrecursos como se cargaron las imágenes y los marcos.

Cómo especificar marcos

El campo "all_frames" permite que la extensión especifique si los archivos JavaScript y CSS deben insertar en todos los marcos que coincidan con los requisitos de URL especificados o solo en el marco superior de una .

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" ],
}]);
Nombre Tipo Descripción
all_frames boolean Opcional. El valor predeterminado es false, lo que significa que solo se muestra el marco superior.

Si se especifica true, se insertarán todos los fotogramas, incluso si no es el marco superior de la pestaña. Cada marco se verifica por separado para la URL y los requisitos de cumplimiento. No se insertará en marcos secundarios si no se cumplen los requisitos de URL.

Es posible que las extensiones quieran ejecutar secuencias de comandos en marcos relacionados con una coincidencia un marco, pero no coinciden. Una situación común cuando este es el caso es para marcos con URL creadas por un marco coincidente, pero cuyas URL no coinciden con los patrones especificados de la secuencia de comandos.

Esto ocurre cuando una extensión quiere inyectar en marcos con URLs que tienen los esquemas about:, data:, blob: y filesystem:. En estos casos, el URL no coincidirá con el patrón de la secuencia de comandos del contenido (y, en el caso de about: y data:, ni siquiera incluyas la URL principal ni el origen en la URL en absoluto, como en about:blank o data:text/html,<html>Hello, World!</html>). Sin embargo, estos marcos aún pueden asociarse con el marco de creación.

Para insertar en estos marcos, las extensiones pueden especificar La propiedad "match_origin_as_fallback" en una especificación de secuencia de comandos de contenido en el .

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Cuando se especifica y se establece en true, Chrome buscará el origen del iniciador del fotograma para determinar si este coincide, en lugar de la URL del marco en sí. Ten en cuenta que esto también podría ser diferente del origen del marco de destino (p.ej., data: de URLs tienen un origen nulo).

El iniciador del fotograma es el que creó el objetivo o navegó por él marco. Si bien suele ser el elemento principal directo o la introducción, es posible que no lo sea (como el caso de un marco que navega por un iframe dentro de un iframe).

Como se compara el origen del marco iniciador, este último podría estar en cualquier ruta desde ese origen. Para dejar en claro esta implicación, Chrome requiere cualquier secuencia de comandos de contenido especificada con "match_origin_as_fallback" Se configura en true para especificar también una ruta de acceso de *.

Cuando se especifican "match_origin_as_fallback" y "match_about_blank", "match_origin_as_fallback" tiene prioridad.

Comunicación con la página de incorporación

Aunque los entornos de ejecución de las secuencias de comandos de contenido y las páginas que los alojan están aislados, entre sí, comparten el acceso al DOM de la página. Si la página desea comunicarse con el secuencia de comandos del contenido, o con la extensión a través de la secuencia de comandos del contenido, debe hacerlo a través del DOM compartido.

Se puede lograr un ejemplo con 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);

La página sin extensión, example.html, publica mensajes en sí misma. Este mensaje se intercepta inspeccionado por la secuencia de comandos del contenido y, luego, publicado en el proceso de extensión. De esta manera, la página establece una línea de comunicación con el proceso de extensión. Es posible lo contrario. medios similares.

Cómo acceder a los archivos de extensión

Para acceder a un archivo de extensión desde una secuencia de comandos de contenido, puedes llamar a chrome.runtime.getURL() para obtener la URL absoluta de tu recurso de extensión, como se muestra en el siguiente ejemplo (content.js):

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

Para usar fuentes o imágenes en un archivo CSS, puedes usar @@extension_id para crear una URL como se muestra en el siguiente ejemplo (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');
}

Todos los activos se deben declarar como recursos accesibles desde la Web en el archivo manifest.json:

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

Mantente protegido

Si bien los mundos aislados brindan una capa de protección, el uso de secuencias de comandos de contenido vulnerabilidades en una extensión y en la página web. Si la secuencia de comandos de contenido recibe contenido de un sitio web independiente, como llamar a fetch(), ten cuidado de filtrar el contenido según ataques de secuencia de comandos entre sitios antes de insertarlo. Solo comunícate a través de HTTPS para Evita los ataques de &quot;man-in-the-middle&quot;.

Asegúrate de filtrar las páginas web maliciosas. Por ejemplo, los siguientes patrones son peligrosos y lo que no está permitido en Manifest V3:

Qué no debes hacer

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
Qué no debes hacer

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

En su lugar, usa APIs más seguras que no ejecuten secuencias de comandos:

Qué debes hacer

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
Qué debes hacer

content-script.js

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