Desde que Chrome introdujo externally_connectable
, esto es bastante fácil de hacer en Chrome. En primer lugar, especificar el dominio permitido en su manifest.json
archivo:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Uso chrome.runtime.sendMessage
para enviar un mensaje desde la página:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
// ...
});
Por último, escuchar en su página de fondo con chrome.runtime.onMessageExternal
:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
// verify `sender.url`, read `request` object, reply with `sednResponse(...)`...
});
Si no tiene acceso a externally_connectable
soporte, la respuesta original sigue:
Responderé desde una perspectiva centrada en Chrome, aunque los principios descritos aquí (inyecciones de script de página web, scripts de fondo de larga duración, paso de mensajes) son aplicables a prácticamente todos los marcos de extensión de navegador .
Desde un nivel alto, lo que quiere hacer es inyectar un content script en cada página web, que agrega una API, accesible a la página web. Cuando el sitio llama a la API, la API activa el script de contenido para que haga algo, como enviar mensajes a la página de fondo y/o enviar un resultado al script de contenido, a través de una devolución de llamada asincrónica.
La principal dificultad aquí es que los scripts de contenido que se "inyectan" en una página web no pueden alterar directamente el código JavaScript execution environment de una página. Comparten el DOM, por lo que eventos y cambios en la estructura DOM se comparten entre el script de contenido y la página web, pero las funciones y variables no se comparten. Ejemplos:
manipulación DOM: Si un script contenido añade un elemento <div>
a una página, que funcionará como se espera. Tanto el script de contenido como la página verán el nuevo <div>
.
Eventos: Si un script de contenido configura un detector de eventos, por ejemplo, para clics en un elemento, el detector se activará correctamente cuando ocurra el evento. Si la página configura un oyente para los eventos personalizados activados desde el script de contenido, se recibirán correctamente cuando el script de contenido los active.
Funciones: Si el script de contenido define una nueva función global foo()
(como se podría tratar cuando la creación de una nueva API). La página no puede ver o ejecutar foo
, porque foo
existe solo en el entorno de ejecución del script de contenido, no en el entorno de la página.
Entonces, ¿cómo se puede configurar una API adecuada? La respuesta viene en muchos pasos:
a un bajo nivel, haga su API event-based. La página web dispara eventos DOM personalizados con dispatchEvent
, y los scripts de contenido los escuchan con addEventListener
, actuando cuando se reciben. He aquí una sencilla API de almacenamiento basado en evento que una página web puede utilizar para tener la extensión para almacenar datos para ello:
content_script.js (en su extensión):
// an object used to store things passed in from the API
internalStorage = {};
// listen for myStoreEvent fired from the page with key/value pair data
document.addEventListener('myStoreEvent', function(event) {
var dataFromPage = event.detail;
internalStorage[dataFromPage.key] = dataFromPage.value
});
no extensión página web, usando su API basada en eventos:
function sendDataToExtension(key, value) {
var dataObj = {"key":key, "value":value};
var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj});
document.dispatchEvent(storeEvent);
}
sendDataToExtension("hello", "world");
Como se puede ver, la página web ordinaria se arranques que el guión de contenido puede ver y reaccionar ante, ya que comparten t el DOM. Los eventos tienen datos adjuntos, agregados en el CustomEvent
constructor. Mi ejemplo aquí es lamentablemente simple: obviamente puedes hacer mucho más en tu script de contenido una vez que tenga los datos de la página (lo más probable es que pass it en el background page para su posterior procesamiento).
Sin embargo, esto es solo la mitad de la batalla. En mi ejemplo anterior, la página web ordinaria tenía que crear sendDataToExtension
. Crear y activar eventos personalizados es bastante detallado (mi código ocupa 3 líneas y es relativamente breve). No desea obligar a un sitio a escribir código arcano de activación de eventos solo para usar su API. La solución es un truco desagradable: añada una etiqueta <script>
a su DOM compartido, que agrega el código de activación de eventos al entorno de ejecución de la página principal.
interior content_script.js:
// inject a script from the extension's files
// into the execution environment of the main page
var s = document.createElement('script');
s.src = chrome.extension.getURL("myapi.js");
document.documentElement.appendChild(s);
Cualquier función que se definen en myapi.js
será accesible a la página principal. (Si usa "manifest_version":2
, deberá incluir myapi.js
en la lista de manifiesto de web_accessible_resources
).
myapi.JS:
function sendDataToExtension(key, value) {
var dataObj = {"key":key, "value":value};
var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj});
document.dispatchEvent(storeEvent);
}
Ahora el página Web sencilla simplemente puede hacer:
sendDataToExtension("hello", "world");
No es uno más arrugas a nuestro proceso de API: la secuencia de comandos myapi.js
no estará disponible exactamente en tiempo de carga. En su lugar, se cargará un tiempo después del tiempo de carga de la página. Por lo tanto, la página web simple necesita saber cuándo puede llamar a su API de manera segura. Puede resolver esto haciendo que myapi.js
active un evento "API listo", que su página escuchará.
myapi.js:
function sendDataToExtension(key, value) {
// as above
}
// since this script is running, myapi.js has loaded, so let the page know
var customAPILoaded = new CustomEvent('customAPILoaded');
document.dispatchEvent(customAPILoaded);
página web Llanura usando API:
document.addEventListener('customAPILoaded', function() {
sendDataToExtension("hello", "world");
// all API interaction goes in here, now that the API is loaded...
});
Otra solución al problema de la disponibilidad de la escritura en tiempo de carga está fijando run_at
propiedad del contenido secuencia de comandos en manifiesto a "document_start"
de esta manera:
manifest.json:
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": [
"myapi.js"
],
"run_at": "document_start"
}
],
Extracto de docs:
En el caso de "document_start", los archivos se inyecta después de cualquier archivo de css, pero antes de que se construye cualquier otra DOM o cualquier otra secuencia de comandos se ejecuta.
Para algunos contenidos que podrían ser más apropiados y de menos esfuerzo que tener el evento "API cargado".
Para enviar los resultados atrás a la página, debe proporcionar una función de devolución de llamada asincrónica. No hay forma de devolver un resultado de manera sincronizada desde su API, porque la activación/escucha de eventos es inherentemente asíncrona (es decir, su función API del lado del sitio finaliza antes de que el script de contenido reciba el evento con la solicitud API).
myapi.js:
function getDataFromExtension(key, callback) {
var reqId = Math.random().toString(); // unique ID for this request
var dataObj = {"key":key, "reqId":reqId};
var fetchEvent = new CustomEvent('myFetchEvent', {"detail":dataObj});
document.dispatchEvent(fetchEvent);
// get ready for a reply from the content script
document.addEventListener('fetchResponse', function respListener(event) {
var data = event.detail;
// check if this response is for this request
if(data.reqId == reqId) {
callback(data.value);
document.removeEventListener('fetchResponse', respListener);
}
}
}
content_script.js (en su extensión):
// listen for myFetchEvent fired from the page with key
// then fire a fetchResponse event with the reply
document.addEventListener('myStoreEvent', function(event) {
var dataFromPage = event.detail;
var responseData = {"value":internalStorage[dataFromPage.key], "reqId":data.reqId};
var fetchResponse = new CustomEvent('fetchResponse', {"detail":responseData});
document.dispatchEvent(fetchResponse);
});
página Web ordinaria:
document.addEventListener('customAPILoaded', function() {
getDataFromExtension("hello", function(val) {
alert("extension says " + val);
});
});
El reqId
es necesario en caso de que tenga varias solicitudes al mismo tiempo, para que no lean las respuestas incorrectas.
¡Y creo que eso es todo!Por lo tanto, no es para corazones débiles, y posiblemente no valga la pena, si tenemos en cuenta que otras extensiones también pueden vincular a los oyentes con sus eventos para espiar cómo una página usa su API. Solo sé todo esto porque hice una API de criptografía de prueba de concepto para un proyecto escolar (y posteriormente aprendí los principales problemas de seguridad asociados con ella).
En suma: Un script de contenido puede escuchar eventos personalizados desde una página web normal y el script también puede insertar un archivo de script con funciones que facilitan a las páginas web disparar esos eventos. El script de contenido puede pasar mensajes a una página de fondo, que luego almacena, transforma o transmite datos del mensaje.
Gracias por las explicaciones detalladas! +1 para señalar el entorno de ejecución. – keewooi
Considere actualizar su respuesta con el constructor 'CustomEvent'. Su sintaxis se ve mucho mejor que el método obsoleto 'document.createEvent'. –
¿Hay alguna manera de verificar los detalles de seguridad de la página/estado de HSTS antes de inyectar el script? – r3m0t