Bug 1080943 - UITour: Allow opening the Hello panel. r=Unfocused a=dolske
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Sun, 30 Nov 2014 21:46:23 -0800
changeset 242518 ea2567a86c38151e83cd402ae1f7bf69ed2307c5
parent 242517 b9ec1831c7259ebc9b0b6ff8382dc7ad20f75f25
child 242519 94d5dccebb4ee3791e37a077fbad4ca379b0c86a
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused, dolske
bugs1080943
milestone36.0a2
Bug 1080943 - UITour: Allow opening the Hello panel. r=Unfocused a=dolske
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;
@@ -1234,16 +1240,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) {
@@ -1254,16 +1280,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;
@@ -1285,16 +1314,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 = [];
+}