Bug 949659 - UITour: Hide annotations anchored in the menu panel when it closes. r=Unfocused
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 05 Feb 2014 01:02:40 -0800
changeset 166832 a07a4dba6a8d884b8d1f2140682c6f196cc73ac9
parent 166831 36caecbe41620181abcc583c307696bb9bf2953d
child 166833 18d70a3136f51e5cd12d5f54f28d9f6c2c77f2e2
push id4847
push usermozilla@noorenberghe.ca
push dateWed, 05 Feb 2014 09:04:01 +0000
treeherderfx-team@a07a4dba6a8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused
bugs949659
milestone30.0a1
Bug 949659 - UITour: Hide annotations anchored in the menu panel when it closes. r=Unfocused [Australis]
browser/modules/UITour.jsm
browser/modules/test/browser.ini
browser/modules/test/browser_UITour_panel_close_annotation.js
browser/modules/test/head.js
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -318,16 +318,17 @@ this.UITour = {
         }
         break;
       }
     }
   },
 
   teardownTour: function(aWindow, aWindowClosing = false) {
     aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
+    aWindow.PanelUI.panel.removeEventListener("popuphiding", this.onAppMenuHiding);
     aWindow.removeEventListener("SSWindowClosing", this);
 
     let originTabs = this.originTabs.get(aWindow);
     if (originTabs) {
       for (let tab of originTabs)
         tab.removeEventListener("TabClose", this);
     }
     this.originTabs.delete(aWindow);
@@ -427,37 +428,42 @@ this.UITour = {
   getTarget: function(aWindow, aTargetName, aSticky = false) {
     let deferred = Promise.defer();
     if (typeof aTargetName != "string" || !aTargetName) {
       deferred.reject("Invalid target name specified");
       return deferred.promise;
     }
 
     if (aTargetName == "pinnedTab") {
-      deferred.resolve({node: this.ensurePinnedTab(aWindow, aSticky)});
+      deferred.resolve({
+          targetName: aTargetName,
+          node: this.ensurePinnedTab(aWindow, aSticky)
+      });
       return deferred.promise;
     }
 
     let targetObject = this.targets.get(aTargetName);
     if (!targetObject) {
       deferred.reject("The specified target name is not in the allowed set");
       return deferred.promise;
     }
 
     let targetQuery = targetObject.query;
     aWindow.PanelUI.ensureReady().then(() => {
       if (typeof targetQuery == "function") {
         deferred.resolve({
+          targetName: aTargetName,
           node: targetQuery(aWindow.document),
           widgetName: targetObject.widgetName,
         });
         return;
       }
 
       deferred.resolve({
+        targetName: aTargetName,
         node: aWindow.document.querySelector(targetQuery),
         widgetName: targetObject.widgetName,
       });
     }).then(null, Cu.reportError);
     return deferred.promise;
   },
 
   targetIsInAppMenu: function(aTarget) {
@@ -568,16 +574,17 @@ this.UITour = {
       if (effect == "random") {
         // Exclude "random" from the randomly selected effects.
         let randomEffect = 1 + Math.floor(Math.random() * (this.highlightEffects.length - 1));
         if (randomEffect == this.highlightEffects.length)
           randomEffect--; // On the order of 1 in 2^62 chance of this happening.
         effect = this.highlightEffects[randomEffect];
       }
       highlighter.setAttribute("active", effect);
+      highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
       highlighter.parentElement.hidden = false;
 
       let targetRect = aTargetEl.getBoundingClientRect();
       let highlightHeight = targetRect.height;
       let highlightWidth = targetRect.width;
       let minDimension = Math.min(highlightHeight, highlightWidth);
       let maxDimension = Math.max(highlightHeight, highlightWidth);
 
@@ -675,16 +682,17 @@ this.UITour = {
         tooltipButtons.appendChild(el);
       }
 
       tooltipButtons.hidden = !aButtons.length;
 
       let tooltipClose = document.getElementById("UITourTooltipClose");
       tooltipClose.addEventListener("command", this);
 
+      tooltip.setAttribute("targetName", aAnchor.targetName);
       tooltip.hidden = false;
       let alignment = "bottomcenter topright";
       tooltip.openPopup(aAnchorEl, alignment);
     }
 
     // Prevent showing a panel at an undefined position.
     if (!_isElementVisible(aAnchor.node))
       return;
@@ -719,16 +727,17 @@ this.UITour = {
     }
     function onPopupShown(event) {
       this.removeEventListener("popupshown", onPopupShown);
       aOpenCallback(event);
     }
 
     if (aMenuName == "appMenu") {
       aWindow.PanelUI.panel.setAttribute("noautohide", "true");
+      aWindow.PanelUI.panel.addEventListener("popuphiding", this.onAppMenuHiding);
       if (aOpenCallback) {
         aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
       }
       aWindow.PanelUI.show();
     } else if (aMenuName == "bookmarks") {
       openMenuButton("bookmarks-menu-button");
     }
   },
@@ -743,16 +752,41 @@ this.UITour = {
     if (aMenuName == "appMenu") {
       aWindow.PanelUI.panel.removeAttribute("noautohide");
       aWindow.PanelUI.hide();
     } else if (aMenuName == "bookmarks") {
       closeMenuButton("bookmarks-menu-button");
     }
   },
 
+  onAppMenuHiding: function(aEvent) {
+    let win = aEvent.target.ownerDocument.defaultView;
+    let annotationElements = new Map([
+      // [annotationElement (panel), method to hide the annotation]
+      [win.document.getElementById("UITourHighlightContainer"), UITour.hideHighlight.bind(UITour)],
+      [win.document.getElementById("UITourTooltip"), UITour.hideInfo.bind(UITour)],
+    ]);
+    annotationElements.forEach((hideMethod, annotationElement) => {
+      if (annotationElement.state != "closed") {
+        let targetName = annotationElement.getAttribute("targetName");
+        UITour.getTarget(win, targetName).then((aTarget) => {
+          // Since getTarget is async, we need to make sure that the target hasn't
+          // changed since it may have just moved to somewhere outside of the app menu.
+          if (annotationElement.getAttribute("targetName") != aTarget.targetName ||
+              annotationElement.state == "closed" ||
+              !UITour.targetIsInAppMenu(aTarget)) {
+            return;
+          }
+          hideMethod(win);
+        }).then(null, Cu.reportError);
+      }
+    });
+    UITour.appMenuOpenForAnnotation.clear();
+  },
+
   startUrlbarCapture: function(aWindow, aExpectedText, aUrl) {
     let urlbar = aWindow.document.getElementById("urlbar");
     this.urlbarCapture.set(aWindow, {
       expected: aExpectedText.toLocaleLowerCase(),
       url: aUrl
     });
     urlbar.addEventListener("input", this);
   },
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -5,11 +5,12 @@ support-files =
   image.png
 
 [browser_NetworkPrioritizer.js]
 [browser_SignInToWebsite.js]
 [browser_UITour.js]
 skip-if = os == "linux" # Intermittent failures, bug 951965
 [browser_UITour2.js]
 [browser_UITour3.js]
+[browser_UITour_panel_close_annotation.js]
 [browser_UITour_sync.js]
 [browser_taskbar_preview.js]
 run-if = os == "win"
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_UITour_panel_close_annotation.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that annotations disappear when their target is hidden.
+ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+let highlight = document.getElementById("UITourHighlight");
+let tooltip = document.getElementById("UITourTooltip");
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  registerCleanupFunction(() => {
+    // Close the find bar in case it's open in the remaining tab
+    gBrowser.getFindBar(gBrowser.selectedTab).close();
+  });
+  UITourTest();
+}
+
+let tests = [
+  function test_highlight_move_outside_panel(done) {
+    gContentAPI.showInfo("urlbar", "test title", "test text");
+    gContentAPI.showHighlight("customize");
+    waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+      isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+      // Move the highlight outside which should close the app menu.
+      gContentAPI.showHighlight("appMenu");
+      waitForPopupAtAnchor(highlight.parentElement, document.getElementById("PanelUI-menu-button"), () => {
+        isnot(PanelUI.panel.state, "open",
+              "Panel should have closed after the highlight moved elsewhere.");
+        is(tooltip.state, "open", "The info panel should have remained open");
+        done();
+      }, "Highlight should move to the appMenu button and still be visible");
+    }, "Highlight should be shown after showHighlight() for fixed panel items");
+  },
+
+  function test_highlight_panel_hideMenu(done) {
+    gContentAPI.showHighlight("customize");
+    gContentAPI.showInfo("search", "test title", "test text");
+    waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+      isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+      // Close the app menu and make sure the highlight also disappeared.
+      gContentAPI.hideMenu("appMenu");
+      waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
+        isnot(PanelUI.panel.state, "open",
+              "Panel still should have closed");
+        is(tooltip.state, "open", "The info panel should have remained open");
+        done();
+      }, "Highlight should have disappeared when panel closed");
+    }, "Highlight should be shown after showHighlight() for fixed panel items");
+  },
+
+  function test_highlight_panel_click_find(done) {
+    gContentAPI.showHighlight("help");
+    gContentAPI.showInfo("searchProvider", "test title", "test text");
+    waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+      isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+      // Click the find button which should close the panel.
+      let findButton = document.getElementById("find-button");
+      EventUtils.synthesizeMouseAtCenter(findButton, {});
+      waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
+        isnot(PanelUI.panel.state, "open",
+              "Panel should have closed when the find bar opened");
+        is(tooltip.state, "open", "The info panel should have remained open");
+        done();
+      }, "Highlight should have disappeared when panel closed");
+    }, "Highlight should be shown after showHighlight() for fixed panel items");
+  },
+
+  function test_highlight_info_panel_click_find(done) {
+    gContentAPI.showHighlight("help");
+    gContentAPI.showInfo("customize", "customize me!", "awesome!");
+    waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+      isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+      // Click the find button which should close the panel.
+      let findButton = document.getElementById("find-button");
+      EventUtils.synthesizeMouseAtCenter(findButton, {});
+      waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
+        isnot(PanelUI.panel.state, "open",
+              "Panel should have closed when the find bar opened");
+        waitForElementToBeHidden(tooltip, function checkTooltipIsClosed() {
+          isnot(tooltip.state, "open", "The info panel should have closed too");
+          done();
+        }, "Tooltip should hide with the menu");
+      }, "Highlight should have disappeared when panel closed");
+    }, "Highlight should be shown after showHighlight() for fixed panel items");
+  },
+
+  function test_info_move_outside_panel(done) {
+    gContentAPI.showInfo("addons", "test title", "test text");
+    gContentAPI.showHighlight("urlbar");
+    let addonsButton = document.getElementById("add-ons-button");
+    waitForPopupAtAnchor(tooltip, addonsButton, function checkPanelIsOpen() {
+      isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+      // Move the info panel outside which should close the app menu.
+      gContentAPI.showInfo("appMenu", "Cool menu button", "It's three lines");
+      waitForPopupAtAnchor(tooltip, document.getElementById("PanelUI-menu-button"), () => {
+        isnot(PanelUI.panel.state, "open",
+              "Menu should have closed after the highlight moved elsewhere.");
+        is(highlight.parentElement.state, "open", "The highlight should have remained visible");
+        done();
+      }, "Tooltip should move to the appMenu button and still be visible");
+    }, "Tooltip should be shown after showInfo() for a panel item");
+  },
+
+];
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -48,21 +48,30 @@ function waitForElementToBeVisible(eleme
   waitForCondition(() => !is_hidden(element),
                    () => {
                      ok(true, msg);
                      nextTest();
                    },
                    "Timeout waiting for visibility: " + msg);
 }
 
+function waitForElementToBeHidden(element, nextTest, msg) {
+  waitForCondition(() => is_hidden(element),
+                   () => {
+                     ok(true, msg);
+                     nextTest();
+                   },
+                   "Timeout waiting for invisibility: " + msg);
+}
+
 function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
   waitForCondition(() => popup.popupBoxObject.anchorNode == anchorNode,
                    () => {
                      ok(true, msg);
-                     is_element_visible(popup);
+                     is_element_visible(popup, "Popup should be visible");
                      nextTest();
                    },
                    "Timeout waiting for popup at anchor: " + msg);
 }
 
 function is_element_hidden(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
   ok(is_hidden(element), msg);