Bug 1104921 - UITour: Add Loop panel targets. r=Unfocused
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Thu, 04 Dec 2014 14:40:03 -0800
changeset 218483 ef884e9f38d4224e9c6d88d2ea649d009f07d942
parent 218482 eda93688e4b51393270c27e59adf802c89432018
child 218484 fd1ce8cc5c029fb137ea672232c6f464da7faeee
push id27932
push usercbook@mozilla.com
push dateFri, 05 Dec 2014 12:05:46 +0000
treeherdermozilla-central@18188c19a3c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused
bugs1104921
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 1104921 - UITour: Add Loop panel targets. r=Unfocused
browser/base/content/browser-loop.js
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/modules/UITour.jsm
browser/modules/test/browser_UITour_loop.js
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -13,43 +13,59 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 (function() {
   LoopUI = {
     get toolbarButton() {
       delete this.toolbarButton;
       return this.toolbarButton = CustomizableUI.getWidget("loop-button").forWindow(window);
     },
 
     /**
+     * @return {Promise}
+     */
+    promiseDocumentVisible(aDocument) {
+      if (!aDocument.hidden) {
+        return Promise.resolve();
+      }
+
+      return new Promise((resolve) => {
+        aDocument.addEventListener("visibilitychange", function onVisibilityChanged() {
+          aDocument.removeEventListener("visibilitychange", onVisibilityChanged);
+          resolve();
+        });
+      });
+    },
+
+    /**
      * 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) {
       return new Promise((resolve) => {
         let callback = iframe => {
           // Helper function to show a specific tab view in the panel.
           function showTab() {
             if (!tabId) {
-              resolve();
+              resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
               return;
             }
 
             let win = iframe.contentWindow;
             let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
               detail: {
                 action: "selectTab",
                 tab: tabId
               }
             }, win));
             win.dispatchEvent(ev);
-            resolve();
+            resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
           }
 
           // 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;
           }
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -747,17 +747,17 @@ loop.panel = (function(_, mozL10n) {
               return RoomEntry({
                 key: room.roomToken, 
                 dispatcher: this.props.dispatcher, 
                 room: room}
               );
             }, this)
           ), 
           React.DOM.p(null, 
-            React.DOM.button({className: "btn btn-info", 
+            React.DOM.button({className: "btn btn-info new-room-button", 
                     onClick: this.handleCreateButtonClick, 
                     disabled: this._hasPendingOperation()}, 
               mozL10n.get("rooms_new_room_button_label")
             )
           )
         )
       );
     }
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -747,17 +747,17 @@ loop.panel = (function(_, mozL10n) {
               return <RoomEntry
                 key={room.roomToken}
                 dispatcher={this.props.dispatcher}
                 room={room}
               />;
             }, this)
           }</div>
           <p>
-            <button className="btn btn-info"
+            <button className="btn btn-info new-room-button"
                     onClick={this.handleCreateButtonClick}
                     disabled={this._hasPendingOperation()}>
               {mozL10n.get("rooms_new_room_button_label")}
             </button>
           </p>
         </div>
       );
     }
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -115,16 +115,43 @@ this.UITour = {
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
     ["forget", {
       query: "#panic-button",
       widgetName: "panic-button",
       allowAdd: true,
     }],
     ["loop",        {query: "#loop-button"}],
+    ["loop-newRoom", {
+      query: (aDocument) => {
+        let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
+        if (!loopBrowser) {
+          return null;
+        }
+        return loopBrowser.contentDocument.querySelector(".new-room-button");
+      },
+    }],
+    ["loop-roomList", {
+      query: (aDocument) => {
+        let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
+        if (!loopBrowser) {
+          return null;
+        }
+        return loopBrowser.contentDocument.querySelector(".room-list");
+      },
+    }],
+    ["loop-signInUpLink", {
+      query: (aDocument) => {
+        let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop");
+        if (!loopBrowser) {
+          return null;
+        }
+        return loopBrowser.contentDocument.querySelector(".signin-link");
+      },
+    }],
     ["privateWindow",  {query: "#privatebrowsing-button"}],
     ["quit",        {query: "#PanelUI-quit"}],
     ["search",      {
       query: "#searchbar",
       widgetName: "search-container",
     }],
     ["searchProvider", {
       query: (aDocument) => {
@@ -353,17 +380,17 @@ this.UITour = {
           if (!target.node) {
             log.error("UITour: Target could not be resolved: " + data.target);
             return;
           }
           let effect = undefined;
           if (this.highlightEffects.indexOf(data.effect) !== -1) {
             effect = data.effect;
           }
-          this.showHighlight(target, effect);
+          this.showHighlight(window, target, effect);
         }).catch(log.error);
         break;
       }
 
       case "hideHighlight": {
         this.hideHighlight(window);
         break;
       }
@@ -409,17 +436,17 @@ this.UITour = {
 
           let infoOptions = {};
 
           if (typeof data.closeButtonCallbackID == "string")
             infoOptions.closeButtonCallbackID = data.closeButtonCallbackID;
           if (typeof data.targetCallbackID == "string")
             infoOptions.targetCallbackID = data.targetCallbackID;
 
-          this.showInfo(messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
+          this.showInfo(window, messageManager, target, data.title, data.text, iconURL, buttons, infoOptions);
         }).catch(log.error);
         break;
       }
 
       case "hideInfo": {
         this.hideInfo(window);
         break;
       }
@@ -730,20 +757,22 @@ this.UITour = {
       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);
+    aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hideAppMenuAnnotations);
+    aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hideAppMenuAnnotations);
+    aWindow.PanelUI.panel.removeEventListener("popuphidden", this.onPanelHidden);
     let loopPanel = aWindow.document.getElementById("loop-notification-panel");
-    loopPanel.removeEventListener("popuphidden", this.onLoopPanelHidden);
+    loopPanel.removeEventListener("popuphidden", this.onPanelHidden);
+    loopPanel.removeEventListener("popuphiding", this.hideLoopPanelAnnotations);
 
     this.endUrlbarCapture(aWindow);
     this.removePinnedTab(aWindow);
     this.resetTheme();
   },
 
   getChromeWindow: function(aContentDocument) {
     return aContentDocument.defaultView
@@ -787,17 +816,19 @@ this.UITour = {
   sendPageCallback: function(aMessageManager, aCallbackID, aData = {}) {
     let detail = {data: aData, callbackID: aCallbackID};
     log.debug("sendPageCallback", detail);
     aMessageManager.sendAsyncMessage("UITour:SendPageCallback", detail);
   },
 
   isElementVisible: function(aElement) {
     let targetStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
-    return (targetStyle.display != "none" && targetStyle.visibility == "visible");
+    return !aElement.ownerDocument.hidden &&
+             targetStyle.display != "none" &&
+             targetStyle.visibility == "visible";
   },
 
   getTarget: function(aWindow, aTargetName, aSticky = false) {
     log.debug("getTarget:", aTargetName);
     let deferred = Promise.defer();
     if (typeof aTargetName != "string" || !aTargetName) {
       log.warn("getTarget: Invalid target name specified");
       deferred.reject("Invalid target name specified");
@@ -948,49 +979,50 @@ this.UITour = {
 
   removePinnedTab: function(aWindow) {
     let tabInfo = this.pinnedTabs.get(aWindow);
     if (tabInfo)
       aWindow.gBrowser.removeTab(tabInfo.tab);
   },
 
   /**
+   * @param aChromeWindow The chrome window that the highlight is in. Necessary since some targets
+   *                      are in a sub-frame so the defaultView is not the same as the chrome
+   *                      window.
    * @param aTarget    The element to highlight.
    * @param aEffect    (optional) The effect to use from UITour.highlightEffects or "none".
    * @see UITour.highlightEffects
    */
-  showHighlight: function(aTarget, aEffect = "none") {
-    let window = aTarget.node.ownerDocument.defaultView;
-
+  showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
     function showHighlightPanel() {
       if (aTarget.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX)) {
         // This won't affect normal higlights done via the panel, so we need to
         // manually hide those.
-        this.hideHighlight(window);
+        this.hideHighlight(aChromeWindow);
         aTarget.node.setAttribute("_moz-menuactive", true);
         return;
       }
 
       // Conversely, highlights for search engines are highlighted via CSS
       // rather than a panel, so need to be manually removed.
-      this._hideSearchEngineHighlight(window);
+      this._hideSearchEngineHighlight(aChromeWindow);
 
-      let highlighter = aTarget.node.ownerDocument.getElementById("UITourHighlight");
+      let highlighter = aChromeWindow.document.getElementById("UITourHighlight");
 
       let effect = aEffect;
       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];
       }
       // Toggle the effect attribute to "none" and flush layout before setting it so the effect plays.
       highlighter.setAttribute("active", "none");
-      aTarget.node.ownerDocument.defaultView.getComputedStyle(highlighter).animationName;
+      aChromeWindow.getComputedStyle(highlighter).animationName;
       highlighter.setAttribute("active", effect);
       highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
       highlighter.parentElement.hidden = false;
 
       let highlightAnchor;
       // If the target is in the overflow panel, just highlight the overflow button.
       if (aTarget.node.getAttribute("overflowedItem")) {
         let doc = aTarget.node.ownerDocument;
@@ -1020,17 +1052,17 @@ this.UITour = {
 
       // Close a previous highlight so we can relocate the panel.
       if (highlighter.parentElement.state == "showing" || highlighter.parentElement.state == "open") {
         log.debug("showHighlight: Closing previous highlight first");
         highlighter.parentElement.hidePopup();
       }
       /* The "overlap" position anchors from the top-left but we want to centre highlights at their
          minimum size. */
-      let highlightWindow = aTarget.node.ownerDocument.defaultView;
+      let highlightWindow = aChromeWindow;
       let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
       let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
       let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
       let highlightStyle = highlightWindow.getComputedStyle(highlighter);
       let highlightHeightWithMin = Math.max(highlightHeight, parseFloat(highlightStyle.minHeight));
       let highlightWidthWithMin = Math.max(highlightWidth, parseFloat(highlightStyle.minWidth));
       let offsetX = paddingTopPx
                       - (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
@@ -1041,17 +1073,17 @@ this.UITour = {
     }
 
     // Prevent showing a panel at an undefined position.
     if (!this.isElementVisible(aTarget.node)) {
       log.warn("showHighlight: Not showing a highlight since the target isn't visible", aTarget);
       return;
     }
 
-    this._setAppMenuStateForAnnotation(aTarget.node.ownerDocument.defaultView, "highlight",
+    this._setAppMenuStateForAnnotation(aChromeWindow, "highlight",
                                        this.targetIsInAppMenu(aTarget),
                                        showHighlightPanel.bind(this));
   },
 
   hideHighlight: function(aWindow) {
     let tabData = this.pinnedTabs.get(aWindow);
     if (tabData && !tabData.sticky)
       this.removePinnedTab(aWindow);
@@ -1080,31 +1112,32 @@ this.UITour = {
       for (let menuItem of searchPopup.children)
         menuItem.removeAttribute("_moz-menuactive");
     }
   },
 
   /**
    * Show an info panel.
    *
+   * @param {ChromeWindow} aChromeWindow
    * @param {nsIMessageSender} aMessageManager
    * @param {Node}     aAnchor
    * @param {String}   [aTitle=""]
    * @param {String}   [aDescription=""]
    * @param {String}   [aIconURL=""]
    * @param {Object[]} [aButtons=[]]
    * @param {Object}   [aOptions={}]
    * @param {String}   [aOptions.closeButtonCallbackID]
    */
-  showInfo: function(aMessageManager, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
+  showInfo: function(aChromeWindow, aMessageManager, aAnchor, aTitle = "", aDescription = "", aIconURL = "",
                      aButtons = [], aOptions = {}) {
     function showInfoPanel(aAnchorEl) {
       aAnchorEl.focus();
 
-      let document = aAnchorEl.ownerDocument;
+      let document = aChromeWindow.document;
       let tooltip = document.getElementById("UITourTooltip");
       let tooltipTitle = document.getElementById("UITourTooltipTitle");
       let tooltipDesc = document.getElementById("UITourTooltipDescription");
       let tooltipIcon = document.getElementById("UITourTooltipIcon");
       let tooltipButtons = document.getElementById("UITourTooltipButtons");
 
       if (tooltip.state == "showing" || tooltip.state == "open") {
         tooltip.hidePopup();
@@ -1182,25 +1215,27 @@ this.UITour = {
         document.defaultView.addEventListener("endmodalstate", function endModalStateHandler() {
           document.defaultView.removeEventListener("endmodalstate", endModalStateHandler);
           tooltip.openPopup(aAnchorEl, alignment);
         }, false);
       }
     }
 
     // Prevent showing a panel at an undefined position.
-    if (!this.isElementVisible(aAnchor.node))
+    if (!this.isElementVisible(aAnchor.node)) {
+      log.warn("showInfo: Not showing since the target isn't visible", aAnchor);
       return;
+    }
 
     // Due to a platform limitation, we can't anchor a panel to an element in a
     // <menupopup>. So we can't support showing info panels for search engines.
     if (aAnchor.targetName.startsWith(TARGET_SEARCHENGINE_PREFIX))
       return;
 
-    this._setAppMenuStateForAnnotation(aAnchor.node.ownerDocument.defaultView, "info",
+    this._setAppMenuStateForAnnotation(aChromeWindow, "info",
                                        this.targetIsInAppMenu(aAnchor),
                                        showInfoPanel.bind(this, aAnchor.node));
   },
 
   hideInfo: function(aWindow) {
     let document = aWindow.document;
 
     let tooltip = document.getElementById("UITourTooltip");
@@ -1230,18 +1265,19 @@ this.UITour = {
     }
 
     if (aMenuName == "appMenu") {
       aWindow.PanelUI.panel.setAttribute("noautohide", "true");
       // If the popup is already opened, don't recreate the widget as it may cause a flicker.
       if (aWindow.PanelUI.panel.state != "open") {
         this.recreatePopup(aWindow.PanelUI.panel);
       }
-      aWindow.PanelUI.panel.addEventListener("popuphiding", this.hidePanelAnnotations);
-      aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hidePanelAnnotations);
+      aWindow.PanelUI.panel.addEventListener("popuphiding", this.hideAppMenuAnnotations);
+      aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hideAppMenuAnnotations);
+      aWindow.PanelUI.panel.addEventListener("popuphidden", this.onPanelHidden);
       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") {
@@ -1249,81 +1285,92 @@ this.UITour = {
       if (!toolbarButton || !toolbarButton.node) {
         return;
       }
 
       let panel = aWindow.document.getElementById("loop-notification-panel");
       panel.setAttribute("noautohide", true);
       if (panel.state != "open") {
         this.recreatePopup(panel);
+        this.availableTargetsCache.clear();
       }
 
       // 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);
+      panel.addEventListener("popuphidden", this.onPanelHidden);
+      panel.addEventListener("popuphiding", this.hideLoopPanelAnnotations);
     } else if (aMenuName == "searchEngines") {
       this.getTarget(aWindow, "searchProvider").then(target => {
         openMenuButton(target.node);
       }).catch(log.error);
     }
   },
 
   hideMenu: function(aWindow, aMenuName) {
     function closeMenuButton(aMenuBtn) {
       if (aMenuBtn && aMenuBtn.boxObject)
         aMenuBtn.boxObject.openMenu(false);
     }
 
     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) {
+  hideAnnotationsForPanel: function(aEvent, aTargetPositionCallback) {
     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)) {
+              !aTargetPositionCallback(aTarget)) {
             return;
           }
           hideMethod(win);
         }).catch(log.error);
       }
     });
     UITour.appMenuOpenForAnnotation.clear();
   },
 
-  onLoopPanelHidden: function(aEvent) {
+  hideAppMenuAnnotations: function(aEvent) {
+    UITour.hideAnnotationsForPanel(aEvent, UITour.targetIsInAppMenu);
+  },
+
+  hideLoopPanelAnnotations: function(aEvent) {
+    UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
+      // TODO: Bug 1104927 - Handle the conversation targets separately.
+      return aTarget.targetName.startsWith("loop-");
+    });
+  },
+
+  onPanelHidden: 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) {
--- a/browser/modules/test/browser_UITour_loop.js
+++ b/browser/modules/test/browser_UITour_loop.js
@@ -45,28 +45,72 @@ let tests = [
     }, "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 test_availableTargets(done) {
+    gContentAPI.showMenu("loop");
+    gContentAPI.getConfiguration("availableTargets", (data) => {
+      for (let targetName of ["loop-newRoom", "loop-roomList", "loop-signInUpLink"]) {
+        isnot(data.targets.indexOf(targetName), -1, targetName + " should exist");
+      }
+      done();
+    });
+  },
+  function test_hideMenuHidesAnnotations(done) {
+    let infoPanel = document.getElementById("UITourTooltip");
+    let highlightPanel = document.getElementById("UITourHighlightContainer");
+
+    gContentAPI.showMenu("loop", function menuCallback() {
+      gContentAPI.showHighlight("loop-roomList");
+      gContentAPI.showInfo("loop-newRoom", "Make a new room", "AKA. conversation");
+      UITour.getTarget(window, "loop-newRoom").then((target) => {
+        waitForPopupAtAnchor(infoPanel, target.node, Task.async(function* checkPanelIsOpen() {
+          isnot(loopPanel.state, "closed", "Loop panel should still be open");
+          ok(loopPanel.hasAttribute("noautohide"), "@noautohide should still be on the loop panel");
+          is(highlightPanel.getAttribute("targetName"), "loop-roomList", "Check highlight @targetname");
+          is(infoPanel.getAttribute("targetName"), "loop-newRoom", "Check info panel @targetname");
+
+          info("Close the loop menu and make sure the annotations inside disappear");
+          let hiddenPromises = [promisePanelElementHidden(window, infoPanel),
+                                promisePanelElementHidden(window, highlightPanel)];
+          gContentAPI.hideMenu("loop");
+          yield Promise.all(hiddenPromises);
+          isnot(infoPanel.state, "open", "Info panel should have automatically hid");
+          isnot(highlightPanel.state, "open", "Highlight panel should have automatically hid");
+          done();
+        }), "Info panel should be anchored to the new room button");
+      });
+    });
+  },
 ];
 
 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;
+  // The targets to highlight only appear after getting started is launched.
+  Services.prefs.setBoolPref("loop.gettingStarted.seen", true);
+  Services.prefs.setCharPref("loop.server", "http://localhost");
+  Services.prefs.setCharPref("services.push.serverURL", "ws://localhost/");
+
   registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("loop.gettingStarted.seen");
+    Services.prefs.clearUserPref("loop.server");
+    Services.prefs.clearUserPref("services.push.serverURL");
+
     // Copied from browser/components/loop/test/mochitest/head.js
     // Remove the iframe after each test. This also avoids mochitest complaining
     // about leaks on shutdown as we intentionally hold the iframe open for the
     // life of the application.
     let frameId = loopButton.getAttribute("notificationFrameId");
     let frame = document.getElementById(frameId);
     if (frame) {
       frame.remove();