Bug 1355443 - Speculatively connect to the current search engine. r=mak
authorEvelyn Hung <jj.evelyn@gmail.com>
Wed, 26 Jul 2017 17:35:13 +0800
changeset 419964 282362cd36e35256d49247ad027ca8af6cf9c8af
parent 419963 72eae6a2f5d20ce9c20737a8be2efc501462fc00
child 419965 f65f933ecdce1161bed0a3ef3cdf02e4efb197be
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1355443
milestone56.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 1355443 - Speculatively connect to the current search engine. r=mak when the first result is "Search with ...", we can preconnect to the search engine to speed up the possible search query. MozReview-Commit-ID: 1K1Vp5gVwmR
browser/base/content/test/urlbar/browser.ini
browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
browser/base/content/test/urlbar/head.js
browser/base/content/test/urlbar/searchSuggestionEngine2.xml
browser/base/content/urlbarBindings.xml
testing/profiles/prefs_general.js
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -106,16 +106,20 @@ subsuite = clipboard
 [browser_urlbar_blanking.js]
 support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_searchsettings.js]
 [browser_urlbar_search_speculative_connect.js]
+[browser_urlbar_search_speculative_connect_engine.js]
+support-files =
+  searchSuggestionEngine2.xml
+  searchSuggestionEngine.sjs
 [browser_urlbar_stop_pending.js]
 support-files =
   slow-page.sjs
 [browser_urlbar_remoteness_switch.js]
 run-if = e10s
 [browser_urlHighlight.js]
 [browser_wyciwyg_urlbarCopying.js]
 subsuite = clipboard
--- a/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
@@ -1,82 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 // This test ensures that we setup a speculative network
 // connection for autoFilled values.
 
 let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
 let gHttpServer = null;
 let gScheme = "http";
 let gHost = "localhost"; // 'localhost' by default.
 let gPort = -1;
+let gPrivateWin = null;
+let gIsSpeculativeConnected = false;
 
 add_task(async function setup() {
   if (!gHttpServer) {
     gHttpServer = new HttpServer();
     try {
       gHttpServer.start(gPort);
       gPort = gHttpServer.identity.primaryPort;
       gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
     } catch (ex) {
       info("We can't launch our http server successfully.")
     }
   }
   is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
 
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.autoFill", true],
-          ["browser.urlbar.speculativeConnection.enabled", true],
+          // Turn off speculative connect to the search engine.
+          ["browser.search.suggest.enabled", false],
+          ["browser.urlbar.speculativeConnect.enabled", true],
           // In mochitest this number is 0 by default but we have to turn it on.
           ["network.http.speculative-parallel-limit", 6],
           // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
           // networking problem.
           ["network.dns.disableIPv6", true]],
   });
 
   await PlacesTestUtils.addVisits([{
     uri: `${gScheme}://${gHost}:${gPort}`,
     title: "test visit for speculative connection",
     transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
   }]);
 
+  gPrivateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
+  is(PrivateBrowsingUtils.isWindowPrivate(gPrivateWin), true, "A private window created.");
+
   // Bug 764062 - we can't get port number from autocomplete result, so we have to mock
   // this function to add it manually.
   let oldSpeculativeConnect = gURLBar.popup.maybeSetupSpeculativeConnect.bind(gURLBar.popup);
-  gURLBar.popup.maybeSetupSpeculativeConnect = (uriString) => {
+  let newSpeculativeConnect = (uriString) => {
+    gIsSpeculativeConnected = true;
     info(`Original uri is ${uriString}`);
     let newUriString = uriString.substr(0, uriString.length - 1) +
                        ":" + gPort + "/";
     info(`New uri is ${newUriString}`);
     oldSpeculativeConnect(newUriString);
   };
+  gURLBar.popup.maybeSetupSpeculativeConnect = newSpeculativeConnect;
+  gPrivateWin.gURLBar.popup.maybeSetupSpeculativeConnect = newSpeculativeConnect;
 
   registerCleanupFunction(async function() {
-    await PlacesTestUtils.clearHistory();
+    await PlacesUtils.history.clear();
     gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
+    gPrivateWin.gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
     gHttpServer.identity.remove(gScheme, gHost, gPort);
     gHttpServer.stop(() => {
       gHttpServer = null;
     });
+    await BrowserTestUtils.closeWindow(gPrivateWin);
   });
 });
 
+const test = {
+  search: gHost.substr(0, 2),
+  autofilledValue: `${gHost}/`
+};
+
 add_task(async function autofill_tests() {
-  const test = {
-    search: gHost.substr(0, 2),
-    autofilledValue: `${gHost}/`
-  };
-
+  gIsSpeculativeConnected = false;
   info(`Searching for '${test.search}'`);
   await promiseAutocompleteResultPopup(test.search, window, true);
   is(gURLBar.inputField.value, test.autofilledValue,
      `Autofilled value is as expected for search '${test.search}'`);
-
-  await BrowserTestUtils.waitForCondition(() => {
-    if (gHttpServer) {
-      is(gHttpServer.connectionNumber, 1,
-         `${gHttpServer.connectionNumber} speculative connection has been setup.`)
-      return gHttpServer.connectionNumber == 1;
-    }
-    return false;
-  }, "Waiting for connection setup");
+  is(gIsSpeculativeConnected, true, "Speculative connection should be called");
+  await promiseSpeculativeConnection(gHttpServer);
 });
 
+add_task(async function privateContext_test() {
+  info("In private context.");
+  gIsSpeculativeConnected = false;
+  info(`Searching for '${test.search}'`);
+  await promiseAutocompleteResultPopup(test.search, gPrivateWin, true);
+  is(gPrivateWin.gURLBar.inputField.value, test.autofilledValue,
+     `Autofilled value is as expected for search '${test.search}'`);
+  is(gIsSpeculativeConnected, false, "Speculative connection shouldn't be called");
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect_engine.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This test ensures that we setup a speculative network connection to
+// current search engine if the first result is 'searchengine'.
+
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+let gHttpServer = null;
+let gScheme = "http";
+let gHost = "localhost"; // 'localhost' by default.
+let gPort = 20709; // the port number must be identical to what we said in searchSuggestionEngine2.xml
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine2.xml";
+
+add_task(async function setup() {
+  if (!gHttpServer) {
+    gHttpServer = new HttpServer();
+    try {
+      gHttpServer.start(gPort);
+      gPort = gHttpServer.identity.primaryPort;
+      gHttpServer.identity.setPrimary(gScheme, gHost, gPort);
+    } catch (ex) {
+      info("We can't launch our http server successfully.")
+    }
+  }
+  is(gHttpServer.identity.has(gScheme, gHost, gPort), true, "make sure we have this domain listed");
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", true],
+          // Turn off speculative connect to the search engine.
+          ["browser.search.suggest.enabled", true],
+          ["browser.urlbar.suggest.searches", true],
+          ["browser.urlbar.speculativeConnect.enabled", true],
+          // In mochitest this number is 0 by default but we have to turn it on.
+          ["network.http.speculative-parallel-limit", 6],
+          // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
+          // networking problem.
+          ["network.dns.disableIPv6", true]],
+  });
+
+  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let oldCurrentEngine = Services.search.currentEngine;
+  Services.search.currentEngine = engine;
+
+  registerCleanupFunction(async function() {
+    await PlacesUtils.history.clear();
+    Services.search.currentEngine = oldCurrentEngine;
+    gHttpServer.identity.remove(gScheme, gHost, gPort);
+    gHttpServer.stop(() => {
+      gHttpServer = null;
+    });
+  });
+});
+
+add_task(async function autofill_tests() {
+  info("Searching for 'foo'");
+  await promiseAutocompleteResultPopup("foo", window, true);
+  // Check if the first result is with type "searchengine"
+  let controller = gURLBar.popup.input.controller;
+  let style = controller.getStyleAt(0);
+  is(style.includes("searchengine"), true, "The first result type is searchengine");
+  await promiseSpeculativeConnection(gHttpServer);
+});
+
--- a/browser/base/content/test/urlbar/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -230,8 +230,19 @@ function promisePageActionViewShown() {
     gPageActionPanel.addEventListener("ViewShown", (event) => {
       let target = event.originalTarget;
       window.setTimeout(() => {
         resolve(target);
       }, 5000);
     }, { once: true });
   });
 }
+
+function promiseSpeculativeConnection(httpserver) {
+  return BrowserTestUtils.waitForCondition(() => {
+    if (httpserver) {
+      is(httpserver.connectionNumber, 1,
+         `${httpserver.connectionNumber} speculative connection has been setup.`)
+      return httpserver.connectionNumber == 1;
+    }
+    return false;
+  }, "Waiting for connection setup");
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<!-- Redirect the actual search request to the test-server because of proxy restriction -->
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/urlbar/searchSuggestionEngine.sjs?{searchTerms}"/>
+<!-- Redirect speculative connect to a local http server we run for this test -->
+<Url type="text/html" method="GET" template="http://localhost:20709/" rel="searchform">
+  <Param name="terms" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -71,19 +71,24 @@ file, You can obtain one at http://mozil
                                 .getService(Components.interfaces.nsIPrefService)
                                 .getBranch("browser.urlbar.");
         this._prefs.addObserver("", this);
 
         this._defaultPrefs = Components.classes["@mozilla.org/preferences-service;1"]
                                        .getService(Components.interfaces.nsIPrefService)
                                        .getDefaultBranch("browser.urlbar.");
 
+        Services.prefs.addObserver("browser.search.suggest.enabled", this);
+        this.browserSearchSuggestEnabled = Services.prefs.getBoolPref("browser.search.suggest.enabled");
+
         this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
         this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
         this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
+        this.speculativeConnectEnabled = this._prefs.getBoolPref("speculativeConnect.enabled");
+        this.urlbarSearchSuggestEnabled = this._prefs.getBoolPref("suggest.searches");
         this.timeout = this._prefs.getIntPref("delay");
         this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
         this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
         this.inputField.addEventListener("paste", this);
         this.inputField.addEventListener("mousedown", this);
         this.inputField.addEventListener("mousemove", this);
         this.inputField.addEventListener("mouseout", this);
@@ -129,16 +134,17 @@ file, You can obtain one at http://mozil
         // to handle cases like backspace, autofill or repeated searches.
         // Ensure to clear those internal caches when switching tabs.
         gBrowser.tabContainer.addEventListener("TabSelect", this);
       ]]></constructor>
 
       <destructor><![CDATA[
         this._prefs.removeObserver("", this);
         this._prefs = null;
+        Services.prefs.removeObserver("browser.search.suggest.enabled", this);
         this.inputField.controllers.removeController(this._copyCutController);
         this.inputField.removeEventListener("paste", this);
         this.inputField.removeEventListener("mousedown", this);
         this.inputField.removeEventListener("mousemove", this);
         this.inputField.removeEventListener("mouseout", this);
         this.inputField.removeEventListener("overflow", this);
         this.inputField.removeEventListener("underflow", this);
 
@@ -1077,22 +1083,29 @@ file, You can obtain one at http://mozil
                 this.completeDefaultIndex = this._prefs.getBoolPref(aData);
                 break;
               case "delay":
                 this.timeout = this._prefs.getIntPref(aData);
                 break;
               case "formatting.enabled":
                 this._formattingEnabled = this._prefs.getBoolPref(aData);
                 break;
+              case "speculativeConnect.enabled":
+                this.speculativeConnectEnabled = this._prefs.getBoolPref(aData);
+                break;
+              case "browser.search.suggest.enabled":
+                this.browserSearchSuggestEnabled = Services.prefs.getBoolPref(aData);
+                break;
               case "suggest.searches":
+                this.urlbarSearchSuggestEnabled = this._prefs.getBoolPref(aData);
               case "userMadeSearchSuggestionsChoice":
                 // Mirror the value for future use, see the comment in the
                 // binding's constructor.
                 this._prefs.setBoolPref("searchSuggestionsChoice",
-                  this._prefs.getBoolPref("suggest.searches"));
+                  this.urlbarSearchSuggestEnabled);
                 // Clear the cached value to allow changing conditions in tests.
                 delete this._whichSearchSuggestionsNotification;
                 break;
               case "trimURLs":
                 this._mayTrimURLs = this._prefs.getBoolPref(aData);
                 break;
               case "oneOffSearches":
                 this._enableOrDisableOneOffSearches();
@@ -1376,28 +1389,27 @@ file, You can obtain one at http://mozil
         <getter><![CDATA[
           // Once we return "none" once, we'll always return "none".
           // If available, use the cached value, rather than running all of the
           // checks again at every locationbar focus.
           if (this._whichSearchSuggestionsNotification) {
             return this._whichSearchSuggestionsNotification;
           }
 
-          if (Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
-              !this.inPrivateContext &&
+          if (this.browserSearchSuggestEnabled && !this.inPrivateContext &&
               // In any case, if the user made a choice we should not nag him.
               !this._userMadeSearchSuggestionsChoice) {
             let enabledByDefault = this._defaultPrefs.getBoolPref("suggest.searches");
             if (!enabledByDefault &&
                 this._prefs.getIntPref("daysBeforeHidingSuggestionsPrompt")) {
               return "opt-in";
             }
             if (enabledByDefault &&
                 // Has not been switched off.
-                this._prefs.getBoolPref("suggest.searches") &&
+                this.urlbarSearchSuggestEnabled &&
                 this._prefs.getIntPref("timesBeforeHidingSuggestionsHint")) {
               return "opt-out";
             }
           }
           return this._whichSearchSuggestionsNotification = "none";
         ]]></getter>
       </property>
 
@@ -2119,21 +2131,16 @@ file, You can obtain one at http://mozil
             return parts.filter(str => str).join(" ");
           ]]>
         </body>
       </method>
 
       <method name="maybeSetupSpeculativeConnect">
         <parameter name="aUriString"/>
         <body><![CDATA[
-          // We shouldn't leak autocomplete result in the private context.
-          if (!Services.prefs.getBoolPref("browser.urlbar.speculativeConnect.enabled") ||
-              this.input.inPrivateContext) {
-            return;
-          }
           try {
             let uri = makeURI(aUriString);
             Services.io.speculativeConnect2(uri, gBrowser.contentPrincipal, null);
           } catch (ex) {
             // Can't setup speculative connection for this uri string for some
             // reason, just ignore it.
           }
         ]]></body>
@@ -2147,28 +2154,39 @@ file, You can obtain one at http://mozil
             if (this.selectedIndex == -1 && this._isFirstResultHeuristic) {
               // Don't fire DOMMenuItemActive so that screen readers still see
               // the input as being focused.
               this.richlistbox.suppressMenuItemEvent = true;
               this.input.controller.setInitiallySelectedIndex(0);
               this.richlistbox.suppressMenuItemEvent = false;
             }
             // If this is the first time we get the result from the current
-            // search, and the result is an "autofill" result, that means it's
-            // the site that user frequently visits. Then we could speculatively
-            // connect to this site as a performance optimization.
+            // search and we are not in the private context, we can speculatively
+            // connect to the intended site as a performance optimization.
             if (!this.input.gotResultForCurrentQuery &&
-                this.input.mController.matchCount > 0 &&
-                this.input.mController.getStyleAt(0).includes("autofill")) {
-              let uri = this.input.mController.getFinalCompleteValueAt(0);
-              // "http" will be stripped out, but other scheme won't.
-              if (!uri.includes("://")) {
-                uri = "http://" + uri;
+                this.input.speculativeConnectEnabled &&
+                !this.input.inPrivateContext &&
+                this.input.mController.matchCount > 0) {
+              let firstStyle = this.input.mController.getStyleAt(0);
+              if (firstStyle.includes("autofill")) {
+                let uri = this.input.mController.getFinalCompleteValueAt(0);
+                // "http" will be stripped out, but other scheme won't.
+                if (!uri.includes("://")) {
+                  uri = "http://" + uri;
+                }
+                this.maybeSetupSpeculativeConnect(uri);
+              } else if (firstStyle.includes("searchengine") &&
+                         this.input.browserSearchSuggestEnabled &&
+                         this.input.urlbarSearchSuggestEnabled) {
+                // Preconnect to the current search engine only if the search
+                // suggestions are enabled.
+                let engine = Services.search.currentEngine;
+                engine.speculativeConnect({window,
+                                           originAttributes: gBrowser.contentPrincipal.originAttributes});
               }
-              this.maybeSetupSpeculativeConnect(uri);
             }
 
             // When a result is present the footer should always be visible.
             this.footer.collapsed = false;
 
             this.input.gotResultForCurrentQuery = true;
             this.input.maybeReplayDeferredKeyEvents();
           ]]>
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -2,17 +2,17 @@
 /* globals user_pref */
 user_pref("browser.console.showInPanel", true);
 user_pref("browser.dom.window.dump.enabled", true);
 user_pref("browser.firstrun.show.localepicker", false);
 user_pref("browser.firstrun.show.uidiscovery", false);
 user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.homepage
 user_pref("browser.search.suggest.timeout", 10000); // use a 10s suggestion timeout in tests
 user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
-user_pref("browser.urlbar.speculativeConnection.enabled", false);
+user_pref("browser.urlbar.speculativeConnect.enabled", false);
 user_pref("dom.allow_scripts_to_close_windows", true);
 user_pref("dom.disable_open_during_load", false);
 user_pref("dom.experimental_forms", true); // on for testing
 user_pref("dom.forms.number", true); // on for testing
 user_pref("dom.forms.color", true); // on for testing
 user_pref("dom.forms.datetime", true); // on for testing
 user_pref("dom.forms.datetime.others", true); // on for testing
 user_pref("dom.max_script_run_time", 0); // no slow script dialogs