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.
Comprende las funciones de las secuencias de comandos de contenido
Las secuencias de comandos de contenido pueden acceder directamente a las siguientes APIs de extensión:
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 a través de 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.
Trabajar en mundos aislados
Las secuencias de comandos del contenido viven en un mundo aislado, lo que les permite realizar cambios en su entorno de JavaScript sin entrar en conflicto con la página o las secuencias de comandos del contenido de otras extensiones.
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 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.
Cómo insertar secuencias de comandos
Las secuencias de comandos de contenido se pueden declarar de forma estática, de forma dinámica o inyectar de forma programática.
Cómo insertar 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 ejecutar automáticamente en un conjunto conocido de páginas.
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 glob 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. Se insertan en el orden en que aparecen en este array, antes de que se construya o muestre ningún 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 cortan 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 |
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 creó un origen coincidente, pero cuya URL o origen pueden no coincidir 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 ejecuta 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 no son muy 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 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:
- Registra las secuencias de comandos de 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, 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 insertar de manera 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. Para otorgar permisos de host, puedes solicitarlos como parte del manifiesto de tu extensión o usar "activeTab"
de forma temporal.
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 a chrome.scripting.executeScript()
, no la función original. 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 inserta como una función, también puedes pasarle argumentos.
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 |
array de cadenas | Opcional. Excluye las páginas en las que, de otro modo, 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 aquellas URLs que también coinciden con este glob. El objetivo es emular la palabra clave @include de Greasemonkey. |
exclude_globs |
array de cadenas | Opcional. Se aplica después de matches para excluir las URLs que coincidan con este
glob. Su objetivo es 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
exclude_matches
oexclude_globs
. Debido a que la propiedadmatches
es obligatoria,exclude_matches
,include_globs
yexclude_globs
solo se pueden usar para limitar qué páginas 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 concordancia. Las cadenas de 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/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 del 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 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, de inmediato, después de que se activa 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 está optimizado 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 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 subrecursos, como las imágenes y los marcos. |
Especifica marcos
En el caso de las secuencias de comandos de contenido declarativo 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 la URL especificada 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 se registran 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 coincidan con los requisitos de la URL especificada 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" ],
}]);
Cómo insertar en marcos relacionados
Es posible que las extensiones deseen ejecutar secuencias de comandos en marcos que estén relacionados con un marco coincidente, pero que no coincidan. Una situación común cuando esto ocurre es para los marcos con URLs que creó 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 incluye la URL superior o el origen 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 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
manifiesto.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Cuando se especifique y se establezca en true
, Chrome buscará el origen del iniciador del marco para determinar si coincide, en lugar de la URL del marco en sí. Ten en cuenta que esto también puede ser diferente del origen del fotograma de destino (p.ej., Las URLs de data:
tienen un origen nulo).
El iniciador del marco es el marco que creó o navegó el marco de destino. Si bien, por lo general, es el elemento superior o el elemento 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).
Debido a que esto compara el origen del fotograma del iniciador, el fotograma del iniciador podría estar en cualquier ruta de acceso desde ese origen. Para que esta implicación quede clara, Chrome requiere que las secuencias de comandos de contenido especificadas con "match_origin_as_fallback"
establecidas en true
también especifiquen 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 ella, debe hacerlo a través del DOM compartido.
Se puede obtener 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, publica mensajes para sí misma. La secuencia de comandos de contenido intercepta y 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. Lo inverso es posible a través de medios similares.
Cómo acceder 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 través de 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 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 secuencia 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 el manifiesto 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);