Bug 1348275 - speculative connect to an autocomplete url. r=mak draft
authorEvelyn Hung <jj.evelyn@gmail.com>
Mon, 26 Jun 2017 00:24:42 +0800
changeset 602798 a4bf32ccfc240cadfffe208c94d8ee11721606e5
parent 601524 217b7fcf58944f927118b465769faeb1e613130a
child 635713 ab01be244a4899d44b33a87e263cbead443c9a03
push id66551
push userbmo:ehung@mozilla.com
push dateFri, 30 Jun 2017 18:40:42 +0000
reviewersmak
bugs1348275
milestone56.0a1
Bug 1348275 - speculative connect to an autocomplete url. r=mak When we get the usre's frequent visting site from UnifiedComplete.js, and then open a network connection for it before the user hits the enter key. MozReview-Commit-ID: 36moBeeUnyZ
browser/app/profile/firefox.js
browser/base/content/test/urlbar/browser.ini
browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
browser/base/content/urlbarBindings.xml
netwerk/test/httpserver/httpd.js
testing/profiles/prefs_general.js
toolkit/modules/Services.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -294,16 +294,17 @@ pref("browser.urlbar.clickSelectsAll", t
 pref("browser.urlbar.doubleClickSelectsAll", true);
 #else
 pref("browser.urlbar.doubleClickSelectsAll", false);
 #endif
 
 // Control autoFill behavior
 pref("browser.urlbar.autoFill", true);
 pref("browser.urlbar.autoFill.typed", true);
+pref("browser.urlbar.speculativeConnect.enabled", true);
 
 // 0: Match anywhere (e.g., middle of words)
 // 1: Match on word boundaries and then try matching anywhere
 // 2: Match only on word boundaries (e.g., after / or .)
 // 3: Match at the beginning of the url or title
 pref("browser.urlbar.matchBehavior", 1);
 pref("browser.urlbar.filter.javascript", true);
 
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -105,16 +105,17 @@ subsuite = clipboard
 [browser_urlbar_canonize_on_autofill.js]
 [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_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
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_search_speculative_connect.js
@@ -0,0 +1,82 @@
+"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;
+
+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],
+          // 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,
+  }]);
+
+  // 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) => {
+    info(`Original uri is ${uriString}`);
+    let newUriString = uriString.substr(0, uriString.length - 1) +
+                       ":" + gPort + "/";
+    info(`New uri is ${newUriString}`);
+    oldSpeculativeConnect(newUriString);
+  };
+
+  registerCleanupFunction(async function() {
+    await PlacesTestUtils.clearHistory();
+    gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
+    gHttpServer.identity.remove(gScheme, gHost, gPort);
+    gHttpServer.stop(() => {
+      gHttpServer = null;
+    });
+  });
+});
+
+add_task(async function autofill_tests() {
+  const test = {
+    search: gHost.substr(0, 2),
+    autofilledValue: `${gHost}/`
+  };
+
+  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");
+});
+
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2109,28 +2109,60 @@ file, You can obtain one at http://mozil
               parts.push(this._bundle.GetStringFromName(type + "ResultLabel"));
             } catch (e) {}
 
             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>
+      </method>
+
       <method name="onResultsAdded">
         <body>
           <![CDATA[
             // If nothing is selected yet, select the first result if it is a
             // pre-selected "heuristic" result.  (See UnifiedComplete.js.)
             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.
+            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.maybeSetupSpeculativeConnect(uri);
+            }
 
             // When a result is present the footer should always be visible.
             this.footer.collapsed = false;
 
             this.input.gotResultForCurrentQuery = true;
             this.input.maybeReplayDeferredKeyEvents();
           ]]>
         </body>
--- a/netwerk/test/httpserver/httpd.js
+++ b/netwerk/test/httpserver/httpd.js
@@ -678,16 +678,21 @@ nsHttpServer.prototype =
   //
   // see nsIHttpServer.registerContentType
   //
   registerContentType: function(ext, type)
   {
     this._handler.registerContentType(ext, type);
   },
 
+  get connectionNumber()
+  {
+    return this._connectionGen;
+  },
+
   //
   // see nsIHttpServer.serverIdentity
   //
   get identity()
   {
     return this._identity;
   },
 
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -2,16 +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("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
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -55,31 +55,36 @@ XPCOMUtils.defineLazyGetter(Services, "m
 });
 
 XPCOMUtils.defineLazyGetter(Services, "ppmm", () => {
   return Cc["@mozilla.org/parentprocessmessagemanager;1"]
            .getService(Ci.nsIMessageBroadcaster)
            .QueryInterface(Ci.nsIProcessScriptLoader);
 });
 
+XPCOMUtils.defineLazyGetter(Services, "io", () => {
+  return Cc["@mozilla.org/network/io-service;1"]
+           .getService(Ci.nsIIOService2)
+           .QueryInterface(Ci.nsISpeculativeConnect);
+});
+
 var initTable = [
   ["androidBridge", "@mozilla.org/android/bridge;1", "nsIAndroidBridge",
    AppConstants.platform == "android"],
   ["appShell", "@mozilla.org/appshell/appShellService;1", "nsIAppShellService"],
   ["cache", "@mozilla.org/network/cache-service;1", "nsICacheService"],
   ["cache2", "@mozilla.org/netwerk/cache-storage-service;1", "nsICacheStorageService"],
   ["cpmm", "@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender"],
   ["console", "@mozilla.org/consoleservice;1", "nsIConsoleService"],
   ["contentPrefs", "@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
   ["cookies", "@mozilla.org/cookiemanager;1", "nsICookieManager2"],
   ["downloads", "@mozilla.org/download-manager;1", "nsIDownloadManager"],
   ["droppedLinkHandler", "@mozilla.org/content/dropped-link-handler;1", "nsIDroppedLinkHandler"],
   ["els", "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService"],
   ["eTLD", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"],
-  ["io", "@mozilla.org/network/io-service;1", "nsIIOService2"],
   ["intl", "@mozilla.org/mozintl;1", "mozIMozIntl"],
   ["locale", "@mozilla.org/intl/localeservice;1", "mozILocaleService"],
   ["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],
   ["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
   ["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
   ["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
   ["profiler", "@mozilla.org/tools/profiler;1", "nsIProfiler",
    AppConstants.MOZ_GECKO_PROFILER],