Aujourd’hui, j’ai cherché à injecter du code dans une page HTML depuis une extension Firefox. Ca permet de définir des objects dans le scope de la fenêtre, qui seront ensuite accessibles depuis le code javascript de la page. J’ai aussi cherché à communiquer dans l’autre sens : appeler des fonctions de mon extension depuis la page web.

Injection de script dans une page Web

L’injection se passe en deux étapes :

  1. Il faut savoir sur quelle page intervenir : on peut donc se binder sur l’évènement DOMContentLoaded du navigateur ;
  2. Il faut ensuite injecter le code à proprement parler, et qu’il soit visible.

Etape 1

Voici le code commenté (à inclure dans browser.xul) :

  1. var Injector =
  2. {
  3.   init: function()
  4.   {
  5.     var appcontent = document.getElementById('appcontent'); // On récupère le navigateur
  6.     if(appcontent) // On se bind sur le bon évènement
  7.       appcontent.addEventListener('DOMContentLoaded', Injector.onPageLoaded, true);
  8.   },
  9.  
  10.   onPageLoaded: function(aEvent)
  11.   {
  12.     if(aEvent.target instanceOf HTMLDocument) // Si on est sur un document HTML
  13.     {
  14.       var doc = aEvent.originalTarget; // Voici le HTMLDocument
  15.       var win = doc.defaultView; // Voici la DOMWindow
  16.     }
  17.   }
  18. }

2. Injection de code

Et voici comment injecter :

  1. /**
  2.  * Permet d'injecter du javascript dans une page
  3.  * @param win DOMWindow La fenêtre dans laquelle injecter le script
  4.  * @param id string Un id unique pour identifier le script
  5.  * @param src string Le code source
  6.  * @param [optional] boolean bRemove Vrai si on veut enlever le script de la page après son inclusion
  7. */
  8. Injector.addScript = function(win, id, src, bRemove)
  9. {
  10.   // On crée un élément script
  11.   var element = win.document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script');
  12.   element.setAttribute('type', 'text/javascript');
  13.   element.setAttribute('id', id);
  14.   element.innerHTML = src;
  15.  
  16.   // Et on l'ajoute !
  17.   win.document.documentElement.appendChild(element);
  18.   // On supprime l'élément si demandé
  19.   if(bRemove) element.parentNode.removeChild(element);
  20. }

Communiquer avec un script qui tourne dans le browser.xul

Communiquer dans l’autre sens a des intérêts : ça permet d’accéder à des fonctions plus avancée (accès à toutes les librairies XPCOM !).

La méthode est plutôt simple : on crée un élément dans le HTMLDocument (utile pour faire passer des données, en utilisant des attributs auxquels on aura ensuite accès), et on envoie alors un évènement personnalisé, sur lequel écoute notre Injector.

1. Ecoute des évènements de la page

On modifie le onPageLoad pour rajouter le bind sur les évènements, que l’on va ensuite traiter avec la méthode onEvent :

  1. [...]
  2. doc.addEventListener('injector-event', Injector.onEvent, false, true);
  3. [...]
  4.  
  5. Injector.onEvent = function(aEvent)
  6. {
  7.   // On peut récupérer l'élément sur lequel a été passé l'évènement
  8.   var element = aEvent.target;
  9.   // Et le document associé
  10.   var doc = element.ownerDocument;
  11.   // Faites ensuite ce que vous voulez. Vous pouvez utiliser la méthode element.getAttribute pour récupérer d'éventuels arguments, éventuellement encodés avec JSON.stringify puis décodés avec JSON.parse
  12. }

2. Envoi d’un évènement

Et voici le code à mettre dans votre page HTML :

  1. // Creation de l'évènement
  2. var ev = document.createEvent('Events');
  3. ev.initEvent('injector-event', true, false);
  4. // On utilise documentElement pour envoyer le message. Vous pouvez utiliser un élément personnalisé, n'importe où dans le code
  5. document.documentElement.dispatchEvent(ev);

Bibliographie

  • Le code source de Firebug pour l’injection de code.
  • Un post sur stackoverflow qui décrit la deuxième partie.