merge for backing out of changeset d1b2b899440d, the first of two commits for bug 598980, because it generates test failures per bug 604801 that are 0.9 release blockers
authorMyk Melez <myk@mozilla.org>
Mon, 18 Oct 2010 16:51:57 -0700
changeset 881 378391fa478355e74c654fc2f17b483c4c816f92
parent 879 e80ffb9aa4ca71120e9a86600a4eba6e527f2fbd (current diff)
parent 880 761c5181ec9149dc2fe1c06fbd1fe94b785f8fdf (diff)
child 882 d150cca77cfef60dee2f2fe7c892fd7fc5ce83c4
push id387
push usermyk@mozilla.com
push dateMon, 18 Oct 2010 23:56:05 +0000
bugs598980, 604801
merge for backing out of changeset d1b2b899440d, the first of two commits for bug 598980, because it generates test failures per bug 604801 that are 0.9 release blockers
packages/addon-kit/docs/windows.md
--- a/packages/addon-kit/docs/windows.md
+++ b/packages/addon-kit/docs/windows.md
@@ -106,36 +106,45 @@ optional property.
         // loading.
       }
     });
 
 Events of browserWindows
 ------------------------
 
 Events representing common actions and state changes for windows.
+
+These properties are `collections`. Listeners can be registered by
+passing the callback to the properties' `add` method, and can be removed
+by passing the callback function to the properties' `remove` method.
+
 Listeners are passed the `window` object that triggered the event.
 
-**open**  
+<api name="onOpen">
+@property {collection}
 Called when a new window is opened.
+</api>
 
-**close**  
+<api name="onClose">
+@property {collection}
 Called when a window is closed.
+</api>
 
 **Examples**
 
     var windows = require("windows").browserWindows;
 
     // listen for window openings via property assignment
-    windows.on('open', function(window) {
+    windows.onOpen.add(function(window) {
       myOpenWindows.push(window);
     });
 
     // modify the DOM of the page when ready,
     // by adding listener to the event collection.
-    windows.on('close', function(window) {
+    windows.onClose.add(function(window) {
       console.log("A window was closed.");
     });
 
 Window
 ------
 
 A `window` object represents a single open window. It contains the following
 window properties and methods:
@@ -173,17 +182,17 @@ A function to be called when the window 
     var windows = require("windows").browserWindows;
 
     //Print how many tabs the current window has
     console.log("The active window has " +
                 windows.activeWindow.tabs.length +
                 " tabs.");
 
     // Print the title of all browser windows
-    for each (var window in windows) {
+    for (var window in windows) {
       console.log(window.title);
     }
 
     // close the active window
     windows.activeWindow.close();
 
     // close the active window
     windows.activeWindow.close(function() {
--- a/packages/addon-kit/lib/windows.js
+++ b/packages/addon-kit/lib/windows.js
@@ -14,223 +14,259 @@
  * The Original Code is Jetpack.
  *
  * The Initial Developer of the Original Code is Mozilla.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Felipe Gomes <felipc@gmail.com> (Original author)
- *   Irakli Gozalishvili <gozala@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-"use strict";
 
 if (!require("xul-app").is("Firefox")) {
   throw new Error([
     "The windows module currently supports only Firefox. In the future",
     " we would like it to support other applications, however.  Please see ",
     "https://bugzilla.mozilla.org/show_bug.cgi?id=571449 for more information."
   ].join(""));
 }
 
-const { Cc, Ci } = require('chrome'),
-      { Trait } = require('traits'),
-      { List } = require('list'),
-      { EventEmitter } = require('events'),
-      { WindowTabs, WindowTabTracker } = require('windows/tabs'),
-      { WindowDom } = require('windows/dom'),
-      { WindowLoader } = require('windows/loader'),
-      { WindowTrackerTrait } = require('window-utils'),
-      // { Sidebars } = require('window/sidebars');
-      { utils } = require('xpcom'),
-      apiUtils = require('api-utils'),
-      unload = require('unload'),
+const tabBrowser = require("tab-browser");
+const TabModule = tabBrowser.TabModule;
+const windowUtils = require("window-utils");
+const collection = require("collection");
+const apiUtils = require("api-utils");
+const errors = require("errors");
+const {Cc,Ci} = require("chrome");
+
+const events = [
+  "onOpen",
+  "onClose"
+];
+
+/**
+ * BrowserWindow
+ *
+ * Safe object representing a browser window
+ */
+function BrowserWindow(element) {
+  if (!isBrowserWindow(element))
+    throw new Error("Element is not a browser window");
+
+  this.__defineGetter__("tabs", function() {
+    let tabs = new TabModule(element);
+    delete this.tabs;
+    this.__defineGetter__("tabs", function() tabs);
+    return tabs;
+  })
+
+  this.__defineGetter__("title", function() element.document.title);
+
+  this.close = function(callback) {
+    if (callback)
+      windowMap.addCloseCallback(element, callback);
+
+    element.close();
+  }
+};
+
+/**
+ * windows.browserWindows.__iterator__
+ * windows.browserWindows.length
+ * windows.browserWindows.activeWindow
+ * windows.browserWindows.openWindow
+ */
+exports.browserWindows = {
+  __iterator__: function() {
+    let winEnum = Cc["@mozilla.org/appshell/window-mediator;1"]
+               .getService(Ci.nsIWindowMediator)
+               .getEnumerator("navigator:browser");
+    while (winEnum.hasMoreElements())
+      yield windowMap.getWindowObject(winEnum.getNext());
+  },
 
-      WM = Cc['@mozilla.org/appshell/window-mediator;1'].
-        getService(Ci.nsIWindowMediator),
+  get length() {
+    let count = 0;
+    let enum = Cc["@mozilla.org/appshell/window-mediator;1"]
+               .getService(Ci.nsIWindowMediator)
+               .getEnumerator("navigator:browser");
+    while (enum.hasMoreElements()) {
+      count++;
+      enum.getNext();
+    }
+
+    return count;
+  },
+
+  get activeWindow() {
+    return windowMap.getWindowObject(windowUtils.activeWindow);
+  },
+  set activeWindow(safeWindow) {
+    let element = windowMap.getWindowElement(safeWindow);
+    if (element)
+      windowUtils.activeWindow = element;
+  },
+
+  openWindow: function(options) {
+    if (typeof options === "string")
+      options = { url: options };
 
-      BROWSER = 'navigator:browser';
+    options = apiUtils.validateOptions(options, {
+      url: {
+        is: ["undefined", "string"]
+      },
+      onOpen: {
+        is: ["undefined", "function"]
+      }
+    });
+
+    if (!options.url)
+      options.url = "about:blank";
+
+    let addTabOptions = {
+      inNewWindow: true
+    };
+
+    if (options.onOpen) {
+      addTabOptions.onLoad = function(e) {
+        let win = e.target.defaultView;
+        let safeWindowObj = windowMap.create(win);
+        let tabEl = win.gBrowser.tabContainer.childNodes[0];
+        let tabBrowser = win.gBrowser.getBrowserForTab(tabEl);
+        tabBrowser.addEventListener("load", function(e) {
+          tabBrowser.removeEventListener("load", arguments.callee, true);
+          errors.catchAndLog(function(e) options.onOpen(e))(safeWindowObj);
+        }, true);
+      };
+    }
+    tabBrowser.addTab(options.url.toString(), addTabOptions);
+  }
+};
+
 
 /**
- * Window trait composes safe wrappers for browser window that are I10S
- * compatible.
- */
-const BrowserWindowTrait = Trait.compose(
-  EventEmitter,
-  WindowDom.resolve({ close: '_close' }),
-  WindowTabs,
-  WindowTabTracker,
-  WindowLoader,
-  /* WindowSidebars, */
-  Trait.compose({
-    _emit: Trait.required,
-    _close: Trait.required,
-    /**
-     * Constructor returns wrapper of the specified chrome window.
-     * @param {nsIWindow} window
-     */
-    constructor: function BrowserWindow(options) {
-      // make sure we don't have unhandled errors
-      this.on('error', console.exception);
-
-      if ('onOpen' in options) this.on('open', options.onOpen);
-      if ('onClose' in options) this.on('close', options.onClose);
-      if ('onReady' in options) this.on('ready', options.onReady);
-      if ('params' in options) this._params = options.params;
-      if ('window' in options) this._window = options.window;
-      this._window; // need to invoke lazy getter.
-      return this;
-    },
-    _onLoad: function() {
-      try {
-        this._initWindowTabTracker();
-      } catch(e) {
-        this._emit('error', e)
-      }
-      this._emit('open', this._public);
-    },
-    _onReady: function() {
-      this._emit('ready', this._public);
-    },
-    _onUnload: function() {
-      this._emit('close', this._public);
-      // since window is closed we can destruct it
-      this._window = null;
-      this._removeAllListeners('close');
-      this._removeAllListeners('open');
-      this._removeAllListeners('ready');
-    },
-    close: function close(callback) {
-      // maybe we should deprecate this with message ?
-      if (callback) this.on('close', callback);
-      return this._close();
-    }
-  })
-);
-/**
- * Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for
- * window doesn't exists yet. If wrapper already exists then returns it
- * instead.
- * @params {Object} options
- *    Options that are passed to the the `BrowserWindowTrait`
- * @returns {BrowserWindow}
- * @see BrowserWindowTrait
- */
-function BrowserWindow(options) {
-  let chromeWindow = options.window;
-  for each (let window in windows) {
-    if (chromeWindow == window._window)
-      return window._public
-  }
-  let window = BrowserWindowTrait(options);
-  windows.push(window);
-  return window._public;
-}
-// to have proper `instanceof` behavior will go away when #596248 is fixed.
-BrowserWindow.prototype = BrowserWindowTrait.prototype;
-exports.BrowserWindow = BrowserWindow
-const windows = [];
-/**
- * `BrowserWindows` trait is composed out of `List` trait and it represents
- * "live" list of currently open browser windows. Instance mutates itself
- * whenever new browser window gets opened / closed.
+ * windows.browserWindows.onOpen, windows.browserWindows.onClose
  */
-// Very stupid to resolve all `toStrings` but this will be fixed by #596248
-const browserWindows = Trait.resolve({ toString: null }).compose(
-  List.resolve({ constructor: '_initList' }),
-  EventEmitter.resolve({ toString: null }),
-  WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }),
-  Trait.compose({
-    _emit: Trait.required,
-    _add: Trait.required,
-    _remove: Trait.required,
+events.forEach(function(e) {
+  // create a collection for each event
+  collection.addCollectionProperty(exports.browserWindows, e);
+});
+
+// Mapping between safeWindowObject <-> nsIDOMWindow element
+let windowMap = {
+  cache: [],
+  create: function(windowElement) {
+    let windowObject = this.getWindowObject(windowElement);
+    if (windowObject)
+      return windowObject;
+
+    try {
+      windowObject = new BrowserWindow(windowElement);
+    } catch (e if e.message == "Element is not a browser window") {
+      return null;
+    }
 
-    // public API
+    this.cache.push({
+      windowObject: windowObject,
+      windowElement: windowElement
+    });
+    return windowObject;
+  },
+  destroy: function(windowObject) {
+    for each (let entry in this.cache) {
+      if (entry.windowObject == windowObject) {
+        delete windowObject.tabs;
+        windowObject.__defineGetter__("tabs", function() []);
+        delete windowObject.title;
+        windowObject.__defineGetter__("title", function() null);
+
+        if (entry.closeCallback) {
+          errors.catchAndLog(function() {
+            entry.closeCallback.call(windowObject);
+          })();
+          entry.closeCallback = null;
+        }
 
-    /**
-     * Constructor creates instance of `Windows` that represents live list of open
-     * windows.
-     */
-    constructor: function BrowserWindows() {
-      this._trackedWindows = [];
-      this._initList();
-      this._initTracker();
-      unload.when(this._destructor.bind(this));
-    },
-    _destructor: function _destructor() {
-      for each (window in this)
-        window.close();
-      this._removeAllListeners('open');
-      this._removeAllListeners('close');
-    },
-    /**
-     * This property represents currently active window.
-     * Property is non-enumerable, in order to preserve array like enumeration.
-     * @type {Window|null}
-     */
-    get activeWindow() {
-      let window = WM.getMostRecentWindow(null);
-      return this._isBrowser(window) ? BrowserWindow({ window: window }) : null;
-    },
-    set activeWindow(window) {
-      if (window instanceof BrowserWindow)
-        window.focus()
-    },
-    openWindow: function(options) {
-      if (typeof options === "string")
-        options = { url: options };
-
-      let url = apiUtils.validateOptions(options, {
-        url: { is: ["undefined", "string"] }
-      }).url || "about:blank";
+        let index = this.cache.indexOf(entry);
+        this.cache.splice(index, 1);
+        return;
+      }
+    }
+  },
+  getWindowObject: function(windowElement) {
+    for each (let entry in this.cache)
+      if (windowElement == entry.windowElement)
+        return entry.windowObject;
+    
+    return null;
+  },
+  getWindowElement: function(windowObject) {
+    for each (let entry in this.cache)
+      if (windowObject == entry.windowObject)
+        return entry.windowElement;
+    
+    return null;
+  },
+  addCloseCallback: function(windowElement, callback) {
+    for each (let entry in this.cache) {
+      if (windowElement == entry.windowElement) {
+        entry.closeCallback = callback;
+        break;
+      }
+    }
+  }
+}
 
-      return BrowserWindow(Object.create(options, {
-        params: { value: [ url ] }
-      }));
-    },
-    /**
-     * Returns true if specified window is a browser window.
-     * @param {nsIWindow} window
-     * @returns {Boolean}
-     */
-    _isBrowser: function _isBrowser(window)
-      BROWSER === window.document.documentElement.getAttribute("windowtype")
-    ,
-     /**
-      * Internal listener which is called whenever new window gets open.
-      * Creates wrapper and adds to this list.
-      * @param {nsIWindow} chromeWindow
-      */
-    _onTrack: function _onTrack(chromeWindow) {
-      if (!this._isBrowser(chromeWindow)) return;
-      let window = BrowserWindow({ window: chromeWindow });
-      this._add(window);
-      this._emit('open', window);
-    },
-    /**
-     * Internal listener which is called whenever window gets closed.
-     * Cleans up references and removes wrapper from this list.
-     * @param {nsIWindow} window
-     */
-    _onUntrack: function _onUntrack(chromeWindow) {
-      if (!this._isBrowser(chromeWindow)) return;
-      let window = BrowserWindow({ window: chromeWindow });
-      this._remove(window);
-      windows.splice(windows.indexOf(window), 1);
-      this._emit('close', window);
+// Listen for window notifications, calls the onOpen/onClose handlers
+// and update map of window objects
+let windowDelegate = {
+  onTrack: function(window) {
+    let safeWindowObj = windowMap.create(window);
+    if (!safeWindowObj)
+      return;
+    for (let callback in exports.browserWindows.onOpen) {
+      errors.catchAndLog(function(safeWindowObj) {
+        callback.call(exports.browserWindows, safeWindowObj);
+      })(safeWindowObj);
+    }
+  },
+  onUntrack: function(window) {
+    let safeWindowObj = windowMap.getWindowObject(window);
+    for (let callback in exports.browserWindows.onClose) {
+      errors.catchAndLog(function(safeWindowObj) {
+        callback.call(exports.browserWindows, safeWindowObj);
+      })(safeWindowObj);
     }
-  }).resolve({ toString: null })
-)();
-exports.browserWindows = browserWindows;
+
+    windowMap.destroy(safeWindowObj);
+  }
+};
+let windowTracker = new windowUtils.WindowTracker(windowDelegate);
+
 
+function isBrowserWindow(window) {
+  try {
+    return window.document.documentElement
+           .getAttribute("windowtype") == "navigator:browser";
+  } catch (e) { }
+  return false;
+}
+
+function unload() {
+  windowMap.cache = null;
+  windowMap = null;
+  // Unregister tabs event listeners
+  events.forEach(function(e) exports.browserWindows[e] = []);
+}
+require("unload").ensure(this);
--- a/packages/addon-kit/tests/test-windows.js
+++ b/packages/addon-kit/tests/test-windows.js
@@ -14,17 +14,16 @@
  * The Original Code is Jetpack.
  *
  * The Initial Developer of the Original Code is Mozilla.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Felipe Gomes <felipc@gmail.com> (Original author)
- *   Irakli Gozalishvili <gozala@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -40,30 +39,31 @@ const {Cc, Ci} = require("chrome");
 exports.testOpenAndCloseWindow = function(test) {
   test.waitUntilDone();
   let windows = require("windows").browserWindows;
 
   test.assertEqual(windows.length, 1, "Only one window open");
 
   windows.openWindow({
     url: "data:text/html,<title>windows API test</title>",
-    onReady: function(window) {
+    onOpen: function(window) {
       test.pass("onOpen callback called");
       test.assert(window.title.indexOf("windows API test") != -1, "URL correctly loaded");
       test.assertEqual(window.tabs.length, 1, "Only one tab open");
       test.assertEqual(windows.length, 2, "Two windows open");
-      window.close();
-    },
-    onClose: function(window) {
-      test.assertEqual(window.tabs.length, 0, "Tabs were cleared");
-      test.assertEqual(windows.length, 1, "Only one window open");
-      test.done();
+
+      window.close(function() {
+        test.assertEqual(this.tabs.length, 0, "Tabs were cleared");
+        test.assertEqual(this.title, null, "Window title was cleared");
+        test.assertEqual(windows.length, 1, "Only one window open");
+        test.done();
+      });
     }
   });
-};
+}
 
 exports.testOnOpenOnCloseListeners = function(test) {
   test.waitUntilDone();
   let windows = require("windows").browserWindows;
 
   test.assertEqual(windows.length, 1, "Only one window open");
 
   let received = {
@@ -92,50 +92,50 @@ exports.testOnOpenOnCloseListeners = fun
   }
 
   function listener4() {
     if (received.listener4)
       test.fail("Event received twice");
     received.listener4 = true;
   }
 
-  windows.on('open', listener1);
-  windows.on('open', listener2);
-  windows.on('close', listener3);
-  windows.on('close', listener4);
+  windows.onOpen.add(listener1);
+  windows.onOpen.add(listener2);
+  windows.onClose.add(listener3);
+  windows.onClose.add(listener4);
 
   function verify() {
     test.assert(received.listener1, "onOpen handler called");
     test.assert(received.listener2, "onOpen handler called");
     test.assert(received.listener3, "onClose handler called");
     test.assert(received.listener4, "onClose handler called");
 
-    windows.removeListener('open', listener1);
-    windows.removeListener('open', listener2);
-    windows.removeListener('close', listener3);
-    windows.removeListener('close', listener4);
+    windows.onOpen.remove(listener1);
+    windows.onOpen.remove(listener2);
+    windows.onClose.remove(listener3);
+    windows.onClose.remove(listener4);
     test.done();
   }
 
 
   windows.openWindow({
     url: "data:text/html,foo",
     onOpen: function(window) {
       window.close(verify);
     }
   });
-};
+}
 
 exports.testWindowTabsObject = function(test) {
   test.waitUntilDone();
   let windows = require("windows").browserWindows;
 
   windows.openWindow({
     url: "data:text/html,<title>tab 1</title>",
-    onReady: function(window) {
+    onOpen: function(window) {
       test.assertEqual(window.tabs.length, 1, "Only 1 tab open");
       for (let tab in window.tabs)
         test.assertEqual(tab.title, "tab 1", "Correct tab listing");
 
       window.tabs.open({
         url: "data:text/html,<title>tab 2</title>",
         inBackground: true,
         onOpen: function(newTab) {
@@ -152,17 +152,17 @@ exports.testWindowTabsObject = function(
           window.close(function() {
             test.assertEqual(window.tabs.length, 0, "No more tabs on closed window");
             test.done();
           });
         }
       });
     }
   });
-};
+}
 
 exports.testActiveWindow = function(test) {
   const xulApp = require("xul-app");
   if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) {
     test.pass("This test is disabled on 3.6. For more information, see bug 598525");
     return;
   }
 
@@ -221,23 +221,23 @@ exports.testActiveWindow = function(test
       test.assertEqual(windows.activeWindow.title, window3.title, "Correct active window - 3");
       windows.activeWindow = nonBrowserWindow;
       finishTest();
     }
   ];
 
   windows.openWindow({
     url: "data:text/html,<title>window 2</title>",
-    onReady: function(window) {
+    onOpen: function(window) {
       window2 = window;
       rawWindow2 = wm.getMostRecentWindow("navigator:browser");
 
       windows.openWindow({
         url: "data:text/html,<title>window 3</title>",
-        onReady: function(window) {
+        onOpen: function(window) {
           window3 = window;
           rawWindow3 = wm.getMostRecentWindow("navigator:browser");
           nextStep();
         }
       });
     }
   });
 
@@ -277,17 +277,18 @@ exports.testActiveWindow = function(test
 
   function finishTest() {
     window3.close(function() {
       window2.close(function() {
         test.done();
       });
     });
   }
-};
+
+}
 
 // If the module doesn't support the app we're being run in, require() will
 // throw.  In that case, remove all tests above from exports, and add one dummy
 // test that passes.
 try {
   require("windows");
 }
 catch (err) {
--- a/packages/jetpack-core/lib/window-utils.js
+++ b/packages/jetpack-core/lib/window-utils.js
@@ -36,19 +36,16 @@
 
 const {Cc,Ci} = require("chrome");
 
 var errors = require("errors");
 
 var gWindowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
                      .getService(Ci.nsIWindowWatcher);
 
-const { EventEmitter } = require('events'),
-      { Trait } = require('traits');
-
 /**
  * An iterator for XUL windows currently in the application.
  * 
  * @return A generator that yields XUL windows exposing the
  *         nsIDOMWindow interface.
  */
 var windowIterator = exports.windowIterator = function windowIterator() {
   let winEnum = gWindowWatcher.getWindowEnumerator();
@@ -115,28 +112,16 @@ WindowTracker.prototype = {
       this._regWindow(window);
     else
       this._unregWindow(window);
   }
 };
 
 errors.catchAndLogProps(WindowTracker.prototype, ["handleEvent", "observe"]);
 
-const WindowTrackerTrait = Trait.compose({
-  _onTrack: Trait.required,
-  _onUntrack: Trait.required,
-  constructor: function WindowTrackerTrait() {
-    new WindowTracker({
-      onTrack: this._onTrack.bind(this),
-      onUntrack: this._onUntrack.bind(this)
-    });
-  }
-});
-exports.WindowTrackerTrait = WindowTrackerTrait;
-
 var gDocsToClose = [];
 
 function onDocUnload(event) {
   var index = gDocsToClose.indexOf(event.target);
   if (index == -1)
     throw new Error("internal error: unloading document not found");
   var document = gDocsToClose.splice(index, 1)[0];
   // Just in case, let's remove the event listener too.