Cet article décrit la manière de travailler avec des fenêtres multiples dans le code chrome de Mozilla (applications XUL et extensions). Il donne des astuces et des exemples de code sur l'ouverture de nouvelles fenêtres, la recherche de fenêtres déjà ouvertes, et la transmission de données entre différentes fenêtres.
Ouverture de fenêtres
Pour ouvrir une nouvelle fenêtre, on utilise habituellement un appel DOM window.open
ou window.openDialog
, comme ceci :
var win = window.open("chrome://myextension/content/about.xul", "aproposDeMonExtension", "chrome,centerscreen");
Le premier paramètre de window.open
est l'URI du fichier XUL décrivant la fenêtre et son contenu.
Le second paramètre est le nom de la fenêtre ; il peut être utilisé par des liens ou formulaires dans leur attribut target
. Il est différent du titre de la fenêtre tel que vu par l'utilisateur, qui est spécifié en XUL.
Le troisième paramètre, facultatif, est une liste de fonctionnalités spéciales que la fenêtre doit avoir.
La fonction window.openDialog
fonctionne de manière similaire, mais permet de spécifier des paramètres optionnels qui peuvent être référencés depuis le code JavaScript. Elle gère également les fonctionnalités de la fenêtre légèrement différemment, supposant notamment toujours que la fonctionnalité dialog
est spécifiée.
Si l'objet window
est indisponible (par exemple, lors de l'ouverture d'une fenêtre depuis le code d'un composant XPCOM), vous pouvez avoir besoin de l'interface nsIWindowWatcher. Ses paramètres sont similaires à ceux de window.open
, en fait l'implémentation de window.open
appelle les méthodes de nsIWindowWatcher
.
var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] .getService(Components.interfaces.nsIWindowWatcher); var win = ww.openWindow(null, "chrome://myextension/content/about.xul", "aboutMyExtension", "chrome,centerscreen", null);
L'objet window
Notez la variable win
dans la section ci-dessus, à laquelle est assignée la valeur de retour de window.open
. Elle peut être utilisée pour accéder à la fenêtre ouverte. La valeur renvoyée par window.open
(et les méthodes similaires) est un objet Window
(habituellement ChromeWindow
), du même type que la variable window
.
Du point de vue technique, elle implémente un certain nombre d'interfaces, dont nsIDOMJSWindow
et nsIDOMWindowInternal
, mais elle contient également les propriétés définies par l'utilisateur pour les variables globales et fonctions de la fenêtre. Donc, par exemple, pour accéder au document DOM correspondant à la fenêtre, vous pouvez utiliser win.document
.
Notez cependant que le retour de l'appel à open()
se fait avant que la fenêtre ne soit totalement chargée, donc certains appels comme win.document.getElementById()
ne fonctionneront pas. Pour contourner cette difficulté, vous pouvez déplacer le code d'initialisation vers un gestionnaire load
de la fenêtre ouverte, ou passer une fonction de callback comme décrit ci-dessous.
Vous pouvez obtenir un objet Window
depuis un document à l'aide de document.defaultView
.
Fenêtres de contenu
Lorsqu'une fenêtre XUL contient un élément d'interface capable d'afficher une page, comme <browser>
ou <iframe>
, le document à l'intérieur de celui-ci est, naturellement, séparé du document de la fenêtre chrome elle-même. Il y a également un objet Window
pour chaque sous-document, bien qu'il n'y ait pas de fenêtre dans le sens commun pour le sous-document.
C'est également valable pour les fenêtres chrome ouvertes dans un onglet ou <tt><tabbrowser></tt>. Les éléments au dessus du document chrome ouvert dans l'onglet sont séparés de votre document chrome.
Les deux sous-sections qui suivent décrivent la manière de passer les frontières du contenu chrome dans les deux sens, c'est-à-dire d'accéder à des éléments qui sont les ancêtres de votre document chrome, ou des éléments qui sont descendants de votre document chrome, mais néanmoins dans un contexte différent.
Accès aux documents contenus
Supposons que vous avez un document chargé dans un élément <tt><tabbrowser></tt>, <tt><browser></tt> ou <tt><iframe></tt> dans votre document.
Vous pouvez utiliser browser.contentDocument
pour accéder à ce document et browser.contentWindow
pour accéder à l'objet Window
de ce document.
Il est nécessaire de prendre en compte la fonctionnalité XPCNativeWrapper si l'on n'opère pas dans du contenu de confiance.
Dans le cas de <browser type="content-primary"/>
, vous pouvez utiliser la propriété raccourcie content pour accéder à l'objet Window
du document contenu. Par exemple :
// affiche le titre du document affiché dans l'élément de contenu principal alert(content.document.title);
Par exemple, vous pouvez utiliser content.document
dans un overlay à browser.xul pour accéder à la page Web dans l'onglet sélectionné.
_content
au lieu de content
. La première forme est déconseillée depuis longtemps, et vous devriez utiliser content
dans tout nouveau code.Accès à un document dans un panneau latéral
Firefox permet d'accéder à un panneau latéral, qui est implémenté comme un élément <browser>
avec id="sidebar"
. Pour accéder aux éléments et variables à l'intérieur du panneau, il est nécessaire d'utilier document.getElementById("sidebar").contentDocument
ou .contentWindow
, comme pour l'Accès aux documents contenus.
Accès aux éléments du document de la fenêtre principale depuis une fenêtre fille
Le cas opposé est l'accès au document chrome depuis un script privilégié chargé dans un <tt><browser></tt> ou un <tt><iframe></tt>.
Un cas typique où cela peut s'avérer utile est lorsque du code exécuté dans un panneau latéral de la fenêtre principale de Firefox doit pouvoir accéder aux éléments de cette fenêtre.
L'arbre DOM, tel qu'il apparaît dans l'Inspecteur DOM, peut ressembler à ceci :
#document window main-window ... browser #document window myExtensionWindow
où la fenêtre fille est celle où se trouve votre code.
Le but est d'accéder aux éléments situés au dessus du document chrome, c'est-à-dire de sortir de la fenêtre chrome et d'accéder à ses ancêtres. Cela peut se faire à l'aide de l'instruction suivante :
var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIWebNavigation) .QueryInterface(Components.interfaces.nsIDocShellTreeItem) .rootTreeItem .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindow)
Ceci permet de passer les limites du contenu chrome, et renvoie l'objet de la fenêtre principale.
Recherche de fenêtres déjà ouvertes
Le composant XPCOM window mediator (interface nsIWindowMediator) fournit des informations à propos des fenêtres existantes. Deux de ses méthodes sont souvent utilisées pour obtenir des informations à propos des fenêtres actuellement ouvertes : getMostRecentWindow
et getEnumerator
. Consultez la page nsIWindowMediator pour plus d'informations sur nsIWindowMediator
.
=== Example: Opening a window only if it's not opened already === XXX TBD
Transfert de données entre fenêtres
Lorsque l'on travaille avec plusieurs fenêtres, il est souvent nécessaire de faire passer des informations d'une fenêtre à une autre. Comme des fenêtres séparées ont des documents DOM et objets globaux différents pour les scripts, il n'est pas possible d'utiliser une seule variable globale JavaScript dans des scripts de fenêtres différentes.
Plusieurs techniques existent, de puissance et de simplicité variables, pour partager des données. Nous les présenterons de la plus simple à la plus complexe dans les quelques sections suivantes.
Exemple 1 : Passage de données à une fenêtre à son ouverture avec openDialog
Lorsque vous ouvrez une fenêtre à l'aide de window.openDialog
ou nsIWindowWatcher.openWindow
, vous pouvez fournir un nombre d'arguments arbitraires à cette fenêtre. Les arguments sont des objets JavaScript simples, accessibles au travers de la propriété window.arguments
dans la fenêtre ouverte.
Dans cet exemple, on utilise window.openDialog
pour ouvrir un dialogue de progression. On lui passe le texte d'état ainsi que les valeurs maximale et courante de la barre de progression. (Notez qu'utiliser nsIWindowWatcher.openWindow
est un peu moins trivial .)
window.openDialog("chrome://test/content/progress.xul", "myProgress", "chrome,centerscreen", {status: "Lecture des données distantes", maxProgress: 50, progress: 10} );
progress.xul
:
<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window onload="onLoad();" xmlns="https://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script><![CDATA[ var gStatus, gProgressMeter; var maxProgress = 100; function onLoad() { gStatus = document.getElementById("status"); gProgressMeter = document.getElementById("progressmeter"); if("arguments" in window && window.arguments.length > 0) { maxProgress = window.arguments[0].maxProgress; setProgress(window.arguments[0].progress); setStatus(window.arguments[0].status); } } function setProgress(value) { gProgressMeter.value = 100 * value / maxProgress; } function setStatus(text) { gStatus.value = "Status: " + text + "..."; } ]]></script> <label id="status" value="(Aucun état)"/> <hbox> <progressmeter id="progressmeter" mode="determined"/> <button label="Annuler" oncommand="close();"/> </hbox> </window>
Exemple 2 : Interaction avec la fenêtre ouvrante
Il arrive qu'une fenêtre ouverte doive interagir avec celle qui a déclenché son ouverture (opener). Par exemple, cela peut avoir pour but de lui indiquer que l'utilisateur a effectué des changements dans la fenêtre. On peut trouver la fenêtre qui en a ouvert une autre à l'aide de sa propriété window.opener ou via une fonction de callback passée à la fenêtre, de la façon décrite dans la section précédente.
Ajoutons un peu de code à l'exemple précédent pour avertir la fenêtre ouvrante lorsque l'utilisateur appuie sur Annuler dans la fenêtre de progression.
- Avec
window.opener
. La propriétéopener
renvoie l'objet Window pour la fenêtre qui a ouvert la fenêtre courante. Si nous sommes certains que la fenêtre qui a ouvert le dialogue de progression a déclaré la fonctioncancelOperation
, nous pouvons utiliserwindow.opener.cancelOperation();
pour lui envoyer une notification :
<button label="Annuler" oncommand="opener.cancelOperation(); close();"/>
- Avec une fonction de callback. Une autre manière de faire est de passer une fonction de callback depuis la fenêtre ouvrante vers le dialogue de progression de la même manière que nous avons passé le message d'état dans l'exemple précédent :
function onCancel() { alert("Opération annulée."); } ... window.openDialog("chrome://test/content/progress.xul", "myProgress", "chrome,centerscreen", {status: "Lecture des données distantes", maxProgress: 50, progress: 10}, onCancel);
Le dialogue de progression peut alors exécuter le callback de cette façon :
<button label="Cancel" oncommand="window.arguments[1](); close();"/>
Exemple 3 : Utilisation de nsIWindowMediator
lorsque opener
ne suffit pas
La propriété window.opener
est très facile à utiliser, mais elle n'est utile que lorsque vous pouvez vous assurer que la fenêtre a été ouverte depuis un endroit connu. Dans des cas plus compliqués, vous devez utiliser l'interface nsIWindowMediator
dont il est fait état plus haut.
Une situation dans laquelle nsIWindowMediator
peut être utilisé est dans la fenêtre d'options d'une extension. Supposons que vous développez une extension au navigateur qui consiste en un overlay sur browser.xul
et une fenêtre d'options. Supposons que l'overlay contient un bouton pour ouvrir la fenêtre d'options de l'extension, qui doit lire certaines données depuis la fenêtre de navigation. Comme vous vous en souvenez peut-être, le gestionnaire d'extensions de Firefox peut également être utilisé pour ouvrir votre fenêtre d'options.
Cela signifie que la valeur de window.opener
dans votre dialogue d'options n'est pas forcément la fenêtre de navigation, il peut s'agir à la place de la fenêtre du gestionnaire d'extensions. Vous pourriez vérifier la propriété location
de l'opener
et utiliser opener.opener
dans le cas de la fenêtre du gestionnaire d'extensions, mais une meilleure manière de faire est d'utiliser nsIWindowMediator
:
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); var browserWindow = wm.getMostRecentWindow("navigator:browser"); // lecture de valeurs depuis |browserWindow|
Il peut être tentant d'utiliser une technique similaire pour appliquer les changements effectués par l'utilisateur dans la fenêtre d'options, mais une meilleur manière de le faire est d'utiliser les observateurs de préférences.
Partage de données avancé
Le code ci-dessus est utile lorsque vous avez besoin de passer des données d'une fenêtre à une autre ou vers un ensemble de fenêtres, mais dans certains cas vous voulez simplement partager une variable JavaScript commune à différentes fenêtres. Vous pourriez déclarer une variable locale dans chaque fenêtre ainsi que les fonctions nécessaires pour garder les « instances » de la variable synchronisées entre les différentes fenêtres, mais par chance il existe une meilleure solution.
Pour déclarer une variable partagé, il est nécessaire de trouver un endroit qui existe tout au long de l'exécution de l'application et qui est facilement accessible depuis le code dans différentes fenêtres du chrome. En fait, quelques endroits comme celui-ci existent.
Utilisation d'un composant singleton XPCOM
La manière la plus propre et la plus puissante est de définir votre propre composant XPCOM (vous pouvez en écrire un en JavaScript) et y accéder depuis n'importe quel endroit à l'aide d'un appel à getService
:
Components.classes["@domain.org/mycomponent;1"].getService();
- Avantages :
- C'est la « manière propre. »
- On peut stocker des objets JavaScripts arbitraires dans le composant.
- La visibilité n'est pas partagée entre composants, donc pas d'inquiétude à avoir sur des collisions de noms.
- Inconvénients :
- Vous ne pouvez pas utiliser l'objet
window
, ses membres, commealert
etopen
, ainsi que beaucoup d'objets disponibles à l'intérieur d'une fenêtre. La fonctionnalité n'est pas perdue, cependant, vous devrez juste utiliser les composants XPCOM directement au lieu d'utiliser des raccourcis pratiques. Bien sûr, cela n'a pas d'importance si vous ne faites que stocker des données dans le composant. - Apprendre à créer des composants XPCOM prend du temps.
- Vous ne pouvez pas utiliser l'objet
Plusieurs articles et livres sur la création de composants XPCOM existent en ligne.
Stockage de données partagées dans les préférences
Si vous avez simplement besoin de stocker une chaîne ou un nombre, l'écriture d'un composant XPCOM complet peut être une complication inutile. Vous pouvez utiliser le service de préférences dans de tels cas.
- Avantages :
- Il est assez aisé de stocker des données simples.
- Inconvénients :
- Ne peut pas être utilisé pour stocker des données complexes.
- L'abus du service de préférences sans nettoyer ses traces après utilisation peut causer un accroissement important de la taille du fichier <tt>prefs.js</tt> et ralentir le démarrage de l'application.
Consultez Extraits de code:Préférences pour une description détaillée du système de préférences et des exemples de code.
Exemple :
var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); var branch = prefs.getBranch("extensions.myext."); var var1 = branch.getBoolPref("var1"); // obtient une préférence
L'astuce de la fenêtre cachée
Certains auteurs d'extensions utilisent la fenêtre cachée spéciale (hidden window) pour stocker leurs données et leur code. La fenêtre cachée est similaire à une fenêtre normale, mais contrairement à celles-ci elle est disponible à tout moment de la vie de l'application et n'est pas visible pour l'utilisateur. Le document chargé dans cette fenêtre est chrome://browser/content/hiddenWindow.xul
sous Mac OS X où il est utilisé pour implémenter les menus et resource://gre/res/hiddenWindow.html
pour les autres systèmes d'exploitation. À terme, cette fenêtre sera retirée des systèmes d'exploitation où elle n'est pas nécessaire (bug 71895).
Une référence à la fenêtre cachée peut être obtenue depuis l'interface nsIAppShellService
. Comme tout objet DOM, elle vous permet de définir des propriétés personnalisées :
var hiddenWindow = Components.classes["@mozilla.org/appshell/appShellService;1"] .getService(Components.interfaces.nsIAppShellService) .hiddenDOMWindow; hiddenWindow.myExtensionStatus = "ready";
Cependant, les objets placés dans la fenêtre cachée appartiendront toujours à la fenêtre qui les a créés. Si une méthode d'un tel objet accède à des propriétés de l'objet window
comme XMLHttpRequest
, vous pouvez être confronté à un message d'erreur parce que la fenêtre originale a été fermée. Pour éviter cela, vous pouvez charger les objets avec un fichier script dans la fenêtre cachée:
var hiddenWindow = Components.classes["@mozilla.org/appshell/appShellService;1"] .getService(Components.interfaces.nsIAppShellService) .hiddenDOMWindow; hiddenWnd.Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader) .loadSubScript("chrome://my-extension/content/globalObject.js"); hiddenWnd.myExtensionObject.doSomething();
Où globalObject.js
contient quelque chose comme :
var myExtensionObject = { doSomething: function() { return new XMLHttpRequest(); } }
- Avantages :
- Si vous exécutez du code dans la fenêtre cachée, l'objet
window
et ses propriétés sont disponibles, contrairement au cas du composant. - Vous pouvez stocker des objets JavaScript arbitraires dans la fenêtre cachée.
- Si vous exécutez du code dans la fenêtre cachée, l'objet
- Inconvénients :
- C'est du bidouillage.
- La même fenêtre est utilisée par différentes extensions, il faut utiliser de longs noms de variables pour éviter les conflits.
- La fenêtre cachée peut disparaître des versions Windows et Linux.
Voir aussi
Extraits de code:Dialogues et invites