Bug 936195 - Australis UITour: Give webpage a way to query what UI targets are available. r=unfocused, a=sylvestre
authorDrew Willcoxon <adw@mozilla.com>
Mon, 03 Mar 2014 17:39:35 -0800
changeset 183164 cb3cbffab64b7809936c05e410648248362590c1
parent 183163 b88a05e76c119e1345b0a579775b6d094e7344f0
child 183165 d6b7ebc6f4d8a4ad2f5102bcaf9bdb8d46586a4c
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersunfocused, sylvestre
bugs936195
milestone29.0a2
Bug 936195 - Australis UITour: Give webpage a way to query what UI targets are available. r=unfocused, a=sylvestre
browser/modules/UITour.jsm
browser/modules/test/browser.ini
browser/modules/test/browser_UITour_availableTargets.js
browser/modules/test/browser_UITour_sync.js
browser/modules/test/uitour.js
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -46,16 +46,17 @@ this.UITour = {
   pageIDSourceTabs: new WeakMap(),
   pageIDSourceWindows: new WeakMap(),
   /* Map from browser windows to a set of tabs in which a tour is open */
   originTabs: new WeakMap(),
   /* Map from browser windows to a set of pinned tabs opened by (a) tour(s) */
   pinnedTabs: new WeakMap(),
   urlbarCapture: new WeakMap(),
   appMenuOpenForAnnotation: new Set(),
+  availableTargetsCache: new WeakMap(),
 
   _detachingTab: false,
   _queuedEvents: [],
   _pendingDoc: null,
 
   highlightEffects: ["random", "wobble", "zoom", "color"],
   targets: new Map([
     ["accountStatus", {
@@ -100,17 +101,17 @@ this.UITour = {
       widgetName: "search-container",
     }],
     ["selectedTabIcon", {
       query: (aDocument) => {
         let selectedtab = aDocument.defaultView.gBrowser.selectedTab;
         let element = aDocument.getAnonymousElementByAttribute(selectedtab,
                                                                "anonid",
                                                                "tab-icon-image");
-        if (!element || !this.isElementVisible(element)) {
+        if (!element || !UITour.isElementVisible(element)) {
           return null;
         }
         return element;
       },
     }],
     ["urlbar",      {
       query: "#urlbar",
       widgetName: "urlbar-container",
@@ -123,16 +124,29 @@ this.UITour = {
     delete this.seenPageIDs;
     Object.defineProperty(this, "seenPageIDs", {
       get: this.restoreSeenPageIDs.bind(this),
       configurable: true,
     });
 
     UITelemetry.addSimpleMeasureFunction("UITour",
                                          this.getTelemetry.bind(this));
+
+    // Clear the availableTargetsCache on widget changes.
+    let listenerMethods = [
+      "onWidgetAdded",
+      "onWidgetMoved",
+      "onWidgetRemoved",
+      "onWidgetReset",
+      "onAreaReset",
+    ];
+    CustomizableUI.addListener(listenerMethods.reduce((listener, method) => {
+      listener[method] = () => this.availableTargetsCache.clear();
+      return listener;
+    }, {}));
   },
 
   restoreSeenPageIDs: function() {
     delete this.seenPageIDs;
 
     if (UITelemetry.enabled) {
       let dateThreshold = Date.now() - SEENPAGEID_EXPIRY;
 
@@ -915,18 +929,18 @@ this.UITour = {
     this._setAppMenuStateForAnnotation(aWindow, "info", false);
 
     let tooltipButtons = document.getElementById("UITourTooltipButtons");
     while (tooltipButtons.firstChild)
       tooltipButtons.firstChild.remove();
   },
 
   showMenu: function(aWindow, aMenuName, aOpenCallback = null) {
-    function openMenuButton(aId) {
-      let menuBtn = aWindow.document.getElementById(aId);
+    function openMenuButton(aID) {
+      let menuBtn = aWindow.document.getElementById(aID);
       if (!menuBtn || !menuBtn.boxObject) {
         aOpenCallback();
         return;
       }
       if (aOpenCallback)
         menuBtn.addEventListener("popupshown", onPopupShown);
       menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(true);
     }
@@ -948,18 +962,18 @@ this.UITour = {
       }
       aWindow.PanelUI.show();
     } else if (aMenuName == "bookmarks") {
       openMenuButton("bookmarks-menu-button");
     }
   },
 
   hideMenu: function(aWindow, aMenuName) {
-    function closeMenuButton(aId) {
-      let menuBtn = aWindow.document.getElementById(aId);
+    function closeMenuButton(aID) {
+      let menuBtn = aWindow.document.getElementById(aID);
       if (menuBtn && menuBtn.boxObject)
         menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(false);
     }
 
     if (aMenuName == "appMenu") {
       aWindow.PanelUI.panel.removeAttribute("noautohide");
       aWindow.PanelUI.hide();
       this.recreatePopup(aWindow.PanelUI.panel);
@@ -1037,25 +1051,59 @@ this.UITour = {
 
     let tab = aWindow.gBrowser.addTab(url, {
       owner: aWindow.gBrowser.selectedTab,
       relatedToCurrent: true
     });
     aWindow.gBrowser.selectedTab = tab;
   },
 
-  getConfiguration: function(aContentDocument, aConfiguration, aCallbackId) {
-    let config = null;
+  getConfiguration: function(aContentDocument, aConfiguration, aCallbackID) {
     switch (aConfiguration) {
+      case "availableTargets":
+        this.getAvailableTargets(aContentDocument, aCallbackID);
+        break;
       case "sync":
-        config = {
+        this.sendPageCallback(aContentDocument, aCallbackID, {
           setup: Services.prefs.prefHasUserValue("services.sync.username"),
-        };
+        });
         break;
       default:
         Cu.reportError("getConfiguration: Unknown configuration requested: " + aConfiguration);
         break;
     }
-    this.sendPageCallback(aContentDocument, aCallbackId, config);
+  },
+
+  getAvailableTargets: function(aContentDocument, aCallbackID) {
+    let window = this.getChromeWindow(aContentDocument);
+    let data = this.availableTargetsCache.get(window);
+    if (data) {
+      this.sendPageCallback(aContentDocument, aCallbackID, data);
+      return;
+    }
+
+    let promises = [];
+    for (let targetName of this.targets.keys()) {
+      promises.push(this.getTarget(window, targetName));
+    }
+    Promise.all(promises).then((targetObjects) => {
+      let targetNames = [
+        "pinnedTab",
+      ];
+      for (let targetObject of targetObjects) {
+        if (targetObject.node)
+          targetNames.push(targetObject.targetName);
+      }
+      let data = {
+        targets: targetNames,
+      };
+      this.availableTargetsCache.set(window, data);
+      this.sendPageCallback(aContentDocument, aCallbackID, data);
+    }, (err) => {
+      Cu.reportError(err);
+      this.sendPageCallback(aContentDocument, aCallbackID, {
+        targets: [],
+      });
+    });
   },
 };
 
 this.UITour.init();
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -6,14 +6,15 @@ support-files =
 
 [browser_BrowserUITelemetry_buckets.js]
 [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_availableTargets.js]
 [browser_UITour_panel_close_annotation.js]
 [browser_UITour_detach_tab.js]
 [browser_UITour_registerPageID.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_availableTargets.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+let tests = [
+  function test_availableTargets(done) {
+    gContentAPI.getConfiguration("availableTargets", (data) => {
+      ok_targets(data, [
+        "accountStatus",
+        "addons",
+        "appMenu",
+        "backForward",
+        "bookmarks",
+        "customize",
+        "help",
+        "home",
+        "pinnedTab",
+        "quit",
+        "search",
+        "searchProvider",
+        "urlbar",
+      ]);
+      ok(UITour.availableTargetsCache.has(window),
+         "Targets should now be cached");
+      done();
+    });
+  },
+
+  function test_availableTargets_changeWidgets(done) {
+    CustomizableUI.removeWidgetFromArea("bookmarks-menu-button");
+    ok(!UITour.availableTargetsCache.has(window),
+       "Targets should be evicted from cache after widget change");
+    gContentAPI.getConfiguration("availableTargets", (data) => {
+      ok_targets(data, [
+        "accountStatus",
+        "addons",
+        "appMenu",
+        "backForward",
+        "customize",
+        "help",
+        "home",
+        "pinnedTab",
+        "quit",
+        "search",
+        "searchProvider",
+        "urlbar",
+      ]);
+      ok(UITour.availableTargetsCache.has(window),
+         "Targets should now be cached again");
+      CustomizableUI.reset();
+      ok(!UITour.availableTargetsCache.has(window),
+         "Targets should not be cached after reset");
+      done();
+    });
+  },
+];
+
+function ok_targets(actualData, expectedTargets) {
+  // Depending on how soon after page load this is called, the selected tab icon
+  // may or may not be showing the loading throbber.  Check for its presence and
+  // insert it into expectedTargets if it's visible.
+  let selectedTabIcon =
+    document.getAnonymousElementByAttribute(gBrowser.selectedTab,
+                                            "anonid",
+                                            "tab-icon-image");
+  if (selectedTabIcon && UITour.isElementVisible(selectedTabIcon))
+    expectedTargets.push("selectedTabIcon");
+
+  ok(Array.isArray(actualData.targets), "data.targets should be an array");
+  is(actualData.targets.sort().toString(), expectedTargets.sort().toString(),
+     "Targets should be as expected");
+}
--- a/browser/modules/test/browser_UITour_sync.js
+++ b/browser/modules/test/browser_UITour_sync.js
@@ -18,21 +18,21 @@ function test() {
 
 let tests = [
   function test_checkSyncSetup_disabled(done) {
     function callback(result) {
       is(result.setup, false, "Sync shouldn't be setup by default");
       done();
     }
 
-    gContentAPI.getSyncConfiguration(callback);
+    gContentAPI.getConfiguration("sync", callback);
   },
 
   function test_checkSyncSetup_enabled(done) {
     function callback(result) {
       is(result.setup, true, "Sync should be setup");
       done();
     }
 
     Services.prefs.setCharPref("services.sync.username", "uitour@tests.mozilla.org");
-    gContentAPI.getSyncConfiguration(callback);
+    gContentAPI.getConfiguration("sync", callback);
   },
 ];
--- a/browser/modules/test/uitour.js
+++ b/browser/modules/test/uitour.js
@@ -155,15 +155,15 @@ if (typeof Mozilla == 'undefined') {
 	};
 
 	Mozilla.UITour.hideMenu = function(name) {
 		_sendEvent('hideMenu', {
 			name: name
 		});
 	};
 
-	Mozilla.UITour.getSyncConfiguration = function(callback) {
+	Mozilla.UITour.getConfiguration = function(configName, callback) {
 		_sendEvent('getConfiguration', {
 			callbackID: _waitForCallback(callback),
-			configuration: "sync",
+			configuration: configName,
 		});
 	};
 })();