Working with windows in chrome code

  • Revision slug: Working_with_windows_in_chrome_code
  • Revision title: Working with windows in chrome code
  • Revision id: 65725
  • Created:
  • Creator: Nickolay
  • Is current revision? No
  • Comment

Revision Content

This article describes working with multiple windows in Mozilla chrome code (XUL applications and Extensions). It contains tips and example code on opening new windows, finding an already opened window, and passing data between different windows.

Opening windows

To open a new window, we usually use a window.open or window.openDialog DOM call, like this:

var win = window.open("chrome://myextension/content/about.xul", 
                      "aboutMyExtension", "chrome,centerscreen"); 

XXX briefly explain the params and open vs openDialog; also find details about the second param of window.open

If the window object is unavailable (for example, when opening a window from XPCOM component code), you might want to use nsIWindowWatcher interface. Its parameters are similar to window.open, in fact window.open implementation calls nsIWindowWatcher's methods.

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);

Window object

Note the win variable in the above section, which is assigned the return value of window.open. It can be used to access the opened window. The return value of window.open (and similar methods) is a Window object (usually ChromeWindow), of the same type that the window variable.

Technically speaking, it implements a number of interfaces, including nsIDOMJSWindow and nsIDOMWindowInternal, but it also contains the user-defined properties for global variables and functions of the window. So, for example, to access the DOM document corresponding to the window, you can use win.document.

Note however, that the open() call returns before the window is fully loaded, so some calls, like win.document.getElementById() will fail. To overcome this difficulcy, you can move the initialization code to an onload handler of the window being opened or pass a callback function, as described below.

You can get a Window object from a document using document.defaultView.

Content windows

When a XUL window contains widgets such as <browser> or iframe, the documents displayed in those widgets are, naturally, separate from the document of the chrome window itself. There is a Window object for each sub-document, although there are no physical windows for those documents.

To access those sub-documents and sub-windows, use containerElement.contentDocument and containerElement.contentWindow, where containerElement is a reference to the widget. If the <browser> or <iframe> was declared with type="content-primary", you can use the content shortcut property to accesss the Window object of that primary document, for example:

alert(content.document.title); // alerts the title of the document displayed in the content-primary widget

Finding already opened windows

The window mediator XPCOM component (nsIWindowMediator interface) provides information about opened windows. Two of its methods are often used to obtain information about currently open windows: getMostRecentWindow and getEnumerator. Please refer to the MozillaZine page about nsIWindowMediator for examples and notes on using nsIWindowMediator. === Example: Opening a window only if it's not opened already === XXX TBD

Passing data between windows

When working with multiple windows, you often need to pass information from one window to another. Since different windows have separate DOM documents and global objects for scripts, you can't just use one global JavaScript variable in scripts from different windows.

There are several techniques of varying power and simplicity that can be used to share data. We'll demonstrate them from the simplest to the most complex in the next few sections.

Example 1: Passing data to window when opening it with openDialog

When you open a window using window.openDialog or nsIWindowWatcher.openWindow, you can pass an arbitrary number of arguments to that window. Arguments are simple JavaScript objects, accessible through window.arguments property in the opened window.

In this example we're using window.openDialog to open a progress dialog and specify the current status text, the maximum progress value, and the current progress.

Opener code:

window.openDialog("chrome://test/content/progress.xul",
                  "myProgress", "chrome,centerscreen", 
                  {status: "Reading remote data", maxProgress: 50, progress: 10} );

progress.xul:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window onload="onLoad();" xmlns="http://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="(No status)"/>
<hbox>
  <progressmeter id="progressmeter" mode="determined"/>
  <button label="Cancel" oncommand="close();"/>
</hbox>

</window>

Example 2: Interacting with the opener

Sometimes an opened window needs to interact with its opener, for example to notify about changes user made in it. This can be done using the window.opener property or via a callback function passed to the window in a way described in the previous section.

We'll add a code to notify the opener when user presses Cancel to progress dialog from the previous example.

  • Using window.opener. The opener property returns the {{template.Anch("Window object")}} for the window that opened the current window. XXX window, window, window... someone please edit this

If we are sure that the window that opened the progress dialog declares the cancelOperation function, we can use window.opener.cancelOperation(); to notify it:

<button label="Cancel" oncommand="opener.cancelOperation(); close();"/>
  • Using a callback function. Alternatively, the opener window can pass the callback function to progress dialog in the same way we passed the status string in the example above:
function onCancel() {
  alert("Operation cancelled!");
}

...

window.openDialog("chrome://test/content/progress.xul",
                  "myProgress", "chrome,centerscreen", 
                  {status: "Reading remote data", maxProgress: 50, progress: 10},
                  onCancel); 

The progress dialog can then run the callback like this:

<button label="Cancel" oncommand="window.arguments[1](); close();"/>

Example 3: Using nsIWindowMediator when opener is not enough

window.opener property is very easy to use, but it's only useful when you are sure that your window is opened from few well-known places. In more complicated cases you need to use the nsIWindowMediator interface, introduced above.

An example when you might want to use nsIWindowMediator is the extension's Options window. Suppose you're developing a browser extension which consists of a browser.xul overlay and an Options window. Suppose the overlay contains a button to open extension's Options window, which needs to read some data from the browser window. As you may remember, the Firefox's Extension Manager can also be used to open your Options dialog.

This means the value of window.opener in your Options dialog is not necessarily the browser window - it may also be the Extension Manager window. You could check the location property of opener and use opener.opener in case it's the Extension Manager window, but a better way is to use nsIWindowMediator:

var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                   .getService(Components.interfaces.nsIWindowMediator);
var browserWindow = wm.getMostRecentWindow("navigator:browser");
// read values from |browserWindow|

You might be tempted to use a similar technique to apply the changes user made in the Options dialog, but a better way to do that is to use preferences observers.

Advanced data sharing

The above code is useful when you need pass data from one window to another or to a set of windows, but sometimes you just want to make a JavaScript variable shared between different windows. You could declare a variable in each window, and setter functions which will keep the "instances" of the variable in various windows in sync. But as usual there is a better way.

To declare a shared variable, we need to find a place which exists while the application is running and which is easily accessible from the code in different chrome windows. There are actually a few such places.

Using an XPCOM singleton component

The most clean and powerful way is to define your own XPCOM component (you can write one in JavaScript) and access it from any place using a getService call:

Components.classes["@domain.org/mycomponent;1"].getService();
  • Pros:
    • It's the "right way".
    • You can store arbitrary JavaScript objects in the component.
    • The scope is not shared between components, so you don't have to worry about name collisions.
  • Cons:
    • You can't use the window object, it's members, like alert and open, and many other objects available from inside a window. The functionality is not lost, however, - you just have to use the XPCOM components directly instead of using convenient shortcuts. Of course, this doesn't matter if you just store data in the component.
    • Learning to create XPCOM components takes time.

There are several articles and books about creating XPCOM components online.

Storing shared data in preferences

If you just need to store a string or a number, writing a whole XPCOM component may be an unnecessary complication. You can use the preferences service XXX link in such cases.

  • Pros:
    • Quite easy to use for storing simple data.
  • Cons:
    • Can't be used to store complex data easily.
    • Abusing preferences service and not cleaning up after yourself can cause <tt>prefs.js</tt> to grow large and slow down application startup.

See MozillaZine page about preferences for detailed description of the preferences system and example code.

Example:

var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                      .getService(Components.interfaces.nsIPrefService);
var branch = prefs.getBranch("extensions.myext.");
var var1 = branch.getBoolPref("var1"); // get a pref

The hidden window hack

Some extension authors use the special hidden window to store their data and code. The hidden window is similar to regular windows, but unlike any other window, it is available for the whole time the application is running, and it is not visible to user. It's used to implement some Mac-specific thing (please edit, if you know what it is), so it is not necessary on Windows. There is a bug to remove it from Windows builds.

  • Pros:
    • If you're running code in the hidden window, the window object and its properties are available, unlike the component case.
    • You can store arbitrary JavaScript objects in the hidden window.
  • Cons:
    • It's a hack.
    • The hidden window may be removed from Windows builds.

You can see this technique in action in rue's SessionSaver extension.

Revision Source

<p>This article describes working with multiple windows in Mozilla chrome code (<a href="en/XUL">XUL</a> applications and <a href="en/Extensions">Extensions</a>). It contains tips and example code on opening new windows, finding an already opened window, and passing data between different windows.
</p>
<h3 name="Opening_windows"> Opening windows </h3>
<p>To open a new window, we usually use a <code><a href="en/Window.open">window.open</a></code> or <code><a href="en/Window.openDialog">window.openDialog</a></code> DOM call, like this:
</p>
<pre class="eval">var win = window.open("chrome://myextension/content/about.xul", 
                      "aboutMyExtension", "chrome,centerscreen"); 
</pre>
<p><span class="comment">XXX briefly explain the params and open vs openDialog; also find details about the second param of window.open</span>
</p><p>If the <code>window</code> object is unavailable (for example, when opening a window from XPCOM component code), you might want to use <a href="en/NsIWindowWatcher">nsIWindowWatcher</a> interface. Its parameters are similar to <code>window.open</code>, in fact <code>window.open</code> implementation calls <code>nsIWindowWatcher</code>'s methods.
</p>
<pre class="eval">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);
</pre>
<h3 name="Window_object"> Window object </h3>
<p>Note the <code>win</code> variable in the above section, which is assigned the return value of <code>window.open</code>. It can be used to access the opened window. The return value of <code>window.open</code> (and similar methods) is a <code><a class="external" href="http://xulplanet.com/references/objref/Window.html">Window</a></code> object (usually <code><a class="external" href="http://xulplanet.com/references/objref/ChromeWindow.html">ChromeWindow</a></code>), of the same type that the <code>window</code> variable.
</p><p>Technically speaking, it implements a number of interfaces, including <code><a href="en/NsIDOMJSWindow">nsIDOMJSWindow</a></code> and <code><a href="en/NsIDOMWindowInternal">nsIDOMWindowInternal</a></code>, but it also contains the user-defined properties for global variables and functions of the window. So, for example, to access the DOM document corresponding to the window, you can use <code><a href="en/DOM/window.document">win.document</a></code>.
</p><p>Note however, that the <code>open()</code> call returns <i>before</i> the window is fully loaded, so some calls, like <code>win.document.getElementById()</code> will fail. To overcome this difficulcy, you can move the initialization code to an <code>onload</code> handler of the window being opened or pass a callback function, as described <a href="#callback">below</a>.
</p><p>You can get a <code>Window</code> object from a document using <code>document.defaultView</code>.
</p>
<h3 name="Content_windows"> Content windows </h3>
<p>When a XUL window contains widgets such as <code>&lt;browser&gt;</code> or <code>iframe</code>, the documents displayed in those widgets are, naturally, separate from the document of the chrome window itself. There is a <code>Window</code> object for each sub-document, although there are no physical windows for those documents.
</p><p>To access those sub-documents and sub-windows, use <code>containerElement.contentDocument</code> and <code>containerElement.contentWindow</code>, where <code>containerElement</code> is a reference to the widget. If the <code>&lt;browser&gt;</code> or <code>&lt;iframe&gt;</code> was declared with <code>type="content-primary"</code>, you can use the <code>content</code> shortcut property to accesss the <code>Window</code> object of that primary document, for example:
</p>
<pre class="eval">alert(content.document.title); // alerts the title of the document displayed in the content-primary widget
</pre>
<h3 name="Finding_already_opened_windows"> Finding already opened windows </h3>
<p>The window mediator XPCOM component (<a href="en/NsIWindowMediator">nsIWindowMediator</a> interface) provides information about opened windows. Two of its methods are often used to obtain information about currently open windows: <code>getMostRecentWindow</code> and <code>getEnumerator</code>. Please refer to the <a class="external" href="http://kb.mozillazine.org/nsIWindowMediator">MozillaZine page about nsIWindowMediator</a> for examples and notes on using <code>nsIWindowMediator</code>.
<span class="comment">=== Example: Opening a window only if it's not opened already === XXX TBD</span>
</p>
<h3 name="Passing_data_between_windows"> Passing data between windows </h3>
<p>When working with multiple windows, you often need to pass information from one window to another. Since different windows have separate DOM documents and global objects for scripts, you can't just use one global JavaScript variable in scripts from different windows.
</p><p>There are several techniques of varying power and simplicity that can be used to share data. We'll demonstrate them from the simplest to the most complex in the next few sections.
</p>
<h4 name="Example_1:_Passing_data_to_window_when_opening_it_with_openDialog"> Example 1: Passing data to window when opening it with <code>openDialog</code> </h4>
<p>When you open a window using <code><a href="en/Window.openDialog">window.openDialog</a></code> or <code>nsIWindowWatcher.openWindow</code>, you can pass an arbitrary number of <i>arguments</i> to that window. Arguments are simple JavaScript objects, accessible through <code><a href="en/Window.arguments">window.arguments</a></code> property in the opened window.
</p><p>In this example we're using <code>window.openDialog</code> to open a progress dialog and specify the current status text, the maximum progress value, and the current progress.
</p><p>Opener code:
</p>
<pre class="eval">window.openDialog("chrome://test/content/progress.xul",
                  "myProgress", "chrome,centerscreen", 
                  {status: "Reading remote data", maxProgress: 50, progress: 10} );
</pre>
<p><code>progress.xul</code>:
</p>
<pre>&lt;?xml version="1.0"?&gt;
&lt;?xml-stylesheet href="chrome://global/skin/" type="text/css"?&gt;

&lt;window onload="onLoad();" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;
&lt;script&gt;&lt;![CDATA[
  var gStatus, gProgressMeter;
  var maxProgress = 100;
  function onLoad() {
    gStatus = document.getElementById("status");
    gProgressMeter = document.getElementById("progressmeter");
  
    if("arguments" in window &amp;&amp; window.arguments.length &gt; 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 + "...";
  }
]]&gt;&lt;/script&gt;
 
&lt;label id="status" value="(No status)"/&gt;
&lt;hbox&gt;
  &lt;progressmeter id="progressmeter" mode="determined"/&gt;
  &lt;button label="Cancel" oncommand="close();"/&gt;
&lt;/hbox&gt;

&lt;/window&gt;
</pre>
<h4 name="Example_2:_Interacting_with_the_opener"> Example 2: Interacting with the opener </h4>
<p>Sometimes an opened window needs to interact with its opener, for example to notify about changes user made in it. This can be done using the <a href="en/Window.opener">window.opener</a> property or via a callback function passed to the window in a way described in the previous section.
</p><p>We'll add a code to notify the opener when user presses Cancel to progress dialog from the previous example.
</p>
<ul><li> <b>Using <code>window.opener</code>.</b> The <code>opener</code> property returns the {{template.Anch("Window object")}} for the window that opened the current window. <span class="comment">XXX window, window, window... someone please edit this</span>
</li></ul>
<p>If we are sure that the window that opened the progress dialog declares the <code>cancelOperation</code> function, we can use <code>window.opener.cancelOperation();</code> to notify it:
</p>
<pre class="eval">&lt;button label="Cancel" oncommand="<b>opener.cancelOperation();</b> close();"/&gt;
</pre>
<ul><li> <b id="callback"></b><b>Using a callback function.</b> Alternatively, the opener window can pass the callback function to progress dialog in the same way we passed the status string in the example above:
</li></ul>
<pre class="eval">function onCancel() {
  alert("Operation cancelled!");
}

...

window.openDialog("chrome://test/content/progress.xul",
                  "myProgress", "chrome,centerscreen", 
                  {status: "Reading remote data", maxProgress: 50, progress: 10},
                  <b>onCancel</b>); 
</pre>
<p>The progress dialog can then run the callback like this:
</p>
<pre class="eval">&lt;button label="Cancel" oncommand="<b>window.arguments[1]();</b> close();"/&gt;
</pre>
<h4 name="Example_3:_Using_nsIWindowMediator_when_opener_is_not_enough"> Example 3: Using <code>nsIWindowMediator</code> when <code>opener</code> is not enough </h4>
<p><code>window.opener</code> property is very easy to use, but it's only useful when you are sure that your window is opened from few well-known places. In more complicated cases you need to use the <code><a href="en/NsIWindowMediator">nsIWindowMediator</a></code> interface, introduced above.
</p><p>An example when you might want to use <code>nsIWindowMediator</code> is the extension's Options window. Suppose you're developing a browser extension which consists of a browser.xul overlay and an Options window. Suppose the overlay contains a button to open extension's Options window, which needs to read some data from the browser window. As you may remember, the Firefox's Extension Manager can also be used to open your Options dialog.
</p><p>This means the value of <code>window.opener</code> in your Options dialog is not necessarily the browser window - it may also be the Extension Manager window. You could check the <code>location</code> property of <code>opener</code> and use <code>opener.opener</code> in case it's the Extension Manager window, but a better way is to use <code>nsIWindowMediator</code>:
</p>
<pre class="eval">var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                   .getService(Components.interfaces.nsIWindowMediator);
var browserWindow = wm.getMostRecentWindow("navigator:browser");
// read values from |browserWindow|
</pre>
<p>You might be tempted to use a similar technique to apply the changes user made in the Options dialog, but a better way to do that is to use <a class="external" href="http://kb.mozillazine.org/Dev_:_Using_preferences#Using_preferences_observers">preferences observers</a>.
</p>
<h3 name="Advanced_data_sharing"> Advanced data sharing </h3>
<p>The above code is useful when you need pass data from one window to another or to a set of windows, but sometimes you just want to make a JavaScript variable shared between different windows. You could declare a variable in each window, and setter functions which will keep the "instances" of the variable in various windows in sync. But as usual there is a better way.
</p><p>To declare a shared variable, we need to find a place which exists while the application is running and which is easily accessible from the code in different chrome windows. There are actually a few such places.
</p>
<h4 name="Using_an_XPCOM_singleton_component"> Using an XPCOM singleton component </h4>
<p>The most clean and powerful way is to define your own XPCOM component (you can write one in JavaScript) and access it from any place using a <code>getService</code> call:
</p>
<pre class="eval">Components.classes["@domain.org/mycomponent;1"].getService();
</pre>
<ul><li> Pros:
<ul><li> It's the "right way".
</li><li> You can store arbitrary JavaScript objects in the component.
</li><li> The scope is not shared between components, so you don't have to worry about name collisions.
</li></ul>
</li><li> Cons:
<ul><li> You can't use the <code><a href="en/DOM/window">window</a></code> object, it's members, like <code>alert</code> and <code>open</code>, and many other objects available from inside a window. The functionality is not lost, however, - you just have to use the XPCOM components directly instead of using convenient shortcuts. Of course, this doesn't matter if you just store data in the component.
</li><li> Learning to create XPCOM components takes time.
</li></ul>
</li></ul>
<p>There are several articles and books about creating XPCOM components online.
</p>
<h4 name="Storing_shared_data_in_preferences"> Storing shared data in preferences </h4>
<p>If you just need to store a string or a number, writing a whole XPCOM component may be an unnecessary complication. You can use the preferences service <span class="comment">XXX link</span> in such cases.
</p>
<ul><li> Pros:
<ul><li> Quite easy to use for storing simple data.
</li></ul>
</li><li> Cons:
<ul><li> Can't be used to store complex data easily.
</li><li> Abusing preferences service and not cleaning up after yourself can cause <tt>prefs.js</tt> to grow large and slow down application startup.
</li></ul>
</li></ul>
<p>See <a class="external" href="http://kb.mozillazine.org/Dev_:_Using_preferences">MozillaZine page about preferences</a> for detailed description of the preferences system and example code.
</p><p>Example:
</p>
<pre class="eval">var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                      .getService(Components.interfaces.nsIPrefService);
var branch = prefs.getBranch("extensions.myext.");
var var1 = branch.getBoolPref("var1"); // get a pref
</pre>
<h4 name="The_hidden_window_hack"> The hidden window hack </h4>
<p>Some extension authors use the special <i>hidden window</i> to store their data and code. The hidden window is similar to regular windows, but unlike any other window, it is available for the whole time the application is running, and it is not visible to user. It's used to implement some Mac-specific thing (please edit, if you know what it is), so it is not necessary on Windows. There is a bug to remove it from Windows builds.
</p>
<ul><li> Pros:
<ul><li> If you're running code in the hidden window, the <code>window</code> object and its properties are available, unlike the component case.
</li><li> You can store arbitrary JavaScript objects in the hidden window.
</li></ul>
</li><li> Cons:
<ul><li> It's a hack.
</li><li> The hidden window may be removed from Windows builds.
</li></ul>
</li></ul>
<p>You can see this technique in action in rue's SessionSaver extension.
</p>
Revert to this revision