javascript - foxified - ¿Puede un sitio invocar una extensión de navegador?
extensiones firefox (1)
Soy un novato en el desarrollo de extensiones de navegador y entiendo el concepto de extensiones de navegador que modifican la página y le insertan códigos.
¿Hay alguna manera de cambiar esta dirección? Escribo una extensión que proporciona un conjunto de API, y los sitios web que quieren usar mi extensión pueden detectar su presencia y, si está presente, el sitio web puede llamar a mis métodos API, como var extension = Extenion(foo, bar)
. ¿Es esto posible en Chrome, Firefox y Safari?
Ejemplo:
Google creó una nueva extensión llamada BeautifierExtension. Tiene un conjunto de API como objetos JS.
El usuario va a reddit.com. Reddit.com detecta BeautifierExtension e invoca la API llamando a
beautifer = Beautifier();
Ver # 2: normalmente es la extensión que detecta los sitios coincidentes y altera las páginas. Lo que me interesa saber es si el # 2 es posible.
Dado que Chrome presentó externally_connectable
, esto es bastante fácil de hacer en Chrome. Primero, especifique el dominio permitido en su archivo manifest.json
:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Use chrome.runtime.sendMessage
para enviar un mensaje desde la página:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
// ...
});
Finalmente, escucha en tu 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
compatibilidad con externally_connectable
, la respuesta original es la siguiente:
Responderé desde una perspectiva centrada en Chrome, aunque los principios que se describen aquí (inyecciones de scripts de páginas web, scripts de fondo de larga duración, paso de mensajes) son aplicables a prácticamente todos los marcos de extensión del navegador.
Desde un alto nivel, lo que desea hacer es inyectar un script de contenido 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 hacer algo, como enviar mensajes a la página de fondo y / o enviar un resultado de vuelta 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 entorno de ejecución de JavaScript de una página. Comparten el DOM, por lo que los eventos y los 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 de DOM: si un script de contenido agrega un elemento
<div>
a una página, 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 oyente se activará con éxito 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 podría intentar al configurar una API nueva). La página no puede ver ni ejecutarfoo
, porquefoo
existe solo en el entorno de ejecución del guión de contenido, no en el entorno de la página.
Entonces, ¿cómo puedes configurar una API adecuada? La respuesta viene en muchos pasos:
En un nivel bajo, haga su API basada en eventos . La página web dispara eventos DOM personalizados con
dispatchEvent
, y los scripts de contenido los escuchan conaddEventListener
, actuando cuando se reciben. Aquí hay una API simple de almacenamiento basada en eventos que una página web puede usar para tener la extensión para almacenar datos para ella: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 });
Página web sin extensión, utilizando 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 puede ver, la página web común está activando eventos que el script de contenido puede ver y en los que puede reaccionar, porque comparten el DOM. Los eventos tienen datos adjuntos, agregados en el constructor
CustomEvent
. 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 lo pases a la página de fondo 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
sí. 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.Inside 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 esté definida en
myapi.js
será accesible para la página principal. (Si está utilizando"manifest_version":2
, deberá incluirmyapi.js
en su lista de manifiestos deweb_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 la página web simple simplemente puede hacer:
sendDataToExtension("hello", "world");
Hay una arruga más en nuestro proceso API: el script
myapi.js
no estará disponible exactamente en el momento de la 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. Puedes resolver esto haciendo quemyapi.js
un evento "API listo", que tu 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 simple 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 scripts en el momento de la carga es establecer la propiedad
run_at
de la secuencia de comandos de contenido en manifest en"document_start"
esta manera:manifest.json:
"content_scripts": [ { "matches": ["https://example.com/*"], "js": [ "myapi.js" ], "run_at": "document_start" } ],
Extracto de documentos :
En el caso de "document_start", los archivos se inyectan después de cualquier archivo de css, pero antes de que se construya cualquier otro DOM o se ejecute cualquier otro script.
Para algunos contenidos, los guiones podrían ser más apropiados y de menos esfuerzo que tener el evento "API cargada".
Para enviar resultados a la página, debe proporcionar una función de devolución de llamada asincrónica. No hay forma de devolver un resultado de forma sincronizada desde su API, porque la activación / escucha de eventos es intrínsecamente 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 resumen: una secuencia de comandos de contenido puede escuchar eventos personalizados desde una página web normal, y la secuencia de comandos 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.