Modify a web page

Uno de los usos m√°s comunes para las extensiones es modificar p√°ginas web.  Por ejemplo, una extension puede querer cambiar el estilo de la p√°gina, esconder determinados nodos DOM o incluir otros nuevos.
 
Existen dos maneras de hacer esto con extensiones Web y APIs:
 
  • Declarativamente: Define un patr√≥n que encaja con un conjunto de URLs y carga un conjunto de scripts a las p√°ginas cuyos URL encajen con ese patr√≥n.
  • Programaticamente: Usando una Javascript API, carga una script en la p√°gina alojada en una leng√ľeta espec√≠fica.

De ambas formas, estos scripts se llaman scripts de contenido y son distintos del resto de scripts que constituyen una extensi√≥n. 

  • Solamente tienen acceso a un grupo peque√Īo de extensiones web y APIs.
  • Obtienen acceso directo a la p√°gina web en la que han sido cargadas.
  • Se comunican con el resto de la extensi√≥n usando una API de mensajer√≠a.

En este artículo hablaremos de los dos métodos para cargar una script..

Modificando páginas que encajen con un patrón URL

Para empezar, crea un nuevo directorio llamado "modify-page". En este directorio, crea un archivo llamado "manifest.json", con el siguiente contenido:

{

  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",

  "content_scripts": [
    {
      "matches": ["https://developer.mozilla.org/*"],
      "js": ["page-eater.js"]
    }
  ]

}

La llave content_scripts es el m√©todo de carga de scripts a p√°ginas cuya URL encaje con los patrones. En este caso, content_scripts le dice al navegador que cargue una script llamada "page-eater.js" en todas las p√°ginas con https://developer.mozilla.org/.

Debido a que el atributo "js" de content_scripts es una array, puedes usarla para inyectar m√°s de una script a las p√°ginas que encajen con el patr√≥n. Si haces esto las p√°ginas compartiran el mismo campo de aplicaci√≥n, igual que m√ļltiples scripts cargadas por una p√°gina, y son cargadas en el mismo orden en las que est√°n dispuestas en la array.

La llave content_scripts tambien tiene un atributo de "css" que puedes usar para inyectar c√≥digo CSS. 

Despu√©s, crea un archivo llamado "page-eater.js" dentro del directorio "modify-page" e introduce el contenido de a continuaci√≥n:

document.body.textContent = "";

var header = document.createElement('h1');
header.textContent = "This page has been eaten";
document.body.appendChild(header);

 Ahora, instala la extensi√≥n y visita https://developer.mozilla.org/:

Ten en cuenta que aunque este video muestra el contenido de la script operando en addons.mozilla.org, las scripts de contenido est√°n bloqueadas en esta p√°gina por el momento.

Modificando las p√°ginas programaticamente

Y si quieres comer p√°ginas, pero solo cuando el usuario te lo pida?  Actualicemos este ejemplo para que podamos enyectar el contenido de la script cuando el usuario haga click en un item del menu de contexto.

Primero actualiza "manifest.json" para que incluya el contenido a continuaci√≥n:

{

  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",

  "permissions": [
    "activeTab",
    "contextMenus"
  ],

  "background": {
    "scripts": ["background.js"]
  }

}

Aqu√≠, hemos eliminado la llave content_scripts  y hemos a√Īadido dos nuevas llaves:

  • permissions: Para inyectar scripts a las p√°ginas necesitamos los permisos para la p√°gina que estamos modificando. El permiso activeTab es una manera de obtener el permiso temporalmente para la leng√ľeta que est√© actualmente abierta. Tambi√©n necesitamos el permiso contextMenus para poder a√Īadir items al menu de contexto.
  • background: Usamos esto para cargar un "background script" persistente llamado "background.js", en el cual montamos el menu de contexto e inyectamos el script de contenido.

Ahora generaremos este archivo. Crea un archivo llamado "background.js" en el directorio "modify-page" e introduce el siguiente contenido: 

browser.contextMenus.create({
  id: "eat-page",
  title: "Eat this page"
});

browser.contextMenus.onClicked.addListener(function(info, tab) {
  if (info.menuItemId == "eat-page") {
    browser.tabs.executeScript({
      file: "page-eater.js"
    });
  }
});

En esta script estamos creando un item del menu de contexto  y d√°ndole una ID y un t√≠tulo espec√≠fico (el texto que se estar√° expuesto en el menu de contexto). Despu√©s configuramos un evento de escucha para que cuando el usuario haga click en uno de los items del menu podamos comprobar si se trata de nuestro item eat-page . En caso afirmativo, inyectaremos "page-eater.js" a la leng√ľeta actual usando la API tabs.executeScript(). Alternativamente, esta API puede tomar como argumento una ID. En este caso la hemos omitido lo cual quiere decir que la script es inyectada en la leng√ľeta que est√° actualmente abierta.

Ahora mismo la extensión debería ser algo como esto:

modify-page/
    background.js
    manifest.json
    page-eater.js

Ahora recarga la extensi√≥n, abre la p√°gina (cualquier p√°gina en esta ocasi√≥n), activa el menu de contexto y selecciona "Eat this page" (Comer esta p√°gina):

Ten en cuenta que aunque este video muestra el contenido de la script operando en addons.mozilla.org, las scripts de contenido est√°n bloqueadas en esta p√°gina por el momento.

Mensajería

Scripts de contenido y scripts de fondo no pueden acceder directamente al estado del otro. Sin embargo, pueden comunicarse mediante el uso de mensajes. Una terminal configura un escuchador de mensajes y la otra terminal puede mandarle un mensaje. La siguente tabla resume las APIs involucradas en cada parte:

  En el script de contenido En el script de fondo
Mandar un mensaje browser.runtime.sendMessage() browser.tabs.sendMessage()
Recibir un mensaje browser.runtime.onMessage browser.runtime.onMessage

Actualicemos nuestro ejemplo para ilustrar como mandar un mensaje desde una script de fondo.

Primero, hemos de editar "background.js" para que tenga el siguiente contenido:

browser.contextMenus.create({
  id: "eat-page",
  title: "Eat this page"
});

function messageTab(tabs) {
  browser.tabs.sendMessage(tabs[0].id, {
    replacement: "Message from the extension!"
  });
}

function onExecuted(result) {
    var querying = browser.tabs.query({
        active: true,
        currentWindow: true
    });
    querying.then(messageTab);
}

browser.contextMenus.onClicked.addListener(function(info, tab) {
  if (info.menuItemId == "eat-page") {
    let executing = browser.tabs.executeScript({
      file: "page-eater.js"
    });
    executing.then(onExecuted);
  }
});

Ahora, despu√©s de inyectar "page-eater.js", hemos de usar tabs.query()  para obtener la leng√ľeta actualmente activa y entonces usar tabs.sendMessage() para mandar un mensaje a las scripts de contenido cargadas en la leng√ľeta. El mensaje tiene el contenido {replacement: "Message from the extension!"}.

Después, actualiza "page-eater.js" de esta forma:

function eatPageReceiver(request, sender, sendResponse) {
  document.body.textContent = "";
  var header = document.createElement('h1');
  header.textContent = request.replacement;
  document.body.appendChild(header);
}
browser.runtime.onMessage.addListener(eatPageReceiver);

Ahora, en vez de simplemente comer la p√°gina, el contenido espera a un mensaje usando runtime.onMessage.  Cuando el mensaje llega, el script de contenido ejecuta el mismo c√≥digo de antes, exceptuando que el  texto de reemplazo se obtenido de request.replacement.

Debido a que tabs.executeScript() es una funci√≥n asincr√≥nica y para asegurarnos de que mandamos el mensaje solo cuando el escuchador ha sido a√Īadido en "page-eater.js", usamos onExecuted que sera llamado despu√©s de que "page-eater.js" se ejecute.

Pulsa Ctrl+Shift+J (o Cmd+Shift+J en el Mac)  o web-ext run --bc para abrir la consola de navegaci√≥n para ver console.log en la script de fondo. Alternativamente puedes usar el Add-on Debugger, el cual te permite poner un breakpoint.  De momento no hay forma de iniciar un Add-on Debugger directamente de una extensi√≥n web.

Si queremos enviar mensajes directamente desde el contenido script de vuelta a la p√°gina de fondo, podr√≠amos usarruntime.sendMessage() en vez de  tabs.sendMessage(). Por ejemplo:

browser.runtime.sendMessage({
    title: "from page-eater.js"
});

Todos estos ejemplos inyectan Javascript; también puedes inyectar CSS programaticamente usando la funcióntabs.insertCSS().

Aprende m√°s