Bug 1080943 - UITour: Allow opening the Hello panel. r=Unfocused
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Sun, 30 Nov 2014 21:46:23 -0800
changeset 218148 5994d92115accb8c5478b5dea15d90f7a6341a6e
parent 218147 9dffef3538c3c683e1804ce14a5713e334bcff90
child 218149 099b473757da34a2ce848d79bf404e214bebd07a
push id27917
push usercbook@mozilla.com
push dateMon, 01 Dec 2014 11:03:31 +0000
treeherdermozilla-central@707a34b55e44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused
bugs1080943
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1080943 - UITour: Allow opening the Hello panel. r=Unfocused
browser/base/content/browser-loop.js
browser/modules/UITour.jsm
browser/modules/test/browser.ini
browser/modules/test/browser_UITour2.js
browser/modules/test/browser_UITour_loop.js
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -19,58 +19,63 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
     /**
      * Opens the panel for Loop and sizes it appropriately.
      *
      * @param {event}  event   The event opening the panel, used to anchor
      *                         the panel to the button which triggers it.
      * @param {String} [tabId] Identifier of the tab to select when the panel is
      *                         opened. Example: 'rooms', 'contacts', etc.
+     * @return {Promise}
      */
     openCallPanel: function(event, tabId = null) {
-      let callback = iframe => {
-        // Helper function to show a specific tab view in the panel.
-        function showTab() {
-          if (!tabId) {
+      return new Promise((resolve) => {
+        let callback = iframe => {
+          // Helper function to show a specific tab view in the panel.
+          function showTab() {
+            if (!tabId) {
+              resolve();
+              return;
+            }
+
+            let win = iframe.contentWindow;
+            let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
+              detail: {
+                action: "selectTab",
+                tab: tabId
+              }
+            }, win));
+            win.dispatchEvent(ev);
+            resolve();
+          }
+
+          // If the panel has been opened and initialized before, we can skip waiting
+          // for the content to load - because it's already there.
+          if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
+            showTab();
             return;
           }
 
-          let win = iframe.contentWindow;
-          let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
-            detail: {
-              action: "selectTab",
-              tab: tabId
-            }
-          }, win));
-          win.dispatchEvent(ev);
-        }
-
-        // If the panel has been opened and initialized before, we can skip waiting
-        // for the content to load - because it's already there.
-        if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
-          showTab();
-          return;
-        }
+          iframe.addEventListener("DOMContentLoaded", function documentDOMLoaded() {
+            iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
+            injectLoopAPI(iframe.contentWindow);
+            iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
+              iframe.contentWindow.removeEventListener("loopPanelInitialized",
+                                                       loopPanelInitialized);
+              showTab();
+            });
+          }, true);
+        };
 
-        iframe.addEventListener("DOMContentLoaded", function documentDOMLoaded() {
-          iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
-          injectLoopAPI(iframe.contentWindow);
-          iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
-            iframe.contentWindow.removeEventListener("loopPanelInitialized",
-              loopPanelInitialized);
-            showTab();
-          });
-        }, true);
-      };
+        // Used to clear the temporary "login" state from the button.
+        Services.obs.notifyObservers(null, "loop-status-changed", null);
 
-      // Used to clear the temporary "login" state from the button.
-      Services.obs.notifyObservers(null, "loop-status-changed", null);
-
-      PanelFrame.showPopup(window, event ? event.target : this.toolbarButton.node,
-                           "loop", null, "about:looppanel", null, callback);
+        PanelFrame.showPopup(window, event ? event.target : this.toolbarButton.node,
+                             "loop", null, "about:looppanel", null, callback);
+      });
     },
 
     /**
      * Triggers the initialization of the loop service.  Called by
      * delayedStartup.
      */
     init: function() {
       // Add observer notifications before the service is initialized
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -106,25 +106,25 @@ this.UITour = {
       query: (aDocument) => {
         let customizeButton = aDocument.getElementById("PanelUI-customize");
         return aDocument.getAnonymousElementByAttribute(customizeButton,
                                                         "class",
                                                         "toolbarbutton-icon");
       },
       widgetName: "PanelUI-customize",
     }],
+    ["devtools",    {query: "#developer-button"}],
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
-    ["loop",        {query: "#loop-button"}],
-    ["devtools",    {query: "#developer-button"}],
-    ["webide",      {query: "#webide-button"}],
     ["forget", {
       query: "#panic-button",
       widgetName: "panic-button",
-      allowAdd: true }],
+      allowAdd: true,
+    }],
+    ["loop",        {query: "#loop-button"}],
     ["privateWindow",  {query: "#privatebrowsing-button"}],
     ["quit",        {query: "#PanelUI-quit"}],
     ["search",      {
       query: "#searchbar",
       widgetName: "search-container",
     }],
     ["searchProvider", {
       query: (aDocument) => {
@@ -183,16 +183,17 @@ this.UITour = {
         }
         return element;
       },
     }],
     ["urlbar",      {
       query: "#urlbar",
       widgetName: "urlbar-container",
     }],
+    ["webide",      {query: "#webide-button"}],
   ]),
 
   init: function() {
     log.debug("Initializing UITour");
     // Lazy getter is initialized here so it can be replicated any time
     // in a test.
     delete this.seenPageIDs;
     Object.defineProperty(this, "seenPageIDs", {
@@ -709,36 +710,41 @@ this.UITour = {
     return {
       seenPageIDs: [...this.seenPageIDs.keys()],
     };
   },
 
   teardownTour: function(aWindow, aWindowClosing = false) {
     log.debug("teardownTour: aWindowClosing = " + aWindowClosing);
     aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
-    aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
-    aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
     aWindow.removeEventListener("SSWindowClosing", this);
 
     let originTabs = this.originTabs.get(aWindow);
     if (originTabs) {
       for (let tab of originTabs) {
         tab.removeEventListener("TabClose", this);
         tab.removeEventListener("TabBecomingWindow", this);
       }
     }
     this.originTabs.delete(aWindow);
 
     if (!aWindowClosing) {
       this.hideHighlight(aWindow);
       this.hideInfo(aWindow);
       // Ensure the menu panel is hidden before calling recreatePopup so popup events occur.
       this.hideMenu(aWindow, "appMenu");
+      this.hideMenu(aWindow, "loop");
     }
 
+    // Clean up panel listeners after we may have called hideMenu above.
+    aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
+    aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
+    let loopPanel = aWindow.document.getElementById("loop-notification-panel");
+    loopPanel.removeEventListener("popuphidden", this.onLoopPanelHidden);
+
     this.endUrlbarCapture(aWindow);
     this.removePinnedTab(aWindow);
     this.resetTheme();
   },
 
   getChromeWindow: function(aContentDocument) {
     return aContentDocument.defaultView
                            .window
@@ -862,17 +868,17 @@ this.UITour = {
   },
 
   /**
    * Called before opening or after closing a highlight or info panel to see if
    * we need to open or close the appMenu to see the annotation's anchor.
    */
   _setAppMenuStateForAnnotation: function(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
     log.debug("_setAppMenuStateForAnnotation:", aAnnotationType);
-    log.debug("_setAppMenuStateForAnnotation: Menu is exptected to be:", aShouldOpenForHighlight ? "open" : "closed");
+    log.debug("_setAppMenuStateForAnnotation: Menu is expected to be:", aShouldOpenForHighlight ? "open" : "closed");
 
     // If the panel is in the desired state, we're done.
     let panelIsOpen = aWindow.PanelUI.panel.state != "closed";
     if (aShouldOpenForHighlight == panelIsOpen) {
       log.debug("_setAppMenuStateForAnnotation: Panel already in expected state");
       if (aCallback)
         aCallback();
       return;
@@ -1233,16 +1239,36 @@ this.UITour = {
       aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hidePanelAnnotations);
       if (aOpenCallback) {
         aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
       }
       aWindow.PanelUI.show();
     } else if (aMenuName == "bookmarks") {
       let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
       openMenuButton(menuBtn);
+    } else if (aMenuName == "loop") {
+      let toolbarButton = aWindow.LoopUI.toolbarButton;
+      if (!toolbarButton || !toolbarButton.node) {
+        return;
+      }
+
+      let panel = aWindow.document.getElementById("loop-notification-panel");
+      panel.setAttribute("noautohide", true);
+      if (panel.state != "open") {
+        this.recreatePopup(panel);
+      }
+
+      // An event object is expected but we don't want to toggle the panel with a click if the panel
+      // is already open.
+      aWindow.LoopUI.openCallPanel({ target: toolbarButton.node, }).then(() => {
+        if (aOpenCallback) {
+          aOpenCallback();
+        }
+      });
+      panel.addEventListener("popuphidden", this.onLoopPanelHidden);
     } else if (aMenuName == "searchEngines") {
       this.getTarget(aWindow, "searchProvider").then(target => {
         openMenuButton(target.node);
       }).catch(log.error);
     }
   },
 
   hideMenu: function(aWindow, aMenuName) {
@@ -1253,16 +1279,19 @@ this.UITour = {
 
     if (aMenuName == "appMenu") {
       aWindow.PanelUI.panel.removeAttribute("noautohide");
       aWindow.PanelUI.hide();
       this.recreatePopup(aWindow.PanelUI.panel);
     } else if (aMenuName == "bookmarks") {
       let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
       closeMenuButton(menuBtn);
+    } else if (aMenuName == "loop") {
+      let panel = aWindow.document.getElementById("loop-notification-panel");
+      panel.hidePopup();
     } else if (aMenuName == "searchEngines") {
       let menuBtn = this.targets.get("searchProvider").query(aWindow.document);
       closeMenuButton(menuBtn);
     }
   },
 
   hidePanelAnnotations: function(aEvent) {
     let win = aEvent.target.ownerDocument.defaultView;
@@ -1284,16 +1313,21 @@ this.UITour = {
           }
           hideMethod(win);
         }).catch(log.error);
       }
     });
     UITour.appMenuOpenForAnnotation.clear();
   },
 
+  onLoopPanelHidden: function(aEvent) {
+    aEvent.target.removeAttribute("noautohide");
+    UITour.recreatePopup(aEvent.target);
+  },
+
   recreatePopup: function(aPanel) {
     // After changing popup attributes that relate to how the native widget is created
     // (e.g. @noautohide) we need to re-create the frame/widget for it to take effect.
     if (aPanel.hidden) {
       // If the panel is already hidden, we don't need to recreate it but flush
       // in case someone just hid it.
       aPanel.clientWidth; // flush
       return;
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -23,16 +23,18 @@ skip-if = e10s # Bug 941428 - UITour.jsm
 [browser_UITour3.js]
 skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
+[browser_UITour_loop.js]
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_modalDialog.js]
 run-if = os == "mac" # modal dialog disabling only working on OS X
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_panel_close_annotation.js]
 skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
 [browser_UITour_registerPageID.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_sync.js]
--- a/browser/modules/test/browser_UITour2.js
+++ b/browser/modules/test/browser_UITour2.js
@@ -84,17 +84,17 @@ let tests = [
     tabInfo = UITour.pinnedTabs.get(window);
     is(tabInfo, null, "Should not have any data about the removed pinned tab after removePinnedTab()");
 
     yield addPinnedTabPromise();
     yield addPinnedTabPromise();
     yield addPinnedTabPromise();
     is(gBrowser.tabs[1].pinned, false, "After multiple calls of addPinnedTab, should still only have one pinned tab");
   }),
-  taskify(function* test_menu() {
+  taskify(function* test_bookmarks_menu() {
     let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
 
     ise(bookmarksMenuButton.open, false, "Menu should initially be closed");
     gContentAPI.showMenu("bookmarks");
 
     yield waitForConditionPromise(() => {
       return bookmarksMenuButton.open;
     }, "Menu should be visible after showMenu()");
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_UITour_loop.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+let loopButton;
+let loopPanel = document.getElementById("loop-notification-panel");
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+let tests = [
+  taskify(function* test_menu_show_hide() {
+    ise(loopButton.open, false, "Menu should initially be closed");
+    gContentAPI.showMenu("loop");
+
+    yield waitForConditionPromise(() => {
+      return loopButton.open;
+    }, "Menu should be visible after showMenu()");
+
+    ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
+    ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
+    is(loopPanel.state, "open", "The panel should be open");
+    ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
+
+    gContentAPI.hideMenu("loop");
+    yield waitForConditionPromise(() => {
+        return !loopButton.open;
+    }, "Menu should be hidden after hideMenu()");
+
+    checkLoopPanelIsHidden();
+  }),
+  // Test the menu was cleaned up in teardown.
+  taskify(function* setup_menu_cleanup() {
+    gContentAPI.showMenu("loop");
+
+    yield waitForConditionPromise(() => {
+      return loopButton.open;
+    }, "Menu should be visible after showMenu()");
+
+    // Leave it open so it gets torn down and we can test below that teardown was succesful.
+  }),
+  taskify(function* test_menu_cleanup() {
+    // Test that the open menu from above was torn down fully.
+    checkLoopPanelIsHidden();
+  }),
+];
+
+function checkLoopPanelIsHidden() {
+  ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
+  ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
+  isnot(loopPanel.state, "open", "The panel shouldn't be open");
+  is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
+}
+
+if (Services.prefs.getBoolPref("loop.enabled")) {
+  loopButton = window.LoopUI.toolbarButton.node;
+} else {
+  ok(true, "Loop is disabled so skip the UITour Loop tests");
+  tests = [];
+}