Bug 1101654: First use tour for search UI. r=felipe, a=gavin
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 20 Nov 2014 15:27:10 -0800
changeset 226123 84717f538f7b55f05ee83b1dcccd3273a78bee2d
parent 226122 1e2510a0b8a04cf6fd8f0aa76cc7281344612512
child 226124 70114bfbbd7008fd4ac76c234c3dc6507845a871
push id4168
push userdtownsend@mozilla.com
push dateFri, 21 Nov 2014 22:04:19 +0000
treeherdermozilla-beta@84717f538f7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, gavin
bugs1101654
milestone34.0
Bug 1101654: First use tour for search UI. r=felipe, a=gavin * * * Bug 1101654: Tests for search first run UI. try: -b do -p linux,linux64,linux64-asan,macosx64,win32 -u reftest,reftest-ipc,reftest-no-accel,crashtest,crashtest-ipc,xpcshell,jsreftest,jetpack,marionette,mozmill,cppunit,jittests,jittest-1,jittest-2,mochitest-1,mochitest-2,mochitest-3,mochitest-4,mochitest-5,mochitest-bc,mochitest-dt,mochitest-o,mochitest-metro-chrome,web-platform-tests,plain-reftest-1,plain-reftest-2,plain-reftest-3,plain-reftest-4,plain-reftest-5,plain-reftest-6,plain-reftest-7,plain-reftest-8,plain-reftest-9,plain-reftest-10,plain-reftest-11,plain-reftest-12,plain-reftest-13,plain-reftest-14,plain-reftest-15,plain-reftest-16,jsreftest-1,jsreftest-2,jsreftest-3,jsreftest-4,jsreftest-5,jsreftest-6,mochitest-1,mochitest-2,mochitest-3,mochitest-4,mochitest-5,mochitest-6,mochitest-7,mochitest-8,mochitest-9,mochitest-10,mochitest-11,mochitest-12,mochitest-gl,robocop-1,robocop-2,robocop-3,robocop-4,robocop-5,crashtest-1,crashtest-2,xpcshell-1,xpcshell-2,xpcshell-3,crashtest-1,crashtest-2,crashtest-3,reftest-1,reftest-2,reftest-3,reftest-4,reftest-5,reftest-6,reftest-7,reftest-8,reftest-9,reftest-10,reftest-11,reftest-12,reftest-13,reftest-14,reftest-15,reftest-16,reftest-17,reftest-18,reftest-19,reftest-20,marionette-webapi,gaia-build,gaia-build-unit,gaia-linter,gaia-unit,gaia-ui-test,gaia-integration -t none
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_searchHighlight.js
browser/base/content/urlbarBindings.xml
browser/themes/linux/jar.mn
browser/themes/osx/jar.mn
browser/themes/shared/UITour.inc.css
browser/themes/shared/dots.png
browser/themes/shared/dots@2x.png
browser/themes/windows/jar.mn
testing/profiles/prefs_general.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -419,16 +419,19 @@ pref("browser.search.suggest.enabled", t
 
 pref("browser.search.showOneOffButtons", false);
 
 #ifdef MOZ_OFFICIAL_BRANDING
 // {moz:official} expands to "official"
 pref("browser.search.official", true);
 #endif
 
+// How many times to show the new search highlight
+pref("browser.search.highlightCount", 5);
+
 pref("browser.sessionhistory.max_entries", 50);
 
 // handle links targeting new windows
 // 1=current window/tab, 2=new window, 3=new tab in most recent window
 pref("browser.link.open_newwindow", 3);
 
 // handle external links (i.e. links opened from a different application)
 // default: use browser.link.open_newwindow
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1283,16 +1283,17 @@ var gBrowserInit = {
         return;
       }
 
       // Enable the Restore Last Session command if needed
       RestoreLastSessionObserver.init();
 
       SocialUI.init();
       TabView.init();
+      SearchHighlight.init();
 
       // Telemetry for master-password - we do this after 5 seconds as it
       // can cause IO if NSS/PSM has not already initialized.
       setTimeout(() => {
         if (window.closed) {
           return;
         }
         let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
@@ -3219,16 +3220,148 @@ const BrowserSearch = {
 
     let countId = this._getSearchEngineId(engine) + "." + source;
 
     let count = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
     count.add(countId);
   },
 };
 
+const SearchHighlight = {
+  eventsReady: false,
+  // The pref that controls how many times to show the highlight.
+  countPref: "browser.search.highlightCount",
+  // The current highlight to show.
+  currentPos: 0,
+  // Tracks if the popup closed very recently.
+  hideTimer: null,
+
+  // The list of highlights and the items in the panel to anchor them to.
+  highlights: [{
+    id: "SearchHighlight1",
+    anchor: "search-panel-one-offs"
+  }, {
+    id: "SearchHighlight2",
+    anchor: "searchbar-engine",
+  }],
+
+  init: function() {
+    this.panel = document.getElementById("PopupSearchAutoComplete");
+    this.panel.addEventListener("popupshowing", this.searchPanelShown.bind(this), false);
+  },
+
+  initEvents: function() {
+    if (this.eventsReady) {
+      return;
+    }
+
+    this.panel.addEventListener("popuphidden", this.searchPanelHidden.bind(this), false);
+
+    for (let highlight of this.highlights) {
+      highlight.panel = document.getElementById(highlight.id);
+      highlight.panel.addEventListener("popupshowing", this.disablePanelHiding.bind(this), false);
+      highlight.panel.addEventListener("popuphiding", this.enablePanelHiding.bind(this), false);
+
+      highlight.panel.querySelector("button").addEventListener("command", this.highlightButtonClicked.bind(this), false);
+    }
+
+    this.eventsReady = true;
+  },
+
+  get highlightPanel() {
+    return this.highlights[this.currentPos].panel;
+  },
+
+  showHighlight: function() {
+    // Check that all the events are setup.
+    this.initEvents();
+
+    // If a highlight is already showing then do nothing.
+    if (this.highlightPanel.state != "closed") {
+      return;
+    }
+
+    // Show the first highlight.
+    this.currentPos = 0;
+    this.showCurrentHighlight();
+  },
+
+  showCurrentHighlight: function() {
+    let highlight = this.highlights[this.currentPos];
+    let anchor = document.getAnonymousElementByAttribute(this.panel, "anonid", highlight.anchor);
+    highlight.panel.hidden = false;
+    highlight.panel.openPopup(anchor, "leftcenter topright");
+  },
+
+  searchPanelShown: function() {
+    let placement = CustomizableUI.getPlacementOfWidget("search-container");
+    if (placement.area == CustomizableUI.AREA_PANEL) {
+      // Opening a panel anchored to a panel anchored to a panel anchored to the
+      // window doesn't work very well
+      return;
+    }
+
+    if (!BrowserSearch.searchBar.value) {
+      // Don't show the panels when there is no text in the search box
+      return;
+    }
+
+    // If the panel was only very recently closed re-show the last highlight.
+    if (this.hideTimer) {
+      clearTimeout(this.hideTimer);
+      this.hideTimer = null;
+      this.showCurrentHighlight();
+      return;
+    }
+
+    // If the highlight has already been show the appropriate number of times
+    // do nothing.
+    let count = Services.prefs.getIntPref(this.countPref);
+    if (count <= 0)
+      return;
+
+    this.showHighlight();
+    Services.prefs.setIntPref(this.countPref, count - 1);
+  },
+
+  searchPanelHidden: function() {
+    if (this.highlightPanel.state == "closed") {
+      return;
+    }
+
+    this.highlightPanel.hidePopup();
+
+    // Set a flag when the panel was closed in the last short time.
+    this.hideTimer = setTimeout(() => {
+      this.hideTimer = null;
+    }, 500);
+  },
+
+  highlightButtonClicked: function() {
+    // When the button is clicked close the current highlight and open the next
+    // one.
+    this.highlightPanel.hidePopup();
+    this.currentPos++;
+    if (this.currentPos < this.highlights.length) {
+      this.showCurrentHighlight();
+    } else {
+      Services.prefs.setIntPref(this.countPref, 0);
+      this.currentPos = 0;
+    }
+  },
+
+  disablePanelHiding: function() {
+    this.panel.setAttribute("noautohide", "true");
+  },
+
+  enablePanelHiding: function() {
+    this.panel.setAttribute("noautohide", "false");
+  },
+};
+
 function FillHistoryMenu(aParent) {
   // Lazily add the hover listeners on first showing and never remove them
   if (!aParent.hasStatusListener) {
     // Show history item's uri in the status bar when hovering, and clear on exit
     aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
       // Only the current page should have the checked attribute, so skip it
       if (!aEvent.target.hasAttribute("checked"))
         XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -235,16 +235,51 @@
            hidden="true"
            noautofocus="true"
            noautohide="true"
            flip="none"
            consumeoutsideclicks="false"
            mousethrough="always">
       <box id="UITourHighlight"></box>
     </panel>
+    <!-- Used to highlight the new search experience -->
+    <panel id="SearchHighlight1"
+           class="SearchHighlight"
+           type="arrow"
+           hidden="true"
+           noautofocus="true"
+           noautohide="true"
+           orient="vertical"
+           align="stretch">
+      <label class="SearchHighlightTitle">One Click Searches</label>
+      <description class="SearchHighlightText" flex="1">Looking for something specific? Instantly search on any site using these options!</description>
+      <hbox class="SearchHighlightFooter" align="center">
+        <spacer class="dot filled"/>
+        <spacer class="dot"/>
+        <spacer flex="1"/>
+        <button label="Next"/>
+      </hbox>
+    </panel>
+    <panel id="SearchHighlight2"
+           class="SearchHighlight"
+           type="arrow"
+           hidden="true"
+           noautofocus="true"
+           noautohide="true"
+           orient="vertical"
+           align="stretch">
+      <label class="SearchHighlightTitle">Search Suggestions</label>
+      <description class="SearchHighlightText" flex="1">As-you-type suggestions from your default search engine help you to get to the right results quickly.</description>
+      <hbox class="SearchHighlightFooter" align="center">
+        <spacer class="dot"/>
+        <spacer class="dot filled"/>
+        <spacer flex="1"/>
+        <button label="Thanks!"/>
+      </hbox>
+    </panel>
 
     <panel id="abouthome-search-panel" orient="vertical" type="arrow">
       <hbox id="abouthome-search-panel-manage" class="abouthome-search-panel-item"
             onclick="openPreferences('paneSearch')">
         <label>&cmd_engineManager.label;</label>
       </hbox>
     </panel>
 
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -382,16 +382,18 @@ skip-if = true  # disabled until the tre
                 # it ever is (bug 480169)
 [browser_save_link-perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
 [browser_scope.js]
+[browser_searchHighlight.js]
+skip-if = os == "linux"
 [browser_searchSuggestionUI.js]
 support-files =
   searchSuggestionUI.html
   searchSuggestionUI.js
 [browser_selectTabAtIndex.js]
 skip-if = e10s # Bug ?????? - no idea! "Accel+9 selects expected tab - Got 0, expected 9"
 [browser_star_hsts.js]
 skip-if = e10s # Bug ?????? - timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_searchHighlight.js
@@ -0,0 +1,260 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const PREF = "browser.search.highlightCount";
+
+const searchBar = BrowserSearch.searchBar;
+const popup = document.getElementById("PopupSearchAutoComplete");
+const panel1 = document.getElementById("SearchHighlight1");
+const panel2 = document.getElementById("SearchHighlight2");
+
+let current = Services.prefs.getIntPref(PREF);
+registerCleanupFunction(() => {
+  Services.prefs.setIntPref(PREF, current);
+});
+
+// The highlight panel should be displayed a set number of times
+add_task(function* firstrun() {
+  yield promiseNewEngine(TEST_ENGINE_BASENAME);
+
+  ok(searchBar, "got search bar");
+  if (!searchBar.getAttribute("oneoffui"))
+    return; // This tests the one-off UI
+  searchBar.value = "foo";
+  searchBar.focus();
+
+  Services.prefs.setIntPref(PREF, 2);
+
+  let promise = promiseWaitForEvent(panel1, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise
+  ok(true, "Saw panel 1 show");
+
+  is(Services.prefs.getIntPref(PREF), 1, "Should have counted this show");
+
+  promise = promiseWaitForEvent(panel1, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw panel 1 hide");
+
+  clearTimer();
+
+  promise = promiseWaitForEvent(panel1, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise
+  ok(true, "Saw panel 1 show");
+
+  is(Services.prefs.getIntPref(PREF), 0, "Should have counted this show");
+
+  promise = promiseWaitForEvent(panel1, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw panel 1 hide");
+
+  clearTimer();
+
+  function listener() {
+    ok(false, "Should not have seen the pane show");
+  }
+  panel1.addEventListener("popupshowing", listener, false);
+
+  promise = promiseWaitForEvent(popup, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise;
+  ok(true, "Saw popup show");
+
+  promise = promiseWaitForEvent(popup, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw popup hide");
+
+  panel1.removeEventListener("popupshowing", listener, false);
+
+  clearTimer();
+});
+
+// Completing the tour should stop the popup from showing again
+add_task(function* dismiss() {
+  ok(searchBar, "got search bar");
+  if (!searchBar.getAttribute("oneoffui"))
+    return; // This tests the one-off UI
+  searchBar.value = "foo";
+  searchBar.focus();
+
+  Services.prefs.setIntPref(PREF, 200);
+
+  let promise = promiseWaitForEvent(panel1, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise
+  ok(true, "Saw panel 1 show");
+
+  promise = promiseWaitForEvent(panel2, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(panel1.querySelector("button"), {});
+  yield promise;
+  ok(true, "Saw panel 2 show");
+
+  promise = promiseWaitForEvent(panel2, "popuphidden");
+  EventUtils.synthesizeMouseAtCenter(panel2.querySelector("button"), {});
+  yield promise;
+  ok(true, "Saw panel 2 hide");
+
+  is(Services.prefs.getIntPref(PREF), 0, "Should have cleared the counter");
+
+  promise = promiseWaitForEvent(popup, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw popup hide");
+
+  clearTimer();
+
+  function listener() {
+    ok(false, "Should not have seen the pane show");
+  }
+  panel1.addEventListener("popupshowing", listener, false);
+
+  promise = promiseWaitForEvent(popup, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise;
+  ok(true, "Saw popup show");
+
+  promise = promiseWaitForEvent(popup, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw popup hide");
+
+  panel1.removeEventListener("popupshowing", listener, false);
+
+  clearTimer();
+});
+
+// The highlight panel should be re-displayed if the search popup closes and
+// opens quickly
+add_task(function* testRedisplay() {
+  ok(searchBar, "got search bar");
+  if (!searchBar.getAttribute("oneoffui"))
+    return; // This tests the one-off UI
+  searchBar.value = "foo";
+  searchBar.focus();
+
+  Services.prefs.setIntPref(PREF, 2);
+
+  let promise = promiseWaitForEvent(panel1, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise
+  ok(true, "Saw panel 1 show");
+
+  is(Services.prefs.getIntPref(PREF), 1, "Should have counted this show");
+
+  promise = promiseWaitForEvent(panel2, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(panel1.querySelector("button"), {});
+  yield promise;
+  ok(true, "Saw panel 2 show");
+
+  promise = promiseWaitForEvent(panel2, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw panel 2 hide");
+
+  promise = promiseWaitForEvent(panel2, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise;
+  ok(true, "Saw panel 2 show");
+
+  is(Services.prefs.getIntPref(PREF), 1, "Should not have counted this show");
+
+  promise = promiseWaitForEvent(panel2, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw panel 2 hide");
+
+  clearTimer();
+});
+
+// The highlight panel shouldn't be displayed if there is no text in the search
+// box
+add_task(function* testNoText() {
+  ok(searchBar, "got search bar");
+  if (!searchBar.getAttribute("oneoffui"))
+    return; // This tests the one-off UI
+  searchBar.value = "";
+  searchBar.focus();
+
+  Services.prefs.setIntPref(PREF, 2);
+
+  function listener() {
+    ok(false, "Should not have seen the pane show");
+  }
+  panel1.addEventListener("popupshowing", listener, false);
+
+  let button = document.getAnonymousElementByAttribute(searchBar,
+                                                       "anonid",
+                                                       "searchbar-search-button");
+  let promise = promiseWaitForEvent(popup, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(button, {});
+  yield promise;
+  ok(true, "Saw popup show");
+
+  promise = promiseWaitForEvent(popup, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw popup hide");
+
+  clearTimer();
+
+  promise = promiseWaitForEvent(popup, "popupshown");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  yield promise;
+  ok(true, "Saw popup show");
+
+  promise = promiseWaitForEvent(popup, "popuphidden");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  yield promise;
+  ok(true, "Saw popup hide");
+
+  panel1.removeEventListener("popupshowing", listener, false);
+
+  clearTimer();
+});
+
+function clearTimer() {
+  // Clear the timeout
+  clearTimeout(SearchHighlight.hideTimer);
+  SearchHighlight.hideTimer = null;
+}
+
+function promiseWaitForEvent(node, type, capturing) {
+  return new Promise((resolve) => {
+    node.addEventListener(type, function listener(event) {
+      node.removeEventListener(type, listener, capturing);
+      resolve(event);
+    }, capturing);
+  });
+}
+
+function promiseNewEngine(basename) {
+  return new Promise((resolve, reject) => {
+    info("Waiting for engine to be added: " + basename);
+    Services.search.init({
+      onInitComplete: function() {
+        let url = getRootDirectory(gTestPath) + basename;
+        let current = Services.search.currentEngine;
+        Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+          onSuccess: function (engine) {
+            info("Search engine added: " + basename);
+            Services.search.currentEngine = engine;
+            registerCleanupFunction(() => {
+              Services.search.currentEngine = current;
+              Services.search.removeEngine(engine);
+            });
+            resolve(engine);
+          },
+          onError: function (errCode) {
+            ok(false, "addEngine failed with error code " + errCode);
+            reject();
+          },
+        });
+      }
+    });
+  });
+}
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -894,17 +894,17 @@
     </binding>
 
   <!-- Note: this binding is applied to the autocomplete popup used in the Search bar -->
   <binding id="browser-search-autocomplete-result-popup" extends="chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup">
     <resources>
       <stylesheet src="chrome://browser/skin/searchbar.css"/>
     </resources>
     <content ignorekeys="true" level="top" consumeoutsideclicks="false">
-      <xul:hbox xbl:inherits="collapsed=showonlysettings"
+      <xul:hbox xbl:inherits="collapsed=showonlysettings" anonid="searchbar-engine"
                 class="search-panel-header search-panel-current-engine">
         <xul:image class="searchbar-engine-image" xbl:inherits="src"/>
         <xul:label anonid="searchbar-engine-name"/>
       </xul:hbox>
       <xul:tree anonid="tree" flex="1"
                 class="autocomplete-tree plain search-panel-tree"
                 hidecolumnpicker="true" seltype="single">
         <xul:treecols anonid="treecols">
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -19,16 +19,18 @@ browser.jar:
   skin/classic/browser/aboutSyncTabs.css
 #endif
   skin/classic/browser/aboutTabCrashed.css
   skin/classic/browser/actionicon-tab.png
 * skin/classic/browser/browser.css
 * skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/click-to-play-warning-stripes.png
 * skin/classic/browser/content-contextmenu.svg
+  skin/classic/browser/dots.png                             (../shared/dots.png)
+  skin/classic/browser/dots@2x.png                          (../shared/dots@2x.png)
 * skin/classic/browser/engineManager.css
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/identity.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https-ev.png
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -19,16 +19,18 @@ browser.jar:
 #endif
   skin/classic/browser/aboutTabCrashed.css
   skin/classic/browser/actionicon-tab.png
   skin/classic/browser/actionicon-tab@2x.png
 * skin/classic/browser/browser.css                          (browser.css)
 * skin/classic/browser/browser-lightweightTheme.css
   skin/classic/browser/click-to-play-warning-stripes.png
 * skin/classic/browser/content-contextmenu.svg
+  skin/classic/browser/dots.png                             (../shared/dots.png)
+  skin/classic/browser/dots@2x.png                          (../shared/dots@2x.png)
 * skin/classic/browser/engineManager.css                    (engineManager.css)
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-16@2x.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/Geolocation-64@2x.png
   skin/classic/browser/identity.png
   skin/classic/browser/identity@2x.png
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -142,8 +142,56 @@
   color: white;
   padding-left: 30px;
   padding-right: 30px;
 }
 
 #UITourTooltipButtons > button.button-primary:not(:active):hover {
   background-color: rgb(105,173,61);
 }
+
+.SearchHighlight {
+  -moz-margin-end: 6px;
+  font-size: 110%;
+  width: 225px;
+}
+
+.SearchHighlight label,
+.SearchHighlight description {
+  color: #535353;
+  margin: 0 0 8px 0;
+  padding: 0;
+}
+
+.SearchHighlightTitle {
+  font-weight: bold;
+}
+
+.SearchHighlight .dot {
+  width: 7px;
+  height: 7px;
+  background-image: -moz-image-rect(url("chrome://browser/skin/dots.png"), 0, 100%, 100%, 9);
+  background-size: 7px;
+  background-position: center center;
+  background-repeat: no-repeat;
+  -moz-margin-end: 2px;
+}
+
+.SearchHighlight .dot.filled {
+  background-image: -moz-image-rect(url("chrome://browser/skin/dots.png"), 0, 7, 100%, 0);
+}
+
+.SearchHighlight button {
+  margin: 0;
+  /* On some platforms clicking the button will steal focus from the search box
+     causing the popup to close. */
+  -moz-user-focus: ignore;
+}
+
+@media not all and (max-resolution: 1dppx) {
+  .SearchHighlight .dot {
+    background-image: -moz-image-rect(url("chrome://browser/skin/dots@2x.png"), 0, 100%, 100%, 18);
+  }
+
+  .SearchHighlight .dot.filled {
+    background-image: -moz-image-rect(url("chrome://browser/skin/dots@2x.png"), 0, 14, 100%, 0);
+  }
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e856fd0ab2e7f4c36a846c678cd9e539a46c92cf
GIT binary patch
literal 496
zc$@+70T2F(P)<h;3K|Lk000e1NJLTq000mG000LF1^@s60U*sn0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzl1W5CRCwAnkxfeiQ51&HIHTgnFq(t2
z5Er&d(;$Nwh%m@sP!Q;{S_B3DfpXVQD(Vk}%M7I4B`qp~n1y4K3peV@g)$QkgX0V;
zc5c+&+|T!U-*Ya-^E{-}X)cjSoP|Q6)n2dn*=#n?OQn)FpU>fcWV6|YWHNb!Z&vWl
zSG``(Rjbu+IueQa5pWrc#ZCl4SY%mtgQjVrR;xV{A;U1>@pvGU$=sn?O4oIn<G5{~
z&$r%gx1Xs(p>Q1zhjXGRg3INC@puf9Btf^^-Elgd4+KI{)G<P?EX(32lZkG(+gZF*
zOw+6|fk0rBltIM9;Si7vWIq~>w!B{NKVWY#7?cs9V>%!+o|RFXVpLUqce~wd#F>c6
ztSAbg;8oK!_ygRN1lSXnBtUo8;c)DtwnD|@aX&WbA{veE697rkYPD{$v;&Oz3qS$n
z^Z5!oiiTmlp<xQeQn6S(q>*YO{l#XpF}OB5olY5l;Z&AoYdW36Y&Ij2Nm)<Wyrp0;
mxZLmeKkyfh8jXhfBftO<-MX_cvj}zo0000<MNUMnLSTa0irY*8
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5b9089bda016397d100545bbb5564c1255e419c7
GIT binary patch
literal 519
zc$@(T0{H!jP)<h;3K|Lk000e1NJLTq001BW000gM1^@s6PTU@{00001b5ch_0Itp)
z=>Px$!AV3xR5%gERJ(2hF%Yy*JUf&w5*3swQqe~!pFx*L0TmSah!jw9MIK$0uZW`b
zR4JgNLrNFXp#uqLXYpE=bB>6F5=(IQ%-rnSw`-;TdFUX%9*@VDTI&nKPb@bz#ys|V
zy$4#GKp$J~?c)*{jYh}ia`}mZxAR;niPtR4I{kkCBaavCw>|~?qL$#5O2sr94PC3%
zm|8BEYCfME3f_i>z;pV=PQv++X0hFFZ`SMe8_iXzR60wN<Q5zBefR|E*sqAZBf_*=
zEnO@Y@s1mt%|=Zo6C|O@^qM}$y(7Je_z<*MtyZUl!Qj)Q)o?gGs#dG-gw@3-fxv!q
z)oeDu1qY9W!1-Ft!y!J{c^Dj8VBjzpEhj*0E;u=M2*TlUQk<a#Yv$zG^8_BpT1g;J
z`zM$rFhw*JTx@t8Ya=@5P;e2Ri_vnd<pj{@08#BIxNrai2VAtF#^+G&D7a7q44ki(
zdqAgY`eIwepy1@2du^z37j(PbAKOrTC`fJ_5?=Rx3N}l$SO@)yexJ=|7IqH~4h<o(
zI{;$mgU!%xpXpw4CM>WQ8njr;31E(~_Z7O6NB<Ay6Af@M;tOsv1wb2UwcP*!002ov
JPDHLkV1g~k;|c%(
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -21,16 +21,18 @@ browser.jar:
         skin/classic/browser/aboutSyncTabs.css
 #endif
         skin/classic/browser/aboutTabCrashed.css
         skin/classic/browser/actionicon-tab.png
 *       skin/classic/browser/browser.css
 *       skin/classic/browser/browser-lightweightTheme.css
         skin/classic/browser/click-to-play-warning-stripes.png
 *       skin/classic/browser/content-contextmenu.svg
+        skin/classic/browser/dots.png                                (../shared/dots.png)
+        skin/classic/browser/dots@2x.png                             (../shared/dots@2x.png)
 *       skin/classic/browser/engineManager.css
         skin/classic/browser/fullscreen-darknoise.png
         skin/classic/browser/Geolocation-16.png
         skin/classic/browser/Geolocation-64.png
         skin/classic/browser/Info.png
         skin/classic/browser/identity.png
         skin/classic/browser/identity-icons-generic.png
         skin/classic/browser/identity-icons-https.png
@@ -448,16 +450,18 @@ browser.jar:
         skin/classic/aero/browser/aboutTabCrashed.css
         skin/classic/aero/browser/aboutWelcomeBack.css               (../shared/aboutWelcomeBack.css)
         skin/classic/aero/browser/actionicon-tab.png
 *       skin/classic/aero/browser/browser.css                        (browser-aero.css)
 *       skin/classic/aero/browser/browser-lightweightTheme.css
         skin/classic/aero/browser/click-to-play-warning-stripes.png
 *       skin/classic/aero/browser/content-contextmenu.svg
 *       skin/classic/aero/browser/engineManager.css
+        skin/classic/aero/browser/dots.png                           (../shared/dots.png)
+        skin/classic/aero/browser/dots@2x.png                        (../shared/dots@2x.png)
         skin/classic/aero/browser/fullscreen-darknoise.png
         skin/classic/aero/browser/Geolocation-16.png
         skin/classic/aero/browser/Geolocation-64.png
         skin/classic/aero/browser/Info.png                           (Info-aero.png)
         skin/classic/aero/browser/identity.png                       (identity-aero.png)
         skin/classic/aero/browser/identity-icons-generic.png
         skin/classic/aero/browser/identity-icons-https.png
         skin/classic/aero/browser/identity-icons-https-ev.png
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -246,8 +246,11 @@ user_pref("loop.throttled", false);
 user_pref("loop.oauth.google.URL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=");
 user_pref("loop.oauth.google.getContactsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=contacts");
 user_pref("loop.oauth.google.getGroupsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=groups");
 user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");
 
 // Ensure UITour won't hit the network
 user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");
 user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
+
+// Don't show the search first run UI by default
+user_pref("browser.search.highlightCount", 0);