Categorías: Labs

Australis para desarrolladores de complementos – parte 2

Nota: por si no lo sabes, actualmente hay una competencia abierta de desarrollo de complementos, enfocada en Australis. La competencia termina el 15 de abril, ¡así que todavía tienes tiempo para participar!

En el artículo anterior sobre Australis, cubrí lo que necesitas cambiar en una extensión para adaptarla para las nuevas barras de herramientas en Australis. En esta edición voy a cubrir una adición muy emocionante que facilita la creación de widgets para las barras de herramientas: el módulo CustomizableUI.

El tamaño de este módulo y su documentación pueden parecer agobiantes, pero en realidad es muy sencillo de utilizar si lo que necesitas hacer es añadir un botón a una de las áreas personalizables en Firefox. Hice un par de extensiones sencillas para probarlo y demostrar cómo funciona.

Los demos sólo funcionan en versiones de Firefox que incluyen Australis, así que para probarlos necesitas Firefox 29 (actualmente en beta) o alguna versión superior.

1. Demo “Hello”

XPI. Código en Github.

Para el primer demo decidí hacer el clásico ejemplo “Hola mundo” (“Hello World”). También lo quise hacer como una extensión restartless (sin reinicio para instalar), que es donde creo que CustomizableUI realmente se destaca. Crear un complemento restartless que añade un botón a la barra de herramientas nunca ha sido así de fácil.

Primero necesitas importar el módulo, por supuesto:

Cu.import("resource:///modules/CustomizableUI.jsm");

Luego necesitas crear el widget en el proceso de startup usando la función createWidget:

CustomizableUI.createWidget(
  { id : "aus-hello-button",
    defaultArea : CustomizableUI.AREA_NAVBAR,
    label : "Hello Button",
    tooltiptext : "Hello!",
    onCommand : function(aEvent) {
      let win = aEvent.target.ownerDocument.defaultView;
 
      win.alert("Hello!");
    }
  });

El ID del widget es el mismo ID que el botón va a tener en el DOM. El defaultArea es donde el widget será añadido, en este caso la barra de herramientas principal. La lista de áreas posibles se encuentra en la documentación. Normalmente querrás usar AREA_NAVBAR o AREA_PANEL en el caso del panel de menú (activado por el botón al final de la barra de herramientas).

El código del evento principal (command) es simple: usar el parámetro aEvent para obtener la ventana del navegador y luego mostrar una alerta sencilla.

Finalmente vas a querer hacer limpieza en el proceso shutdown:

CustomizableUI.destroyWidget("aus-hello-button");

Con sólo esta ya tienes un complemento funcional que agrega un botón. El botón se puede personalizar y mover a otras partes, y su posición será recordada. Funciona donde sea que se ponga. Sin embargo no tiene nada de CSS, así que no tiene un ícono todavía (también le falta localización, pero eso se puede agregar con un stringbundle).

Para una extensión “normal” que requiere reinicio, agregar estilos es muy sencillo. Crear un archivo chrome.manifest, declarar un skin y luego un style que aplica a browser.xul:

style chrome://browser/content/browser.xul chrome://aus-hello/skin/toolbar.css

Para un complemento restartless esto es un poco más complicado porque en este caso no funcionan las directivas style. Igual necesitas el manifiesto y el skin, pero el estilo tendrá que ser inyectado, en este caso usando el servicio de estilos:

let io =
  Cc["@mozilla.org/network/io-service;1"].
    getService(Ci.nsIIOService);
 
this._ss =
  Cc["@mozilla.org/content/style-sheet-service;1"].
    getService(Ci.nsIStyleSheetService);
this._uri = io.newURI("chrome://aus-hello/skin/toolbar.css", null, null);
this._ss.loadAndRegisterSheet(this._uri, this._ss.USER_SHEET);

Esto carga la hoja de estilos para todos los documentos, así que es prudente limitar los estilos al documento deseado:

@-moz-document url("chrome://browser/content/browser.xul") {
/* Código */
}

Y luego hacer limpieza:

if (this._ss.sheetRegistered(this._uri, this._ss.USER_SHEET)) {
  this._ss.unregisterSheet(this._uri, this._ss.USER_SHEET);
}

Nota: cargar la hoja estilos de esta manera es muy ineficiente. El segundo demo lo hace de una mejor manera con la función loadSheet, pero ésto hubiera requerido mucho código adicional para este ejemplo.

¡Listo! Con los íconos 16×16 y 32×32 como se recomiendan en la primera parte, deberías tener un “Hola Mundo” funcional.

2. Demo de vista

XPI. Código en Github.

Otro tipo de widget que puedes crear es un view (vista). Esto es un botón que muestra un panel cuando está en la barra de herramientas, o un panel lateral cuando está en el panel de menú. Los widgets de Marcadores e Historial son buenos ejemplos de views.

Para crear el widget, el código es ligeramente distinto:

CustomizableUI.createWidget(
  { id : "aus-view-button",
    type : "view",
    viewId : "aus-view-panel",
    defaultArea : CustomizableUI.AREA_NAVBAR,
    label : "Hello Button",
    tooltiptext : "Hello!",
    onViewShowing : function (aEvent) {
      // código de inicialización
    },
    onViewHiding : function (aEvent) {
      // código de limpieza
    }
  });

Las diferencias son el type (que es button por defecto), el viewId y los eventos onViewShowing y onViewHiding.

viewId es el ID de un <panelview> que debe existir en el DOM de la ventana del navegador. Esto es bastante sencillo de hacer en una extensión con reinicio donde puedes crear un overlay con el view y cualquier contenido que quieras en él. En el caso de una extensión restartless necesitarás un window listener e insertar el view y su contenido en el DOM para cada ventana. Para hacer las cosas de la manera más simple posible, decidí usar un iframe XUL, de manera que no tengo que construir todo dinámicamente.

let panel = doc.createElement("panelview");
let iframe = doc.createElement("iframe");
 
panel.setAttribute("id", "aus-view-panel");
iframe.setAttribute("id", "aus-view-iframe");
iframe.setAttribute("type", "content");
iframe.setAttribute("src", "chrome://aus-view/content/player.html");
 
panel.appendChild(iframe);
doc.getElementById("PanelUI-multiView").appendChild(panel);

Los eventos onViewShowing y onViewHiding deben ser usados para preparar el view antes de aparecer, y luego para limpiarlo una vez de desaparece.

Este demo detecta vínculos a archivos MP3 o OGG en páginas web, y luego usa <audio> para reproducirlos. El código en el evento onViewShowing analiza la página activa en busca de estos vínculos y pasa las rutas de los archivos de audio al reproductor dentro del iframe. ¿Debería ser sencillo, cierto?

Hay un pequeño problema. Cuando el view se muestra, el nodo del view se mueve desde donde está ubicado hacia otra parte del DOM. La extracción y reinserción del nodo reinicia los elementos dentro del panel. Encima de todo, ésto ocurre después que onViewShowing ha sido invocado. Así que manipular el iframe durante onViewShowing no va a funcionar como esperamos, dado que todos los cambios se van a revertir cuando el view se muestre, porque el iframe será recargado. Hay un par de maneras de resolver esto:

  1. En vez de depender en onViewShowing, el código en el iframe puede detectar cuando ha sido cargado y correr todo por cuenta propia. Sin embargo, eso requiere salirse del contexto del documento para obtener los vínculos de la página abierta, lo cual requiere bastante código adicional.
  2. Utilizar un timeout en el código de onViewShowing, para que corra un poco después y el iframe esté listo para ser modificado. Esta es una solución un poco fea, pero también es la más simple, así que fue la que utilicé dado que es solo un demo.

Con ese pequeño hack, la extensión funciona bien. Puede probarla en páginas del Web Archive, como ésta.

Vista

Complementos que usan el SDK

Desarrolladores que utilizan el SDK pueden utilizar este módulo, el cual encapsula la funcionalidad de CustomizableUI. Este artículo expande las novedades del SDK en Firefox 29.

Referencias

The following two tabs change content below.

jorgev

Add-ons Developer Relations Lead at Mozilla
Jorge trabaja para el equipo de complementos de Mozilla, y se dedica a Mozilla Hispano y Mozilla Costa Rica en su tiempo libre. Actualmente está encargado del blog de Mozilla Hispano Labs.