Bug 1355443 - Speculatively connect to the current search engine. r=mak draft
authorEvelyn Hung <jj.evelyn@gmail.com>
Wed, 26 Jul 2017 17:35:13 +0800
changeset 616506 e418928ec792e69f555df8e8fab4372f3e6a8178
parent 614808 dcfb58fcb6dd8f6474eed6520ba6272dedded393
child 639497 079efe0fe4af60c2f15e07cf857f148abef850a7
push id70710
push userbmo:ehung@mozilla.com
push dateThu, 27 Jul 2017 04:24:10 +0000
reviewersmak
bugs1355443
milestone56.0a1
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);
 
@@ -1071,22 +1077,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();
@@ -1368,28 +1381,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>
 
@@ -2112,21 +2124,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>
@@ -2140,28 +2147,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