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:

json
{
  "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/.

Nota: 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.

Nota: 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:

js
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/:

Nota: 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:

json
{
  "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:

js
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):

Nota: 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:

js
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:

js
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.

Nota: 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:

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

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

Aprende más