Bug 987492 - CustomizableUI.jsm should provide convenience APIs around windows, r=gijs,mconley, a=sledru.
authorBlair McBride <bmcbride@mozilla.com>
Thu, 10 Apr 2014 10:41:33 -0400
changeset 183694 9c70e4856b3f
parent 183693 1244d500650c
child 183695 2948b8b5d51d
push id3450
push usermconley@mozilla.com
push date2014-04-10 14:45 +0000
treeherdermozilla-beta@f5622633b23f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgijs, mconley, sledru
bugs987492
milestone29.0
Bug 987492 - CustomizableUI.jsm should provide convenience APIs around windows, r=gijs,mconley, a=sledru.
browser/components/customizableui/src/CustomizableUI.jsm
browser/components/customizableui/test/browser.ini
browser/components/customizableui/test/browser_987492_window_api.js
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -805,16 +805,18 @@ let CustomizableUIInternal = {
   },
 
   registerBuildWindow: function(aWindow) {
     if (!gBuildWindows.has(aWindow)) {
       gBuildWindows.set(aWindow, new Set());
 
       aWindow.addEventListener("unload", this);
       aWindow.addEventListener("command", this, true);
+
+      this.notifyListeners("onWindowOpened", aWindow);
     }
   },
 
   unregisterBuildWindow: function(aWindow) {
     aWindow.removeEventListener("unload", this);
     aWindow.removeEventListener("command", this, true);
     gPanelsForWindow.delete(aWindow);
     gBuildWindows.delete(aWindow);
@@ -845,16 +847,18 @@ let CustomizableUIInternal = {
         if (areaNode.ownerDocument == document) {
           toDelete.push(areaNode);
         }
       }
       for (let areaNode of toDelete) {
         areaMap.delete(toDelete);
       }
     }
+
+    this.notifyListeners("onWindowClosed", aWindow);
   },
 
   setLocationAttributes: function(aNode, aArea) {
     let props = gAreas.get(aArea);
     if (!props) {
       throw new Error("Expected area " + aArea + " to have a properties Map " +
                       "associated with it.");
     }
@@ -2473,16 +2477,28 @@ this.CustomizableUI = {
    */
   get WIDE_PANEL_CLASS() "panel-wide-item",
   /**
    * The (constant) number of columns in the menu panel.
    */
   get PANEL_COLUMN_COUNT() 3,
 
   /**
+   * An iteratable property of windows managed by CustomizableUI.
+   * Note that this can *only* be used as an iterator. ie:
+   *     for (let window of CustomizableUI.windows) { ... }
+   */
+  windows: {
+    "@@iterator": function*() {
+      for (let [window,] of gBuildWindows)
+        yield window;
+    }
+  },
+
+  /**
    * Add a listener object that will get fired for various events regarding
    * customization.
    *
    * @param aListener the listener object to add
    *
    * Not all event handler methods need to be defined.
    * CustomizableUI will catch exceptions. Events are dispatched
    * synchronously on the UI thread, so if you can delay any/some of your
@@ -2555,16 +2571,22 @@ this.CustomizableUI = {
    *     Fired when exiting customize mode in aWindow.
    *
    *   - onWidgetOverflow(aNode, aContainer)
    *     Fired when a widget's DOM node is overflowing its container, a toolbar,
    *     and will be displayed in the overflow panel.
    *   - onWidgetUnderflow(aNode, aContainer)
    *     Fired when a widget's DOM node is *not* overflowing its container, a
    *     toolbar, anymore.
+   *   - onWindowOpened(aWindow)
+   *     Fired when a window has been opened that is managed by CustomizableUI,
+   *     once all of the prerequisite setup has been done.
+   *   - onWindowClosed(aWindow)
+   *     Fired when a window that has been managed by CustomizableUI has been
+   *     closed.
    */
   addListener: function(aListener) {
     CustomizableUIInternal.addListener(aListener);
   },
   /**
    * Remove a listener added with addListener
    * @param aListener the listener object to remove
    */
@@ -3270,17 +3292,17 @@ this.CustomizableUI = {
         place = "palette";
 
       node = node.parentNode;
     }
     return place;
   }
 };
 Object.freeze(this.CustomizableUI);
-
+Object.freeze(this.CustomizableUI.windows);
 
 /**
  * All external consumers of widgets are really interacting with these wrappers
  * which provide a common interface.
  */
 
 /**
  * WidgetGroupWrapper is the common interface for interacting with an entire
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -84,9 +84,10 @@ skip-if = os == "linux"
 
 [browser_984455_bookmarks_items_reparenting.js]
 skip-if = os == "linux"
 
 [browser_985815_propagate_setToolbarVisibility.js]
 [browser_981305_separator_insertion.js]
 [browser_987177_destroyWidget_xul.js]
 [browser_987177_xul_wrapper_updating.js]
+[browser_987492_window_api.js]
 [browser_panel_toggle.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987492_window_api.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+
+add_task(function* testOneWindow() {
+  let windows = [];
+  for (let win of CustomizableUI.windows)
+    windows.push(win);
+  is(windows.length, 1, "Should have one customizable window");
+});
+
+
+add_task(function* testOpenCloseWindow() {
+  let newWindow = null;
+  let openListener = {
+    onWindowOpened: function(window) {
+      newWindow = window;
+    }
+  }
+  CustomizableUI.addListener(openListener);
+  let win = yield openAndLoadWindow(null, true);
+  isnot(newWindow, null, "Should have gotten onWindowOpen event");
+  is(newWindow, win, "onWindowOpen event should have received expected window");
+  CustomizableUI.removeListener(openListener);
+
+  let windows = [];
+  for (let win of CustomizableUI.windows)
+    windows.push(win);
+  is(windows.length, 2, "Should have two customizable windows");
+  isnot(windows.indexOf(window), -1, "Current window should be in window collection.");
+  isnot(windows.indexOf(newWindow), -1, "New window should be in window collection.");
+
+  let closedWindow = null;
+  let closeListener = {
+    onWindowClosed: function(window) {
+      closedWindow = window;
+    }
+  }
+  CustomizableUI.addListener(closeListener);
+  yield promiseWindowClosed(newWindow);
+  isnot(closedWindow, null, "Should have gotten onWindowClosed event")
+  is(newWindow, closedWindow, "Closed window should match previously opened window");
+  CustomizableUI.removeListener(closeListener);
+
+  let windows = [];
+  for (let win of CustomizableUI.windows)
+    windows.push(win);
+  is(windows.length, 1, "Should have one customizable window");
+  isnot(windows.indexOf(window), -1, "Current window should be in window collection.");
+  is(windows.indexOf(closedWindow), -1, "Closed window should not be in window collection.");
+});