Las secuencias de comandos de contenido son archivos que se ejecutan en el contexto de las páginas web. Con el modelo de objetos del documento (DOM) estándar, pueden leer detalles de las páginas web que visita el navegador, realizar cambios en ellas y pasar información a su extensión principal.
Información sobre las capacidades de las secuencias de comandos de contenido
Las secuencias de comandos de contenido pueden acceder directamente a las siguientes APIs de extensiones:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Las secuencias de comandos de contenido no pueden acceder a otras APIs directamente. 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 ello, debes declararlos como recursos accesibles desde la Web. Ten en cuenta que esto también expone los recursos a cualquier secuencia de comandos de terceros o de origen que se ejecute en el mismo sitio.
Trabaja en mundos aislados
Las secuencias de comandos de contenido se ejecutan en un mundo aislado, lo que permite que una secuencia de comandos de contenido realice cambios en su entorno de JavaScript sin entrar en conflicto con la página ni con las secuencias de comandos de contenido de otras extensiones.
Una extensión puede ejecutarse en una página web con 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 con una de las técnicas que se describen en la sección Cómo insertar 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 aparecen en secuencia cuando se hace clic en el botón.
Inyectar secuencias de comandos
Los secuencias de comandos de contenido se pueden declarar de forma estática, declarar de forma dinámica o insertar de forma programática.
Cómo insertar con declaraciones estáticas
Usa declaraciones de secuencias de comandos de contenido estáticas en manifest.json para las secuencias de comandos que se deben ejecutar automáticamente en un conjunto conocido de páginas.
Las secuencias de comandos declaradas de forma estática se registran en el manifiesto bajo la clave "content_scripts"
.
Pueden incluir archivos JavaScript, archivos CSS o ambos. Todos los secuencias de comandos de contenido de ejecución automática deben especificar patrones de coincidencia.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
Nombre | Tipo | Descripción |
---|---|---|
matches |
Es un array de cadenas. | Obligatorio. Especifica en qué páginas se insertará este script de contenido. Consulta Patrones de coincidencia para obtener detalles sobre la sintaxis de estas cadenas y Patrones de coincidencia y comodines para obtener información sobre cómo excluir URLs. |
css |
Es un array de cadenas. | Opcional. Es la lista de archivos CSS que se insertarán en las páginas coincidentes. Se insertan en el orden en que aparecen en este array, antes de que se construya o muestre cualquier DOM para la página. |
js |
|
Opcional. Es la lista de archivos JavaScript que se insertarán en las páginas coincidentes. Los 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 diagonales iniciales (“/”) se quitan automáticamente. |
run_at |
RunAt | Opcional. Especifica cuándo se debe insertar la secuencia de comandos en la página. El valor predeterminado es
document_idle . |
match_about_blank |
booleano | Opcional. Indica si la secuencia de comandos debe insertarse en un marco about:blank donde el marco principal o de apertura coincida con uno de los patrones declarados en matches . La configuración predeterminada es "false". |
match_origin_as_fallback |
booleano |
Opcional. Indica si la secuencia de comandos debe insertarse en los marcos creados por un origen coincidente, pero cuya URL u origen no coincidan directamente con el patrón. Estos incluyen fotogramas con diferentes esquemas, como about: , data: , blob: y filesystem: . Consulta también Cómo insertar en marcos relacionados.
|
world |
ExecutionWorld |
Opcional. Es el entorno de JavaScript en el que se ejecutará un script. 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 no son bien conocidos o cuando las secuencias de comandos de contenido no siempre deben insertarse en hosts conocidos.
Las declaraciones dinámicas, que se introdujeron en Chrome 96, son similares a las declaraciones estáticas, pero el objeto de la secuencia de comandos de contenido se registra en Chrome con métodos del espacio de nombres chrome.scripting
en lugar de en manifest.json. La API de secuencias de comandos también permite que los desarrolladores de extensiones realicen las siguientes acciones:
- Registra secuencias de comandos de contenido.
- Obtén una lista de los secuencias de comandos de contenido registrados.
- 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 dinámicas pueden incluir archivos JavaScript, archivos 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 forma programática
Usa la inserción programática para las secuencias de comandos de contenido que deben ejecutarse en respuesta a eventos o en ocasiones específicas.
Para insertar una secuencia de comandos de contenido de forma programática, tu extensión necesita permisos de host para la página en la que intenta insertar secuencias de comandos. Los permisos de host se pueden otorgar solicitándolos como parte del manifiesto de la extensión o de forma temporal con "activeTab"
.
A continuación, se muestran diferentes versiones 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, se puede insertar y ejecutar el cuerpo de una función 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 insertada es una copia de la función a la que se hace referencia en la llamada a chrome.scripting.executeScript()
, no la función original en sí. Como resultado, el cuerpo de la función debe ser autónomo; las referencias a variables fuera de la función harán que la secuencia de comandos de contenido arroje un ReferenceError
.
Cuando se inyecta 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" ],
});
});
Excluye coincidencias y globs
Para personalizar la coincidencia de páginas especificada, incluye los siguientes campos en un registro declarativo.
Nombre | Tipo | Descripción |
---|---|---|
exclude_matches |
Es un array de cadenas. | Opcional. Excluye las páginas en las que, de lo contrario, se insertaría esta secuencia de comandos de contenido. Consulta Patrones de coincidencia para obtener detalles sobre la sintaxis de estas cadenas. |
include_globs |
Es un array de cadenas. | Opcional. Se aplica después de matches para incluir solo las URLs que también coinciden con este comodín. Esto tiene como objetivo emular la palabra clave de Greasemonkey @include . |
exclude_globs |
Array de cadenas | Opcional. Se aplica después de matches para excluir las URLs que coinciden con este patrón. Se diseñó para emular la palabra clave @exclude de Greasemonkey. |
La secuencia de comandos de contenido se insertará en una página si se cumplen las siguientes condiciones:
- Su URL coincide con cualquier patrón
matches
y cualquier patróninclude_globs
. - La URL tampoco coincide con un patrón de
exclude_matches
oexclude_globs
. Dado que la propiedadmatches
es obligatoria,exclude_matches
,include_globs
yexclude_globs
solo se pueden usar para limitar las páginas que se verán afectadas.
La siguiente extensión inyecta 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 siguen una sintaxis diferente y más flexible que los patrones de coincidencia. Las cadenas glob aceptables son URLs que pueden contener asteriscos y signos de interrogación "comodín". El asterisco (*
) coincide con cualquier cadena de cualquier longitud, incluida la cadena vacía, mientras que el signo de interrogación (?
) coincide con cualquier carácter único.
Por ejemplo, el comodín https://???.example.com/foo/\*
coincide con cualquiera de los siguientes elementos:
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 inyecta 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 inyecta la secuencia de comandos de contenido en https://history.nytimes.com
y https://.nytimes.com/history
, pero no en https://science.nytimes.com
ni 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 uno, todos o algunos de estos elementos 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. El valor predeterminado y preferido es "document_idle"
. Consulta el tipo RunAt para ver otros valores posibles.
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 | Preferida: Usa "document_idle" siempre que sea posible.El navegador elige un momento para insertar secuencias de comandos entre "document_end" y justo después de que se active el evento window.onload . El momento exacto de la inserción depende de la complejidad del documento y del tiempo que tarda en cargarse, y se optimiza para la velocidad de carga de la página.Las secuencias de comandos de contenido que se ejecutan en "document_idle" no necesitan escuchar el evento window.onload , ya que se garantiza que se ejecutarán después de que se complete el DOM. Si una secuencia de comandos definitivamente debe ejecutarse después de window.onload , la extensión puede verificar si onload ya se activó con la propiedad document.readyState . |
document_start |
string | Las secuencias de comandos se insertan después de cualquier archivo de css , pero antes de que se construya cualquier otro DOM o se ejecute 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 se carguen los recursos secundarios, como imágenes y marcos. |
Cómo especificar fotogramas
En el caso de las secuencias de comandos de contenido declarativas especificadas en el manifiesto, el campo "all_frames"
permite que la extensión especifique si los archivos JavaScript y CSS se deben insertar en todos los marcos que coincidan con los requisitos de URL especificados o solo en el marco superior de una pestaña:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Cuando registras de forma programática secuencias de comandos de contenido con chrome.scripting.registerContentScripts(...)
, puedes usar el parámetro allFrames
para especificar si la secuencia de comandos de contenido se debe insertar en todos los marcos que coincidan con los requisitos de URL especificados o solo en el marco superior de una pestaña. Solo se puede usar con tabId y no se puede usar si se especifican frameIds o documentIds:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Inyectar en fotogramas relacionados
Es posible que las extensiones deseen ejecutar secuencias de comandos en marcos relacionados con un marco coincidente, pero que no coincidan por sí mismos. Un caso común en el que esto sucede es con los marcos que tienen URLs creadas por un marco coincidente, pero cuyas URLs no coinciden con los patrones especificados del script.
Este es el caso cuando una extensión desea insertar contenido en marcos con URLs que tienen esquemas about:
, data:
, blob:
y filesystem:
. En estos casos, la URL no coincidirá con el patrón de la secuencia de comandos de contenido (y, en el caso de about:
y data:
, ni siquiera incluirá la URL o el origen principal en la URL, como en about:blank
o data:text/html,<html>Hello, World!</html>
). Sin embargo, estos marcos aún se pueden asociar con el marco de creación.
Para insertar contenido en estos marcos, las extensiones pueden especificar la propiedad "match_origin_as_fallback"
en una especificación de la secuencia de comandos de contenido en el manifiesto.
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 analizará el origen del iniciador del iframe para determinar si el iframe coincide, en lugar de analizar la URL del iframe en sí. Ten en cuenta que esto también puede ser diferente del origen del fotograma de destino (p.ej., Las URLs data:
tienen un origen nulo).
El iniciador del fotograma es el fotograma que creó el fotograma de destino o navegó a él. Si bien suele ser el elemento superior o el elemento que abrió la ventana de forma directa, es posible que no lo sea (como en el caso de un marco que navega por un iframe dentro de un iframe).
Dado que esto compara el origen del fotograma del iniciador, este podría estar en cualquier ruta de acceso desde ese origen. Para que esta implicación quede clara, Chrome requiere que cualquier secuencia de comandos de contenido especificada con "match_origin_as_fallback"
establecida en true
también especifique una ruta 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 las alojan están aislados entre sí, comparten el acceso al DOM de la página. Si la página desea comunicarse con la secuencia de comandos de contenido o con la extensión a través de la secuencia de comandos de 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 que no es de extensión, example.html, se envía mensajes a sí misma. La secuencia de comandos de contenido intercepta e inspecciona este mensaje, y luego lo publica en el proceso de la extensión. De esta manera, la página establece una línea de comunicación con el proceso de extensión. También es posible hacer lo contrario con 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 construir 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 recursos deben declararse 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/*" ]
}
],
...
}
Política de Seguridad del Contenido
Las secuencias de comandos de contenido que se ejecutan en mundos aislados tienen la siguiente Política de Seguridad del Contenido (CSP):
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Al igual que las restricciones aplicadas a otros contextos de extensiones, esto impide el uso de eval()
y la carga de secuencias de comandos externas.
En el caso de las extensiones no empaquetadas, la CSP también incluye localhost:
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Cuando se inyecta una secuencia de comandos de contenido en el mundo principal, se aplica la CSP de la página.
Mantente protegido
Si bien los mundos aislados proporcionan una capa de protección, el uso de secuencias de comandos de contenido puede crear 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, por ejemplo, llamando a fetch()
, ten cuidado de filtrar el contenido contra ataques de secuencias de comandos entre sitios antes de insertarlo. Comunícate solo a través de HTTPS para evitar ataques de "man-in-the-middle".
Asegúrate de filtrar las páginas web maliciosas. Por ejemplo, los siguientes patrones son peligrosos y no se permiten en Manifest V3:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
content-script.js
const elmt_id = ... // WARNING! elmt_id might be '); ... evil script ... //'! window.setTimeout("animate(" + elmt_id + ")", 200);
En su lugar, prefiere las APIs más seguras que no ejecutan secuencias de comandos:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);