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 superior.
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:
domi18nstorageruntime.connect()runtime.getManifest()runtime.getURL()runtime.idruntime.onConnectruntime.onMessageruntime.sendMessage()
Las secuencias de comandos de contenido no pueden acceder directamente a otras APIs. Sin embargo, pueden acceder a ellas 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 a la Web. Ten en cuenta que esto también expone los recursos a cualquier secuencia de comandos propia o de terceros que se ejecute en el mismo sitio.
Trabaja en mundos aislados
Las secuencias de comandos de contenido se encuentran 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 o las secuencias de comandos de contenido de otras extensiones.
Una extensión puede ejecutarse en una página web con código similar al del 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 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.
Inserta secuencias de comandos
Las secuencias de comandos de contenido se pueden declarar de forma estática, declarar dinámicamente o insertarse de forma programática.
Inserta con declaraciones estáticas
Usa declaraciones estáticas de secuencias de comandos de contenido en manifest.json para las secuencias de comandos que se deben ejecutar automáticamente 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, archivos CSS o ambos. Todas las 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 |
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 Patrones de coincidencia y globs para obtener información sobre cómo excluir URLs. |
css |
array de cadenas | Opcional. Es la lista de archivos CSS que se insertarán en las páginas coincidentes. Estos se insertan en el orden en que aparecen en este array, antes de que se construya o muestre 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 automáticamente recortan. |
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
en el que el marco superior 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 marcos que se crearon con un origen coincidente, pero cuya URL o origen no coinciden directamente con el patrón. Estos incluyen marcos con diferentes esquemas, como
about:, data:, blob: y
filesystem:. Consulta también
Cómo insertar en marcos relacionados.
|
world |
ExecutionWorld |
Opcional. Es el mundo de JavaScript en el que se ejecutará una secuencia de comandos. El valor predeterminado es ISOLATED. Consulta también
Trabaja en mundos aislados.
|
Dentro de una etapa determinada del ciclo de vida del documento, las secuencias de comandos de contenido declaradas de forma estática en el manifiesto son las primeras en insertarse, antes que las secuencias de contenido registradas de cualquier otra manera. Se insertan en el orden en que se especifican en el manifiesto.
Inserta con declaraciones dinámicas
Las secuencias de comandos de contenido dinámico son útiles cuando los patrones de coincidencia para 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.
Introducidas en Chrome 96, las declaraciones dinámicas son similares a las declaraciones
estáticas, pero el objeto de secuencia de comandos de contenido se registra en Chrome con
métodos en el espacio de nombres chrome.scripting en lugar de en
manifest.json. La API de Scripting también permite a los desarrolladores de extensiones hacer lo siguiente:
- Registrar secuencias de comandos de contenido.
- Obtener una lista de secuencias de comandos de contenido registradas
- Actualizar la lista de secuencias de comandos de contenido registradas
- Quitar secuencias de comandos de contenido registradas.
Al igual que las declaraciones estáticas, las declaraciones 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"));
Inserta 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 tu 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"]
});
});
También se puede insertar y ejecutar un cuerpo de 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 chrome.scripting.executeScript(), no la función original. Como resultado, el cuerpo de la función debe ser independiente; las referencias a variables fuera de la función harán que la secuencia de comandos de contenido arroje un ReferenceError.
Cuando insertas 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 especificadas, incluye los siguientes campos en un registro declarativo.
| Nombre | Tipo | Descripción |
|---|---|---|
exclude_matches |
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 |
array de cadenas | Opcional. Se aplica después de matches para incluir solo las URLs que también
coinciden con este glob. Esto tiene como objetivo emular la @include
palabra clave de Greasemonkey. |
exclude_globs |
array de cadenas | Opcional. Se aplica después de matches para excluir las URLs que coinciden con este
glob. Tiene como objetivo emular la @exclude
Greasemonkey palabra clave. |
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
matchesy cualquier patróninclude_globs. - La URL tampoco coincide con un patrón
exclude_matchesoexclude_globs. Debido a que la propiedadmatcheses obligatoria,exclude_matches,include_globsyexclude_globssolo se pueden 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 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.
Por ejemplo, el glob https://???.example.com/foo/\* coincide con cualquiera de los siguientes:
https://www.example.com/foo/barhttps://the.example.com/foo/
Sin embargo, no coincide con lo siguiente:
https://my.example.com/foo/barhttps://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 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 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 preferido y
predeterminado es "document_idle". Consulta el tipo RunAt para conocer 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 |
cadena | Preferida: Usa "document_idle" siempre que sea posible.El navegador elige un momento para insertar secuencias de comandos entre "document_end" e inmediatamente 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 necesita ejecutarse después de window.onload, la extensión puede verificar si
onload ya se activó con la document.readyState
propiedad. |
document_start |
cadena | 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 |
cadena | Las secuencias de comandos se insertan inmediatamente después de que se completa el DOM, pero antes de que se carguen los subrecursos, como imágenes y marcos. |
Especifica marcos
Para las secuencias de comandos de contenido declarativas especificadas en el manifiesto, el "all_frames" campo permite que la extensión especifique si los archivos JavaScript y CSS se deben insertar en todos los marcos que coinciden con los requisitos de las URLs 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 secuencias de comandos de contenido de forma programática con chrome.scripting.registerContentScripts(...), se puede usar el parámetro allFrames para especificar si la secuencia de comandos de contenido se debe insertar en todos los marcos que coinciden con los requisitos de las URLs especificados o solo en el marco superior de una pestaña. Esto 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" ],
}]);
Inserta en marcos relacionados
Es posible que las extensiones deseen ejecutar secuencias de comandos en marcos relacionados con un marco coincidente, pero que no coincidan. Una situación común en la que esto sucede es para los marcos con URLs que se crearon con un marco coincidente, pero cuyas URLs no coinciden con los patrones especificados de la secuencia de comandos.
Este es el caso cuando una extensión desea insertar 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 superior en la URL
en absoluto, 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 en estos marcos, las extensiones pueden especificar la
"match_origin_as_fallback" propiedad en una especificación de 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 observa el origen del iniciador del marco para determinar si el marco coincide, en lugar de la URL del marco. Ten en cuenta que esto también puede ser diferente del origen del marco de destino (p.ej., las URLs data: tienen un origen nulo).
El iniciador del marco es el marco que creó o navegó por el marco de destino. Si bien suele ser el elemento superior o de apertura directo, es posible que no lo sea (como en el caso de un marco que navega por un iframe dentro de un iframe).
Como esto compara el origen del marco iniciador, el marco iniciador podría estar en cualquier ruta de acceso desde ese origen. Para aclarar esta implicación, Chrome
requiere que cualquier secuencia de comandos de contenido especificada con "match_origin_as_fallback"
establecida en true también especifique 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 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 publica mensajes a sí misma. La secuencia de comandos de contenido intercepta y examina este mensaje, y luego lo publica 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. Lo contrario es posible a través de medios similares.
Accede a 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 a 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 extensión, esto impide el uso de eval() y la carga de secuencias de comandos externas.
Para las extensiones sin empaquetar, 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 inserta 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 "man-in-the-middle" ataques.
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 APIs más seguras que no ejecuten 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);