Bug 1520494 - Speculative connections for the Quantum Bar. r=Standard8
authorMarco Bonardo <mbonardo@mozilla.com>
Mon, 28 Jan 2019 13:45:48 +0000
changeset 455622 e83dff0412511dbd5719f139fa7ead5a5d995880
parent 455621 b8a1a3cca350a6c7fb7275554319d738c0f201fd
child 455623 357d8c184220674695df980af610bbe279ec95a7
push id76892
push usermak77@bonardo.net
push dateMon, 28 Jan 2019 13:46:48 +0000
treeherderautoland@e83dff041251 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1520494
milestone66.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 1520494 - Speculative connections for the Quantum Bar. r=Standard8 Differential Revision: https://phabricator.services.mozilla.com/D17519
browser/base/content/urlbarBindings.xml
browser/components/urlbar/UrlbarController.jsm
browser/components/urlbar/UrlbarInput.jsm
browser/components/urlbar/UrlbarPrefs.jsm
browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
browser/components/urlbar/UrlbarProvidersManager.jsm
browser/components/urlbar/UrlbarResult.jsm
browser/components/urlbar/UrlbarUtils.jsm
browser/components/urlbar/UrlbarView.jsm
browser/components/urlbar/tests/UrlbarTestUtils.jsm
browser/components/urlbar/tests/browser/browser.ini
browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect.js
browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect_not_with_client_cert.js
browser/components/urlbar/tests/browser/head-common.js
browser/components/urlbar/tests/legacy/browser.ini
browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions_opt-out.js
browser/components/urlbar/tests/legacy/browser_urlbar_search_no_speculative_connect_with_client_cert.js
browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect.js
browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect_engine.js
browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect_mousedown.js
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -106,17 +106,16 @@ file, You can obtain one at http://mozil
 
         Services.prefs.addObserver("browser.search.suggest.enabled", this);
         this.browserSearchSuggestEnabled = Services.prefs.getBoolPref("browser.search.suggest.enabled");
 
         this.openInTab = this._prefs.getBoolPref("openintab");
         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._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
         this._adoptIntoActiveWindow = this._prefs.getBoolPref("switchTabs.adoptIntoActiveWindow");
         this._ctrlCanonizesURLs = this._prefs.getBoolPref("ctrlCanonizesURLs");
         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
         this.inputField.addEventListener("paste", this);
         this.inputField.addEventListener("mousedown", this);
@@ -1202,19 +1201,16 @@ 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 "ctrlCanonizesURLs":
                 this._ctrlCanonizesURLs = this._prefs.getBoolPref(aData);
                 break;
-              case "speculativeConnect.enabled":
-                this.speculativeConnectEnabled = this._prefs.getBoolPref(aData);
-                break;
               case "openintab":
                 this.openInTab = 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);
@@ -2458,29 +2454,16 @@ 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[
-          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.)
             let selectHeuristic =
               this.selectedIndex == -1 && this._isFirstResultHeuristic;
             if (selectHeuristic) {
@@ -2508,30 +2491,28 @@ file, You can obtain one at http://mozil
               );
             }
 
             if (this.matchCount > 0) {
               // If this is the first time we get the result from the current
               // 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.speculativeConnectEnabled &&
                   !this.input.inPrivateContext) {
                 let firstStyle = this.input.mController.getStyleAt(0);
                 if (firstStyle.includes("autofill")) {
                   let uri = this.input.mController.getFinalCompleteValueAt(0);
-                  this.maybeSetupSpeculativeConnect(uri);
+                  UrlbarUtils.setupSpeculativeConnection(uri, window);
                 } 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.defaultEngine;
-                  engine.speculativeConnect({window,
-                                            originAttributes: gBrowser.contentPrincipal.originAttributes});
+                  UrlbarUtils.setupSpeculativeConnection(engine, window);
                 }
               }
 
               // When a result is present the footer should always be visible.
               this.footer.collapsed = false;
             }
 
             this.input.tabScrolling = true;
@@ -2699,17 +2680,17 @@ file, You can obtain one at http://mozil
 
         if (event.button == 2) {
           // Right mouse button currently allows to select.
           this.input.userSelectionBehavior = "rightClick";
           // Ignore right-clicks.
           return;
         }
 
-        if (!this.input.speculativeConnectEnabled) {
+        if (!UrlbarPrefs.get("speculativeConnect.enabled")) {
           return;
         }
 
         // Ensure the user is clicking on an url instead of other buttons
         // on the popup.
         let elt = event.originalTarget;
         while (elt && elt.localName != "richlistitem" && elt != this) {
           elt = elt.parentNode;
@@ -2718,39 +2699,31 @@ file, You can obtain one at http://mozil
           return;
         }
         // The user might click on a ghost entry which was removed because of
         // the coming new results.
         if (this.input.controller.matchCount <= this.selectedIndex) {
           return;
         }
 
-        let url = this.input.controller.getFinalCompleteValueAt(this.selectedIndex);
-
         // Whitelist the cases that we want to speculative connect, and ignore
         // other moz-action uris or fancy protocols.
         // Note that it's likely we've speculatively connected to the first
         // url because it is a heuristic "autofill" result (see bug 1348275).
         // "moz-action:searchengine" is also the same case. (see bug 1355443)
         // So we won't duplicate the effort here.
+        let url = this.input.controller.getFinalCompleteValueAt(this.selectedIndex);
         if (url.startsWith("http") && this.selectedIndex > 0) {
-          this.maybeSetupSpeculativeConnect(url);
+          UrlbarUtils.setupSpeculativeConnection(url, window);
         } else if (url.startsWith("moz-action:remotetab")) {
-          // URL is in the format moz-action:ACTION,PARAMS
-          // Where PARAMS is a JSON encoded object.
-          const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
-          if (!MOZ_ACTION_REGEX.test(url))
-            return;
-
-          let params = JSON.parse(url.match(MOZ_ACTION_REGEX)[2]);
-          if (params.url) {
-            this.maybeSetupSpeculativeConnect(decodeURIComponent(params.url));
+          let action = PlacesUtils.parseActionUrl(url);
+          if (action && action.params.url) {
+            UrlbarUtils.setupSpeculativeConnection(action.params.url, window);
           }
         }
-
       ]]></handler>
 
     </handlers>
   </binding>
 
   <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
     <implementation>
       <constructor><![CDATA[
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -3,21 +3,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "UrlbarController",
 ];
 
+ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   // BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
+  UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 const TELEMETRY_1ST_RESULT = "PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS";
 const TELEMETRY_6_FIRST_RESULTS = "PLACES_AUTOCOMPLETE_6_FIRST_RESULTS_TIME_MS";
 
 /**
@@ -95,16 +97,17 @@ class UrlbarController {
 
     queryContext.lastResultCount = 0;
     TelemetryStopwatch.start(TELEMETRY_1ST_RESULT, queryContext);
     TelemetryStopwatch.start(TELEMETRY_6_FIRST_RESULTS, queryContext);
 
     this._notify("onQueryStarted", queryContext);
     await this.manager.startQuery(queryContext, this);
     this._notify("onQueryFinished", queryContext);
+    return queryContext;
   }
 
   /**
    * Cancels an in-progress query. Note, queries may continue running if they
    * can't be canceled.
    */
   cancelQuery() {
     if (!this._lastQueryContext) {
@@ -131,19 +134,24 @@ class UrlbarController {
     if (queryContext.lastResultCount < 6 && queryContext.results.length >= 6) {
       TelemetryStopwatch.finish(TELEMETRY_6_FIRST_RESULTS, queryContext);
     }
 
     if (queryContext.lastResultCount == 0 && queryContext.autofillValue) {
       this.input.autofill(queryContext.autofillValue);
     }
 
-    queryContext.lastResultCount = queryContext.results.length;
+    // The first time we receive results try to connect to the heuristic result.
+    if (queryContext.lastResultCount == 0) {
+      this.speculativeConnect(queryContext, 0, "resultsadded");
+    }
 
     this._notify("onQueryResults", queryContext);
+    // Update lastResultCount after notifying, so the view can use it.
+    queryContext.lastResultCount = queryContext.results.length;
   }
 
   /**
    * Adds a listener for query actions and results.
    *
    * @param {object} listener The listener to add.
    * @throws {TypeError} Throws if the listener is not an object.
    */
@@ -236,16 +244,68 @@ class UrlbarController {
             this._handleDeleteEntry()) {
           event.preventDefault();
         }
         break;
     }
   }
 
   /**
+   * Tries to initialize a speculative connection on a result.
+   * Speculative connections are only supported for a subset of all the results.
+   * @param {UrlbarQueryContext} context The queryContext
+   * @param {number} resultIndex index of the result to speculative connect to.
+   * @param {string} reason Reason for the speculative connect request.
+   * @note speculative connect to:
+   *  - Search engine heuristic results
+   *  - autofill results
+   *  - http/https results
+   */
+  speculativeConnect(context, resultIndex, reason) {
+    // Never speculative connect in private contexts.
+    if (!this.input || context.isPrivate || context.results.length == 0) {
+      return;
+    }
+    let result = context.results[resultIndex];
+    let {url} = UrlbarUtils.getUrlFromResult(result);
+    if (!url) {
+      return;
+    }
+
+    switch (reason) {
+      case "resultsadded": {
+        // We should connect to an heuristic result, if it exists.
+        if (resultIndex == 0 && (context.preselected || context.autofillValue)) {
+          if (result.type == UrlbarUtils.RESULT_TYPE.SEARCH) {
+            // Speculative connect only if search suggestions are enabled.
+            if (UrlbarPrefs.get("suggest.searches") &&
+                UrlbarPrefs.get("browser.search.suggest.enabled")) {
+              let engine = Services.search.defaultEngine;
+              UrlbarUtils.setupSpeculativeConnection(engine, this.browserWindow);
+            }
+          } else if (context.autofillValue) {
+            UrlbarUtils.setupSpeculativeConnection(url, this.browserWindow);
+          }
+        }
+        return;
+      }
+      case "mousedown": {
+        // On mousedown, connect only to http/https urls.
+        if (url.startsWith("http")) {
+          UrlbarUtils.setupSpeculativeConnection(url, this.browserWindow);
+        }
+        return;
+      }
+      default: {
+        throw new Error("Invalid speculative connection reason");
+      }
+    }
+  }
+
+  /**
    * Internal function handling deletion of entries. We only support removing
    * of history entries - other result sources will be ignored.
    *
    * @returns {boolean} Returns true if the deletion was acted upon.
    */
   _handleDeleteEntry() {
     if (!this._lastQueryContext) {
       Cu.reportError("Cannot delete - the latest query is not present");
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -52,16 +52,17 @@ class UrlbarInput {
     this.controller = options.controller || new UrlbarController({
       browserWindow: this.window,
     });
     this.controller.setInput(this);
     this.view = new UrlbarView(this);
     this.valueIsTyped = false;
     this.userInitiatedFocus = false;
     this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
+    this.lastQueryContextPromise = Promise.resolve();
     this._untrimmedValue = "";
     this._suppressStartQuery = false;
 
     // Forward textbox methods and properties.
     const METHODS = ["addEventListener", "removeEventListener",
       "setAttribute", "hasAttribute", "removeAttribute", "getAttribute",
       "select"];
     const READ_ONLY_PROPERTIES = ["inputField", "editor"];
@@ -294,71 +295,70 @@ class UrlbarInput {
     this.view.close();
 
     // TODO: Work out how we get the user selection behavior, probably via passing
     // it in, since we don't have the old autocomplete controller to work with.
     // BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(
     //   event, this.userSelectionBehavior);
 
     let where = this._whereToOpen(event);
+    let {url, postData} = UrlbarUtils.getUrlFromResult(result);
     let openParams = {
-      postData: null,
+      postData,
       allowInheritPrincipal: false,
     };
 
     // TODO bug 1521702: Call _maybeCanonizeURL for autofilled results with the
     // typed string (not the autofilled one).
 
-    let url = result.payload.url;
-
     switch (result.type) {
       case UrlbarUtils.RESULT_TYPE.TAB_SWITCH: {
         if (this._overrideDefaultAction(event)) {
           where = "current";
           break;
         }
 
         this.handleRevert();
         let prevTab = this.window.gBrowser.selectedTab;
         let loadOpts = {
           adoptIntoActiveWindow: UrlbarPrefs.get("switchTabs.adoptIntoActiveWindow"),
         };
 
-        if (this.window.switchToTabHavingURI(Services.io.newURI(result.payload.url), false, loadOpts) &&
+        if (this.window.switchToTabHavingURI(Services.io.newURI(url), false, loadOpts) &&
             prevTab.isEmpty) {
           this.window.gBrowser.removeTab(prevTab);
         }
         return;
       }
       case UrlbarUtils.RESULT_TYPE.SEARCH: {
         url = this._maybeCanonizeURL(event,
                 result.payload.suggestion || result.payload.query);
         if (url) {
           break;
         }
-
         const actionDetails = {
           isSuggestion: !!result.payload.suggestion,
           alias: result.payload.keyword,
         };
         const engine = Services.search.getEngineByName(result.payload.engine);
-
-        [url, openParams.postData] = this._getSearchQueryUrl(
-          engine, result.payload.suggestion || result.payload.query);
         this._recordSearch(engine, event, actionDetails);
         break;
       }
-      case UrlbarUtils.RESULT_TYPE.OMNIBOX:
+      case UrlbarUtils.RESULT_TYPE.OMNIBOX: {
         // Give the extension control of handling the command.
         ExtensionSearchHandler.handleInputEntered(result.payload.keyword,
                                                   result.payload.content,
                                                   where);
         return;
+      }
     }
 
+    if (!url) {
+      throw new Error(`Invalid url for result ${JSON.stringify(result)}`);
+    }
     this._loadURL(url, where, openParams);
   }
 
   /**
    * Called by the view when moving through results with the keyboard.
    *
    * @param {UrlbarResult} result The result that was selected.
    */
@@ -404,27 +404,30 @@ class UrlbarInput {
 
     // If the user has deleted text at the end of the input since the last
     // query, then we don't want to autofill because doing so would autofill the
     // very text the user just deleted.
     let enableAutofill =
       UrlbarPrefs.get("autoFill") &&
       (!this._lastSearchString ||
        !this._lastSearchString.startsWith(searchString));
+    this._lastSearchString = searchString;
 
-    this.controller.startQuery(new UrlbarQueryContext({
+    // TODO (Bug 1522902): This promise is necessary for tests, because some
+    // tests are not listening for completion when starting a query through
+    // other methods than startQuery (input events for example).
+    this.lastQueryContextPromise = this.controller.startQuery(new UrlbarQueryContext({
       enableAutofill,
       isPrivate: this.isPrivate,
       lastKey,
       maxResults: UrlbarPrefs.get("maxRichResults"),
       muxer: "UnifiedComplete",
       providers: ["UnifiedComplete"],
       searchString,
     }));
-    this._lastSearchString = searchString;
   }
 
   typeRestrictToken(char) {
     this.window.focusAndSelectUrlBar();
 
     this.inputField.value = char + " ";
 
     let event = this.document.createEvent("UIEvents");
@@ -615,32 +618,16 @@ class UrlbarInput {
 
   _overrideDefaultAction(event) {
     return event.shiftKey ||
            event.altKey ||
            (AppConstants.platform == "macosx" ?
               event.metaKey : event.ctrlKey);
   }
 
-  /**
-   * Get the url to load for the search query and records in telemetry that it
-   * is being loaded.
-   *
-   * @param {nsISearchEngine} engine
-   *   The engine to generate the query for.
-   * @param {string} query
-   *   The query string to search for.
-   * @returns {array}
-   *   Returns an array containing the query url (string) and the
-   *    post data (object).
-   */
-  _getSearchQueryUrl(engine, query) {
-    let submission = engine.getSubmission(query, null, "keyword");
-    return [submission.uri.spec, submission.postData];
-  }
 
   /**
    * Get the url to load for the search query and records in telemetry that it
    * is being loaded.
    *
    * @param {nsISearchEngine} engine
    *   The engine to generate the query for.
    * @param {Event} event
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -92,16 +92,22 @@ const PREF_URLBAR_DEFAULTS = new Map([
 
   // The maximum number of results in the urlbar popup.
   ["maxRichResults", 10],
 
   // Whether addresses and search results typed into the address bar
   // should be opened in new tabs by default.
   ["openintab", false],
 
+  // Whether the quantum bar is enabled.
+  ["quantumbar", false],
+
+  // Whether speculative connections should be enabled.
+  ["speculativeConnect.enabled", true],
+
   // Results will include the user's bookmarks when this is true.
   ["suggest.bookmark", true],
 
   // Results will include the user's history when this is true.
   ["suggest.history", true],
 
   // Results will include switch-to-tab results when this is true.
   ["suggest.openpage", true],
@@ -125,16 +131,17 @@ const PREF_URLBAR_DEFAULTS = new Map([
 
   // When true, URLs in the user's history that look like search result pages
   // are styled to look like search engine results instead of the usual history
   // results.
   ["restyleSearches", false],
 ]);
 const PREF_OTHER_DEFAULTS = new Map([
   ["keyword.enabled", true],
+  ["browser.search.suggest.enabled", true],
 ]);
 
 // Maps preferences under browser.urlbar.suggest to behavior names, as defined
 // in mozIPlacesAutoComplete.
 const SUGGEST_PREF_TO_BEHAVIOR = {
   history: "history",
   bookmark: "bookmark",
   openpage: "openpage",
@@ -178,17 +185,19 @@ class Preferences {
    */
   constructor() {
     this._map = new Map();
     this.QueryInterface = ChromeUtils.generateQI([
       Ci.nsIObserver,
       Ci.nsISupportsWeakReference,
     ]);
     Services.prefs.addObserver(PREF_URLBAR_BRANCH, this, true);
-    Services.prefs.addObserver("keyword.enabled", this, true);
+    for (let pref of PREF_OTHER_DEFAULTS.keys()) {
+      Services.prefs.addObserver(pref, this, true);
+    }
   }
 
   /**
    * Returns the value for the preference with the given name.
    *
    * @param {string} pref
    *        The name of the preference to get.
    * @returns {*} The preference value.
--- a/browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
+++ b/browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
@@ -192,26 +192,27 @@ function convertResultToMatches(context,
       style,
       comment: result.getCommentAt(i),
       firstToken: context.tokens[0],
     });
     // Should not happen, but better safe than sorry.
     if (!match) {
       continue;
     }
-    matches.push(match);
     // Manage autofillValue and preselected properties for the first match.
     if (i == 0) {
       if (style.includes("autofill") && result.defaultIndex == 0) {
         context.autofillValue = result.getValueAt(i);
       }
       if (style.includes("heuristic")) {
         context.preselected = true;
+        match.heuristic = true;
       }
     }
+    matches.push(match);
   }
   return {matches, done};
 }
 
 /**
  * Creates a new UrlbarResult from the provided data.
  * @param {array} tokens the search tokens.
  * @param {object} info includes properties from the legacy match.
--- a/browser/components/urlbar/UrlbarProvidersManager.jsm
+++ b/browser/components/urlbar/UrlbarProvidersManager.jsm
@@ -303,17 +303,22 @@ class Query {
    * @param {object} provider
    * @param {object} match
    */
   add(provider, match) {
     if (!(provider instanceof UrlbarProvider)) {
       throw new Error("Invalid provider passed to the add callback");
     }
     // Stop returning results as soon as we've been canceled.
-    if (this.canceled || !this.acceptableSources.includes(match.source)) {
+    if (this.canceled) {
+      return;
+    }
+    // Check if the result source should be filtered out. Pay attention to the
+    // heuristic result though, that is supposed to be added regardless.
+    if (!this.acceptableSources.includes(match.source) && !match.heuristic) {
       return;
     }
 
     // Filter out javascript results for safety. The provider is supposed to do
     // it, but we don't want to risk leaking these out.
     if (match.payload.url && match.payload.url.startsWith("javascript:") &&
         !this.context.searchString.startsWith("javascript:") &&
         UrlbarPrefs.get("filter.javascript")) {
--- a/browser/components/urlbar/UrlbarResult.jsm
+++ b/browser/components/urlbar/UrlbarResult.jsm
@@ -46,16 +46,20 @@ class UrlbarResult {
 
     // Source describes which data has been used to derive this match. In case
     // multiple sources are involved, use the more privacy restricted.
     if (!Object.values(UrlbarUtils.MATCH_SOURCE).includes(matchSource)) {
       throw new Error("Invalid match source");
     }
     this.source = matchSource;
 
+    // May be used to indicate an heuristic result. Heuristic results can bypass
+    // source filters in the ProvidersManager, that otherwise may skip them.
+    this.heuristic = false;
+
     // The payload contains result data. Some of the data is common across
     // multiple types, but most of it will vary.
     if (!payload || (typeof payload != "object")) {
       throw new Error("Invalid result payload");
     }
     this.payload = payload;
 
     if (!payloadHighlights || (typeof payloadHighlights != "object")) {
--- a/browser/components/urlbar/UrlbarUtils.jsm
+++ b/browser/components/urlbar/UrlbarUtils.jsm
@@ -12,24 +12,24 @@
 var EXPORTED_SYMBOLS = [
   "UrlbarMuxer",
   "UrlbarProvider",
   "UrlbarQueryContext",
   "UrlbarUtils",
 ];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
 XPCOMUtils.defineLazyModuleGetters(this, {
   BinarySearch: "resource://gre/modules/BinarySearch.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
+  UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
 });
 
 var UrlbarUtils = {
   // Values for browser.urlbar.insertMethod
   INSERTMETHOD: {
     // Just append new results.
     APPEND: 0,
     // Merge previous and current results if search strings are related.
@@ -75,17 +75,17 @@ var UrlbarUtils = {
     // Payload: { icon, suggestion, keyword, query }
     SEARCH: 2,
     // A common url/title tuple, may be a bookmark with tags.
     // Payload: { icon, url, title, tags }
     URL: 3,
     // A bookmark keyword.
     // Payload: { icon, url, keyword, postData }
     KEYWORD: 4,
-    // A WebExtension Omnibox match.
+    // A WebExtension Omnibox result.
     // Payload: { icon, keyword, title, content }
     OMNIBOX: 5,
     // A tab from another synced device.
     // Payload: { url, icon, device, title }
     REMOTE_TAB: 6,
   },
 
   // This defines the source of matches returned by a provider. Each provider
@@ -224,19 +224,96 @@ var UrlbarUtils = {
           }, matches, match);
           matches.splice(matchesIndex, 0, match);
           index += token.value.length;
         }
       }
       return matches;
     }, []);
   },
+
+  /**
+   * Extracts an url from a result, if possible.
+   * @param {UrlbarResult} result The result to extract from.
+   * @returns {object} a {url, postData} object, or null if a url can't be built
+   *          from this result.
+   */
+  getUrlFromResult(result) {
+    switch (result.type) {
+      case UrlbarUtils.RESULT_TYPE.URL:
+      case UrlbarUtils.RESULT_TYPE.KEYWORD:
+      case UrlbarUtils.RESULT_TYPE.REMOTE_TAB:
+      case UrlbarUtils.RESULT_TYPE.TAB_SWITCH:
+        return {url: result.payload.url, postData: null};
+      case UrlbarUtils.RESULT_TYPE.SEARCH: {
+        const engine = Services.search.getEngineByName(result.payload.engine);
+        let [url, postData] = getSearchQueryUrl(
+          engine, result.payload.suggestion || result.payload.query);
+        return {url, postData};
+      }
+    }
+    return {url: null, postData: null};
+  },
+
+  /**
+   * Tries to initiate a speculative connection to a given url.
+   * @param {nsISearchEngine|nsIURI|URL|string} urlOrEngine entity to initiate
+   *        a speculative connection for.
+   * @param {window} window the window from where the connection is initialized.
+   * @note This is not infallible, if a speculative connection cannot be
+   *       initialized, it will be a no-op.
+   */
+  setupSpeculativeConnection(urlOrEngine, window) {
+    if (!UrlbarPrefs.get("speculativeConnect.enabled")) {
+      return;
+    }
+    if (urlOrEngine instanceof Ci.nsISearchEngine) {
+      try {
+        urlOrEngine.speculativeConnect({
+          window,
+          originAttributes: window.gBrowser.contentPrincipal.originAttributes,
+        });
+      } catch (ex) {
+        // Can't setup speculative connection for this url, just ignore it.
+      }
+      return;
+    }
+
+    if (urlOrEngine instanceof URL) {
+      urlOrEngine = urlOrEngine.href;
+    }
+
+    try {
+      let uri = urlOrEngine instanceof Ci.nsIURI ? urlOrEngine
+                                                  : Services.io.newURI(urlOrEngine);
+      Services.io.speculativeConnect2(uri, window.gBrowser.contentPrincipal, null);
+    } catch (ex) {
+      // Can't setup speculative connection for this url, just ignore it.
+    }
+  },
 };
 
 /**
+ * Get the url to load for the search query and records in telemetry that it
+ * is being loaded.
+ *
+ * @param {nsISearchEngine} engine
+ *   The engine to generate the query for.
+ * @param {string} query
+ *   The query string to search for.
+ * @returns {array}
+ *   Returns an array containing the query url (string) and the
+ *    post data (object).
+ */
+function getSearchQueryUrl(engine, query) {
+  let submission = engine.getSubmission(query, null, "keyword");
+  return [submission.uri.spec, submission.postData];
+}
+
+/**
  * UrlbarQueryContext defines a user's autocomplete input from within the urlbar.
  * It supplements it with details of how the search results should be obtained
  * and what they consist of.
  */
 class UrlbarQueryContext {
   /**
    * Constructs the UrlbarQueryContext instance.
    *
@@ -293,40 +370,40 @@ class UrlbarQueryContext {
       }
       this[optionName] = options[optionName];
     }
   }
 }
 
 /**
  * Base class for a muxer.
- * The muxer scope is to sort a given list of matches.
+ * The muxer scope is to sort a given list of results.
  */
 class UrlbarMuxer {
   /**
-   * Unique name for the muxer, used by the context to sort matches.
+   * Unique name for the muxer, used by the context to sort results.
    * Not using a unique name will cause the newest registration to win.
    * @abstract
    */
   get name() {
     return "UrlbarMuxerBase";
   }
   /**
-   * Sorts queryContext matches in-place.
-   * @param {UrlbarQueryContext} queryContext the context to sort matches for.
+   * Sorts queryContext results in-place.
+   * @param {UrlbarQueryContext} queryContext the context to sort results for.
    * @abstract
    */
   sort(queryContext) {
     throw new Error("Trying to access the base class, must be overridden");
   }
 }
 
 /**
  * Base class for a provider.
- * The provider scope is to query a datasource and return matches from it.
+ * The provider scope is to query a datasource and return results from it.
  */
 class UrlbarProvider {
   /**
    * Unique name for the provider, used by the context to filter on providers.
    * Not using a unique name will cause the newest registration to win.
    * @abstract
    */
   get name() {
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -30,16 +30,17 @@ class UrlbarView {
     this.controller = input.controller;
     this.document = this.panel.ownerDocument;
     this.window = this.document.defaultView;
 
     this._mainContainer = this.panel.querySelector(".urlbarView-body-inner");
     this._rows = this.panel.querySelector(".urlbarView-results");
 
     this._rows.addEventListener("mouseup", this);
+    this._rows.addEventListener("mousedown", this);
 
     // For the horizontal fade-out effect, set the overflow attribute on result
     // rows when they overflow.
     this._rows.addEventListener("overflow", this);
     this._rows.addEventListener("underflow", this);
 
     this.panel.addEventListener("popuphiding", this);
 
@@ -136,16 +137,19 @@ class UrlbarView {
     this._queryContext = queryContext;
     for (let resultIndex in queryContext.results) {
       this._addRow(resultIndex);
     }
 
     if (queryContext.preselected) {
       this._selected = this._rows.firstElementChild;
       this._selected.toggleAttribute("selected", true);
+    } else if (queryContext.lastResultCount == 0) {
+      // Clear the selection when we get a new set of results.
+      this._selected = null;
     }
 
     this._openPanel();
   }
 
   /**
    * Handles removing a result from the view when it is removed from the query,
    * and attempts to select the new result on the same row.
@@ -369,16 +373,35 @@ class UrlbarView {
     let methodName = "_on_" + event.type;
     if (methodName in this) {
       this[methodName](event);
     } else {
       throw new Error("Unrecognized UrlbarView event: " + event.type);
     }
   }
 
+  _on_mousedown(event) {
+    if (event.button == 2) {
+      // Ignore right clicks.
+      return;
+    }
+
+    let row = event.target;
+    while (!row.classList.contains("urlbarView-row")) {
+      row = row.parentNode;
+    }
+    let resultIndex = row.getAttribute("resultIndex");
+    if (this._selected) {
+      this._selected.toggleAttribute("selected", false);
+    }
+    this._selected = this._rows.children[resultIndex];
+    this._selected.toggleAttribute("selected", true);
+    this.controller.speculativeConnect(this._queryContext, resultIndex, "mousedown");
+  }
+
   _on_mouseup(event) {
     if (event.button == 2) {
       // Ignore right clicks.
       return;
     }
 
     let row = event.target;
     while (!row.classList.contains("urlbarView-row")) {
--- a/browser/components/urlbar/tests/UrlbarTestUtils.jsm
+++ b/browser/components/urlbar/tests/UrlbarTestUtils.jsm
@@ -4,90 +4,317 @@
 "use strict";
 
 const EXPORTED_SYMBOLS = ["UrlbarTestUtils"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BrowserTestUtils: "resource://testing-common/BrowserTestUtils.jsm",
+  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   TestUtils: "resource://testing-common/TestUtils.jsm",
+  UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
+  UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 var UrlbarTestUtils = {
-  promiseSearchComplete(win, dontAnimate = false) {
-    return BrowserTestUtils.waitForPopupEvent(win.gURLBar.popup, "shown").then(() => {
-      function searchIsComplete() {
-        let isComplete = win.gURLBar.controller.searchStatus >=
-                         Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
-        if (isComplete) {
-          dump(`Restore popup dontAnimate value to ${dontAnimate}\n`);
-          win.gURLBar.popup.setAttribute("dontanimate", dontAnimate);
-        }
-        return isComplete;
+  /**
+   * Waits to a search to be complete.
+   * @param {object} win The window containing the urlbar
+   * @param {function} restoreAnimationsFn optional Function to be used to
+   *        restore animations once done.
+   * @returns {Promise} Resolved when done.
+   */
+  promiseSearchComplete(win, restoreAnimationsFn = null) {
+    let urlbar = getUrlbarAbstraction(win);
+    return BrowserTestUtils.waitForPopupEvent(urlbar.panel, "shown").then(async () => {
+      await urlbar.promiseSearchComplete();
+      if (typeof restoreAnimations == "function") {
+        restoreAnimationsFn();
       }
-
-      // Wait until there are at least two matches.
-      return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete");
     });
   },
 
-  promiseAutocompleteResultPopup(inputText, win, waitForFocus, fireInputEvent = false) {
-    let dontAnimate = !!win.gURLBar.popup.getAttribute("dontanimate");
-    waitForFocus(() => {
-      dump(`Disable popup animation. Change dontAnimate value from ${dontAnimate} to true.\n`);
-      win.gURLBar.popup.setAttribute("dontanimate", "true");
-      win.gURLBar.focus();
-      win.gURLBar.value = inputText;
-      if (fireInputEvent) {
-        // This is necessary to get the urlbar to set gBrowser.userTypedValue.
-        let event = win.document.createEvent("Events");
-        event.initEvent("input", true, true);
-        win.gURLBar.dispatchEvent(event);
-      }
-      win.gURLBar.controller.startSearch(inputText);
-    }, win);
+  /**
+   * Starts a search for a given string and waits for the search to be complete.
+   * @param {string} inputText the search string
+   * @param {object} win The window containing the urlbar
+   * @param {function} waitForFocus The Simpletest function
+   * @param {boolean} fireInputEvent whether an input event should be used when
+   *        starting the query (necessary to set userTypedValued)
+   */
+  async promiseAutocompleteResultPopup(inputText, win, waitForFocus, fireInputEvent = false) {
+    let urlbar = getUrlbarAbstraction(win);
+    let restoreAnimationsFn = urlbar.disableAnimations();
+    await new Promise(resolve => waitForFocus(resolve, win));
+    urlbar.focus();
+    urlbar.value = inputText;
+    if (fireInputEvent) {
+      // This is necessary to get the urlbar to set gBrowser.userTypedValue.
+      urlbar.fireInputEvent();
+    }
+    // In the quantum bar it's enough to fire the input event to start a query,
+    // invoking startSearch would do it twice.
+    if (!urlbar.quantumbar || !fireInputEvent) {
+      urlbar.startSearch(inputText);
+    }
+    return this.promiseSearchComplete(win, restoreAnimationsFn);
+  },
 
-    return this.promiseSearchComplete(win, dontAnimate);
+  /**
+   * Waits for a result to be added at a certain index. Since we implement lazy
+   * results replacement, even if we have a result at an index, it may be
+   * related to the previous query, this methods ensures the result is current.
+   * @param {object} win The window containing the urlbar
+   * @param {number} index The index to look for
+   */
+  async waitForAutocompleteResultAt(win, index) {
+    let urlbar = getUrlbarAbstraction(win);
+    return urlbar.promiseResultAt(index);
   },
 
-  async waitForAutocompleteResultAt(win, index) {
-    let searchString = win.gURLBar.controller.searchString;
-    await BrowserTestUtils.waitForCondition(
-      () => win.gURLBar.popup.richlistbox.itemChildren.length > index &&
-            win.gURLBar.popup.richlistbox.itemChildren[index].getAttribute("ac-text") == searchString.trim(),
-      `Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
-    // Ensure the addition is complete, for proper mouse events on the entries.
-    await new Promise(resolve => win.requestIdleCallback(resolve, {timeout: 1000}));
-    return win.gURLBar.popup.richlistbox.itemChildren[index];
+  /**
+   * Gets an abstracted rapresentation of the result at an index.
+   * @param {object} win The window containing the urlbar
+   * @param {number} index The index to look for
+   */
+  async getDetailsOfResultAt(win, index) {
+    let urlbar = getUrlbarAbstraction(win);
+    return urlbar.getDetailsOfResultAt(index);
+  },
+
+  /**
+   * Gets the currently selected element.
+   * @param {object} win The window containing the urlbar
+   * @returns {HtmlElement|XulElement} the selected element.
+   */
+  getSelectedElement(win) {
+    let urlbar = getUrlbarAbstraction(win);
+    return urlbar.getSelectedElement();
+  },
+
+  /**
+   * Gets the number of results.
+   * You must wait for the query to be complete before using this.
+   * @param {object} win The window containing the urlbar
+   * @returns {number} the number of results.
+   */
+  getResultCount(win) {
+    let urlbar = getUrlbarAbstraction(win);
+    return urlbar.getResultCount();
+  },
+
+  /**
+   * Ensures at least one search suggestion is present.
+   * @param {object} win The window containing the urlbar
+   * @returns {boolean} whether at least one search suggestion is present.
+   */
+  promiseSuggestionsPresent(win) {
+    let urlbar = getUrlbarAbstraction(win);
+    return urlbar.promiseSearchSuggestions();
   },
 
-  promiseSuggestionsPresent(win, msg = "") {
-    return TestUtils.waitForCondition(this.suggestionsPresent.bind(this, win),
-                                      msg || "Waiting for suggestions");
-  },
-
-  suggestionsPresent(win) {
-    let controller = win.gURLBar.popup.input.controller;
-    let matchCount = controller.matchCount;
-    for (let i = 0; i < matchCount; i++) {
-      let url = controller.getValueAt(i);
-      let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
-      if (mozActionMatch) {
-        let [, type, paramStr] = mozActionMatch;
-        let params = JSON.parse(paramStr);
-        if (type == "searchengine" && "searchSuggestion" in params) {
-          return true;
-        }
-      }
+  /**
+   * Waits for the given number of connections to an http server.
+   * @param {object} httpserver an HTTP Server instance
+   * @param {number} count Number of connections to wait for
+   * @returns {Promise} resolved when all the expected connections were started.
+   */
+  promiseSpeculativeConnections(httpserver, count) {
+    if (!httpserver) {
+      throw new Error("Must provide an http server");
     }
-    return false;
-  },
-
-  promiseSpeculativeConnection(httpserver) {
-    return BrowserTestUtils.waitForCondition(() => {
-      if (httpserver) {
-        return httpserver.connectionNumber == 1;
-      }
-      return false;
-    }, "Waiting for connection setup");
+    return BrowserTestUtils.waitForCondition(
+      () => httpserver.connectionNumber == count,
+      "Waiting for speculative connection setup"
+    );
   },
 };
+
+/**
+ * Maps windows to urlbar abstractions.
+ */
+var gUrlbarAbstractions = new WeakMap();
+
+function getUrlbarAbstraction(win) {
+  if (!gUrlbarAbstractions.has(win)) {
+    gUrlbarAbstractions.set(win, new UrlbarAbstraction(win));
+  }
+  return gUrlbarAbstractions.get(win);
+}
+
+/**
+ * Abstracts the urlbar implementation, so it can be used regardless of
+ * Quantum Bar being enabled.
+ */
+class UrlbarAbstraction {
+  constructor(win) {
+    if (!win) {
+      throw new Error("Must provide a browser window");
+    }
+    this.urlbar = win.gURLBar;
+    this.quantumbar = UrlbarPrefs.get("quantumbar");
+    this.window = win;
+    this.window.addEventListener("unload", () => {
+      this.urlbar = null;
+      this.window = null;
+    }, {once: true});
+  }
+
+  /**
+   * Disable animations.
+   * @returns {function} can be invoked to restore the previous behavior.
+   */
+  disableAnimations() {
+    if (!this.quantumbar) {
+      let dontAnimate = !!this.urlbar.popup.getAttribute("dontanimate");
+      this.urlbar.popup.setAttribute("dontanimate", "true");
+      return () => {
+        this.urlbar.popup.setAttribute("dontanimate", dontAnimate);
+      };
+    }
+    return () => {};
+  }
+
+  /**
+   * Focus the input field.
+   */
+  focus() {
+    this.urlbar.inputField.focus();
+  }
+
+  /**
+   * Dispatches an input event to the input field.
+   */
+  fireInputEvent() {
+    let event = this.window.document.createEvent("Events");
+    event.initEvent("input", true, true);
+    this.urlbar.inputField.dispatchEvent(event);
+  }
+
+  set value(val) {
+    this.urlbar.value = val;
+  }
+  get value() {
+    return this.urlbar.value;
+  }
+
+  get panel() {
+    return this.quantumbar ? this.urlbar.panel : this.urlbar.popup;
+  }
+
+  startSearch(text) {
+    if (this.quantumbar) {
+      this.urlbar.value = text;
+      this.urlbar.startQuery();
+    } else {
+      this.urlbar.controller.startSearch(text);
+    }
+  }
+
+  promiseSearchComplete() {
+    if (this.quantumbar) {
+      return this.urlbar.lastQueryContextPromise;
+    }
+    return BrowserTestUtils.waitForCondition(
+      () => this.urlbar.controller.searchStatus >=
+              Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH,
+      "waiting urlbar search to complete");
+  }
+
+  async promiseResultAt(index) {
+    if (!this.quantumbar) {
+      // In the legacy address bar, old results are replaced when new results
+      // arrive, thus it's possible for a result to be present but refer to
+      // a previous query. This ensures the given result refers to the current
+      // query by checking its query string against the string being searched
+      // for.
+      let searchString = this.urlbar.controller.searchString;
+      await BrowserTestUtils.waitForCondition(
+        () => this.panel.richlistbox.itemChildren.length > index &&
+            this.panel.richlistbox.itemChildren[index].getAttribute("ac-text") == searchString.trim(),
+        `Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
+      // Ensure the addition is complete, for proper mouse events on the entries.
+      await new Promise(resolve => this.window.requestIdleCallback(resolve, {timeout: 1000}));
+      return this.panel.richlistbox.itemChildren[index];
+    }
+    // TODO: Quantum Bar doesn't yet implement lazy results replacement.
+    await this.promiseSearchComplete();
+    if (index >= this.urlbar.view._rows.length) {
+      throw new Error("Not enough results");
+    }
+    return this.urlbar.view._rows.children[index];
+  }
+
+  getSelectedElement() {
+    if (this.quantumbar) {
+      return this.urlbar.view._selected || null;
+    }
+    return this.panel.selectedIndex >= 0 ?
+      this.panel.richlistbox.itemChildren[this.panel.selectedIndex] : null;
+  }
+
+  getResultCount() {
+    return this.quantumbar ? this.urlbar.view._rows.children.length
+                           : this.urlbar.controller.matchCount;
+  }
+
+  async getDetailsOfResultAt(index) {
+    await this.promiseResultAt(index);
+    function getType(style, action) {
+      if (style.includes("searchengine") || style.includes("suggestions")) {
+        return UrlbarUtils.RESULT_TYPE.SEARCH;
+      } else if (style.includes("extension")) {
+        return UrlbarUtils.RESULT_TYPE.OMNIBOX;
+      } else if (action && action.type == "keyword") {
+        return UrlbarUtils.RESULT_TYPE.KEYWORD;
+      } else if (action && action.type == "remotetab") {
+        return UrlbarUtils.RESULT_TYPE.REMOTE_TAB;
+      } else if (action && action.type == "switchtab") {
+        return UrlbarUtils.RESULT_TYPE.TAB_SWITCH;
+      }
+      return UrlbarUtils.RESULT_TYPE.URL;
+    }
+    let details = {};
+    if (this.quantumbar) {
+      let context = await this.urlbar.lastQueryContextPromise;
+      details.url = (UrlbarUtils.getUrlFromResult(context.results[index])).url;
+      details.type = context.results[index].type;
+      details.autofill = index == 0 && context.autofillValue;
+    } else {
+      details.url = this.urlbar.controller.getFinalCompleteValueAt(index);
+      let style = this.urlbar.controller.getStyleAt(index);
+      let action = PlacesUtils.parseActionUrl(this.urlbar.controller.getValueAt(index));
+      details.type = getType(style, action);
+      details.autofill = style.includes("autofill");
+    }
+    return details;
+  }
+
+  async promiseSearchSuggestions() {
+    if (!this.quantumbar) {
+      return TestUtils.waitForCondition(() => {
+        let controller = this.urlbar.controller;
+        let matchCount = controller.matchCount;
+        for (let i = 0; i < matchCount; i++) {
+          let url = controller.getValueAt(i);
+          let action = PlacesUtils.parseActionUrl(url);
+          if (action && action.type == "searchengine" &&
+              action.params.searchSuggestion) {
+            return true;
+          }
+        }
+        return false;
+      }, "Waiting for suggestions");
+    }
+    // TODO: Quantum Bar doesn't yet implement lazy results replacement. When
+    // we do that, we'll have to be sure the suggestions we find are relevant
+    // for the current query. For now let's just wait for the search to be
+    // complete.
+    return this.promiseSearchComplete().then(context => {
+      // Look for search suggestions.
+      if (!context.results.some(r => r.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+                                r.payload.suggestion)) {
+        throw new Error("Cannot find a search suggestion");
+      }
+    });
+  }
+}
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -25,16 +25,21 @@ support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_content_opener.js]
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_remoteness_switch.js]
 run-if = e10s
 [browser_urlbar_searchsettings.js]
+[browser_urlbar_speculative_connect.js]
+support-files =
+  searchSuggestionEngine2.xml
+  searchSuggestionEngine.sjs
+[browser_urlbar_speculative_connect_not_with_client_cert.js]
 [browser_urlbar_whereToOpen.js]
 [browser_urlbarCopying.js]
 subsuite = clipboard
 support-files =
   authenticate.sjs
 [browser_urlbarEnter.js]
 [browser_urlbarFocusedCmdK.js]
 [browser_urlbarHashChangeProxyState.js]
rename from browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect_mousedown.js
rename to browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect.js
--- a/browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect_mousedown.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect.js
@@ -1,73 +1,152 @@
 /* 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
-// the site in mousedown event before the http request happens(in mouseup).
+// the site in various cases:
+//  1. search engine if it's the first result
+//  2. mousedown event before the http request happens(in mouseup).
+
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine2.xml";
 
-let gHttpServer = null;
-let gScheme = "http";
-let gHost = "localhost"; // 'localhost' by default.
-let gPort = -1;
-let gIsSpeculativeConnected = false;
+const serverInfo = {
+  scheme: "http",
+  host: "localhost",
+  port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
+};
 
 add_task(async function setup() {
-  gHttpServer = runHttpServer(gScheme, gHost, gPort);
-  // The server will be run on a random port if the port number wasn't given.
-  gPort = gHttpServer.identity.primaryPort;
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.urlbar.autoFill", 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],
+    ],
+  });
+
+  // Ensure we start from a clean situation.
+  await PlacesUtils.history.clear();
 
   await PlacesTestUtils.addVisits([{
-    uri: `${gScheme}://${gHost}:${gPort}`,
+    uri: `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}`,
     title: "test visit for speculative connection",
     transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
   }]);
 
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.autoFill", true],
-          // Turn off search suggestion so we won't 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]],
-  });
+  let engine = await SearchTestUtils.promiseNewSearchEngine(
+    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
+  let oldCurrentEngine = Services.search.defaultEngine;
+  Services.search.defaultEngine = engine;
 
   registerCleanupFunction(async function() {
     await PlacesUtils.history.clear();
-    gHttpServer.identity.remove(gScheme, gHost, gPort);
-    gHttpServer.stop(() => {
-      gHttpServer = null;
-    });
+    Services.search.defaultEngine = oldCurrentEngine;
+  });
+});
+
+add_task(async function search_test() {
+  // We speculative connect to the search engine only if suggestions are enabled.
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.search.suggest.enabled", true],
+    ],
+  });
+  await withHttpServer(serverInfo, async server => {
+    let connectionNumber = server.connectionNumber;
+    info("Searching for 'foo'");
+    await promiseAutocompleteResultPopup("foo", window, true);
+    // Check if the first result is with type "searchengine"
+    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+    Assert.equal(details.type, UrlbarUtils.RESULT_TYPE.SEARCH, "The first result is a search");
+    await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber + 1);
   });
 });
 
-add_task(async function popup_mousedown_tests() {
-  const test = {
-    // To not trigger autofill, search keyword starts from the second character.
-    search: gHost.substr(1, 4),
-    completeValue: `${gScheme}://${gHost}:${gPort}/`,
-  };
-  info(`Searching for '${test.search}'`);
-  await promiseAutocompleteResultPopup(test.search, window, true);
-  // Check if the first result is with type "searchengine"
-  let controller = gURLBar.popup.input.controller;
-  // The first item should be 'Search with ...' thus we wan the second.
-  let value = controller.getFinalCompleteValueAt(1);
-  info(`The value of the second item is ${value}`);
-  is(value, test.completeValue, "The second item has the url we visited.");
+add_task(async function popup_mousedown_test() {
+  // Disable search suggestions and autofill, to avoid other speculative
+  // connections.
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.search.suggest.enabled", false],
+      ["browser.urlbar.autoFill", false],
+    ],
+  });
+  await withHttpServer(serverInfo, async server => {
+    let connectionNumber = server.connectionNumber;
+    let searchString = "ocal";
+    let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
+    info(`Searching for '${searchString}'`);
+
+    await promiseAutocompleteResultPopup(searchString, window, true);
+    let listitem = await waitForAutocompleteResultAt(1);
+    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+    Assert.equal(details.url, completeValue, "The second item has the url we visited.");
+
+    info("Clicking on the second result");
+    EventUtils.synthesizeMouseAtCenter(listitem, {type: "mousedown"}, window);
+    Assert.equal(UrlbarTestUtils.getSelectedElement(window), listitem, "The second item is selected");
+    await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber + 1);
+  });
+});
+
+add_task(async function test_autofill() {
+  // Disable search suggestions but enable autofill.
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.search.suggest.enabled", false],
+      ["browser.urlbar.autoFill", true],
+    ],
+  });
+  await withHttpServer(serverInfo, async server => {
+    let connectionNumber = server.connectionNumber;
+    let searchString = serverInfo.host;
+    info(`Searching for '${searchString}'`);
 
-  await BrowserTestUtils.waitForCondition(() => {
-    return !!gURLBar.popup.richlistbox.children[1] &&
-           BrowserTestUtils.is_visible(gURLBar.popup.richlistbox.children[1]);
-  }, "the node is there.");
+    await promiseAutocompleteResultPopup(searchString, window, true);
+    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+    let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
+    Assert.equal(details.url, completeValue, `Autofilled value is as expected`);
+    await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber + 1);
+  });
+});
+
+add_task(async function test_autofill_privateContext() {
+  info("Autofill in private context.");
+  let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
+  registerCleanupFunction(async () => {
+    let promisePBExit = TestUtils.topicObserved("last-pb-context-exited");
+    await BrowserTestUtils.closeWindow(privateWin);
+    await promisePBExit;
+  });
+  await withHttpServer(serverInfo, async server => {
+    let connectionNumber = server.connectionNumber;
+    let searchString = serverInfo.host;
+    info(`Searching for '${searchString}'`);
 
-  let listitem = gURLBar.popup.richlistbox.children[1];
-  EventUtils.synthesizeMouse(listitem, 10, 10, {type: "mousedown"}, window);
-  is(gURLBar.popup.richlistbox.selectedIndex, 1, "The second item is selected");
-  await promiseSpeculativeConnection(gHttpServer);
-  is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
+    await promiseAutocompleteResultPopup(searchString, privateWin, true);
+    let details = await UrlbarTestUtils.getDetailsOfResultAt(privateWin, 0);
+    let completeValue = `${serverInfo.scheme}://${serverInfo.host}:${serverInfo.port}/`;
+    Assert.equal(details.url, completeValue, `Autofilled value is as expected`);
+    await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber);
+  });
 });
+
+add_task(async function test_no_heuristic_result() {
+  info("Don't speculative connect on results addition if there's no heuristic");
+  await withHttpServer(serverInfo, async server => {
+    let connectionNumber = server.connectionNumber;
+    info(`Searching for the empty string`);
+    await promiseAutocompleteResultPopup("", window, true);
+    ok(UrlbarTestUtils.getResultCount(window) > 0, "Has results");
+    let result = await UrlbarTestUtils.getSelectedElement(window);
+    Assert.strictEqual(result, null, `Should have no selection`);
+    await UrlbarTestUtils.promiseSpeculativeConnections(server, connectionNumber);
+  });
+});
rename from browser/components/urlbar/tests/legacy/browser_urlbar_search_no_speculative_connect_with_client_cert.js
rename to browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect_not_with_client_cert.js
--- a/browser/components/urlbar/tests/legacy/browser_urlbar_search_no_speculative_connect_with_client_cert.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_speculative_connect_not_with_client_cert.js
@@ -137,36 +137,29 @@ add_task(async function setup() {
   registerCleanupFunction(async function() {
     await PlacesUtils.history.clear();
     MockRegistrar.unregister(clientAuthDialogsCID);
     certOverrideService.clearValidityOverride("localhost", server.port);
   });
 });
 
 add_task(async function popup_mousedown_no_client_cert_dialog_until_navigate_test() {
-  const test = {
-    // To not trigger autofill, search keyword starts from the second character.
-    search: host.substr(1, 4),
-    completeValue: uri,
-  };
-  info(`Searching for '${test.search}'`);
-  await promiseAutocompleteResultPopup(test.search, window, true);
-  await waitForAutocompleteResultAt(1);
-  let controller = gURLBar.popup.input.controller;
-  // The first item should be 'Search with ...' thus we want the second.
-  let value = controller.getFinalCompleteValueAt(1);
-  info(`The value of the second item is ${value}`);
-  is(value, test.completeValue, "The second item has the url we visited.");
-
-  let listitem = await waitForAutocompleteResultAt(1);
-  Assert.ok(BrowserTestUtils.is_visible(listitem), "The node is there.");
+  // To not trigger autofill, search keyword starts from the second character.
+  let searchString = host.substr(1, 4);
+  let completeValue = uri;
+  info(`Searching for '${searchString}'`);
+  await promiseAutocompleteResultPopup(searchString, window, true);
+  let listitem = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+  let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+  info(`The url of the second item is ${details.url}`);
+  is(details.url, completeValue, "The second item has the url we visited.");
 
   expectingChooseCertificate = false;
   EventUtils.synthesizeMouseAtCenter(listitem, {type: "mousedown"}, window);
-  is(gURLBar.popup.richlistbox.selectedIndex, 1, "The second item is selected");
+  is(UrlbarTestUtils.getSelectedElement(window), listitem, "The second item is selected");
 
   // We shouldn't have triggered a speculative connection, because a client
   // certificate is installed.
   SimpleTest.requestFlakyTimeout("Wait for UI");
   await new Promise(resolve => setTimeout(resolve, 200));
 
   // Now mouseup, expect that we choose a client certificate, and expect that
   // we successfully load a page.
--- a/browser/components/urlbar/tests/browser/head-common.js
+++ b/browser/components/urlbar/tests/browser/head-common.js
@@ -19,27 +19,47 @@ function is_element_visible(element, msg
   ok(BrowserTestUtils.is_visible(element), msg || "Element should be visible");
 }
 
 function is_element_hidden(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
   ok(BrowserTestUtils.is_hidden(element), msg || "Element should be hidden");
 }
 
-function runHttpServer(scheme, host, port = -1) {
-  let httpserver = new HttpServer();
+/**
+ * Initializes an HTTP Server, and runs a task with it.
+ * @param {object} details {scheme, host, port}
+ * @param {function} taskFn The task to run, gets the server as argument.
+ */
+async function withHttpServer(details = { scheme: "http", host: "localhost", port: -1}, taskFn) {
+  let server = new HttpServer();
+  let url = `${details.scheme}://${details.host}:${details.port}`;
   try {
-    httpserver.start(port);
-    port = httpserver.identity.primaryPort;
-    httpserver.identity.setPrimary(scheme, host, port);
-  } catch (ex) {
-    info("We can't launch our http server successfully.");
+    info(`starting HTTP Server for ${url}`);
+    try {
+      server.start(details.port);
+      details.port = server.identity.primaryPort;
+      server.identity.setPrimary(details.scheme, details.host, details.port);
+    } catch (ex) {
+      throw ("We can't launch our http server successfully. " + ex);
+    }
+    Assert.ok(server.identity.has(details.scheme, details.host, details.port),
+              `${url} is listening.`);
+    try {
+      await taskFn(server);
+    } catch (ex) {
+      throw new Error("Exception in the task function " + ex);
+    }
+  } finally {
+    server.identity.remove(details.scheme, details.host, details.port);
+    try {
+      server.stop(() => {});
+    } catch (ex) {}
+    server = null;
   }
-  is(httpserver.identity.has(scheme, host, port), true, `${scheme}://${host}:${port} is listening.`);
-  return httpserver;
 }
 
 function promisePopupShown(popup) {
   return BrowserTestUtils.waitForPopupEvent(popup, "shown");
 }
 
 function promisePopupHidden(popup) {
   return BrowserTestUtils.waitForPopupEvent(popup, "hidden");
@@ -51,20 +71,16 @@ function promiseSearchComplete(win = win
 
 function promiseAutocompleteResultPopup(inputText,
                                         win = window,
                                         fireInputEvent = false) {
   return UrlbarTestUtils.promiseAutocompleteResultPopup(inputText,
     win, waitForFocus, fireInputEvent);
 }
 
-function promiseSpeculativeConnection(httpserver) {
-  return UrlbarTestUtils.promiseSpeculativeConnection(httpserver);
-}
-
 async function waitForAutocompleteResultAt(index) {
   return UrlbarTestUtils.waitForAutocompleteResultAt(window, index);
 }
 
 function promiseSuggestionsPresent(msg = "") {
   return UrlbarTestUtils.promiseSuggestionsPresent(window, msg);
 }
 
--- a/browser/components/urlbar/tests/legacy/browser.ini
+++ b/browser/components/urlbar/tests/legacy/browser.ini
@@ -96,23 +96,16 @@ support-files =
 support-files =
   ../browser/searchSuggestionEngine.xml
   ../browser/searchSuggestionEngine.sjs
 [browser_urlbarStop.js]
 [browser_urlbarTokenAlias.js]
 [browser_urlbar_autoFill_backspaced.js]
 [browser_urlbar_canonize_on_autofill.js]
 [browser_urlbar_remove_match.js]
-[browser_urlbar_search_speculative_connect.js]
-[browser_urlbar_search_speculative_connect_engine.js]
-support-files =
-  ../browser/searchSuggestionEngine2.xml
-  ../browser/searchSuggestionEngine.sjs
-[browser_urlbar_search_speculative_connect_mousedown.js]
-[browser_urlbar_search_no_speculative_connect_with_client_cert.js]
 [browser_urlbar_stop_pending.js]
 support-files =
   ../browser/slow-page.sjs
 [browser_urlbarStopSearchOnSelection.js]
 support-files =
   ../browser/searchSuggestionEngineSlow.xml
   ../browser/searchSuggestionEngine.sjs
 
@@ -149,14 +142,19 @@ support-files =
 [../browser/browser_urlbarPlaceholder.js]
 support-files =
   ../browser/searchSuggestionEngine.xml
   ../browser/searchSuggestionEngine.sjs
 [../browser/browser_urlbarRevert.js]
 [../browser/browser_urlbarSearchSingleWordNotification.js]
 [../browser/browser_urlbarUpdateForDomainCompletion.js]
 [../browser/browser_urlbar_searchsettings.js]
+[../browser/browser_urlbar_speculative_connect.js]
+support-files =
+  ../browser/searchSuggestionEngine2.xml
+  ../browser/searchSuggestionEngine.sjs
+[../browser/browser_urlbar_speculative_connect_not_with_client_cert.js]
 [../browser/browser_urlbar_remoteness_switch.js]
 run-if = e10s
 [../browser/browser_wyciwyg_urlbarCopying.js]
 subsuite = clipboard
 support-files =
   ../browser/test_wyciwyg_copying.html
--- a/browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions_opt-out.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions_opt-out.js
@@ -51,17 +51,17 @@ add_task(async function focus() {
   assertVisible(true);
   assertFooterVisible(false);
   Assert.equal(gURLBar.popup.matchCount, 0, "popup should have no results");
 
   // Start searching.
   EventUtils.sendString("rnd");
   await promiseSearchComplete();
   await waitForAutocompleteResultAt(0);
-  Assert.ok(suggestionsPresent());
+  await promiseSuggestionsPresent();
   assertVisible(true);
   assertFooterVisible(true);
 
   // Check the Change Options link.
   let changeOptionsLink = document.getAnonymousElementByAttribute(gURLBar.popup, "id", "search-suggestions-change-settings");
   let prefsPromise = BrowserTestUtils.waitForLocationChange(gBrowser, "about:preferences#search");
   changeOptionsLink.click();
   await prefsPromise;
deleted file mode 100644
--- a/browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/* 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 gHttpServer = null;
-let gScheme = "http";
-let gHost = "localhost"; // 'localhost' by default.
-let gPort = -1;
-let gPrivateWin = null;
-let gIsSpeculativeConnected = false;
-
-let gTest;
-
-add_task(async function setup() {
-  gHttpServer = runHttpServer(gScheme, gHost);
-  // The server will be run on a random port if the port number wasn't given.
-  gPort = gHttpServer.identity.primaryPort;
-
-  gTest = {
-    search: gHost.substr(0, 2),
-    autofilledValue: `${gHost}:${gPort}/`,
-  };
-
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.autoFill", true],
-          // Turn off search suggestion so we won't 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 and add it manually.
-  let oldSpeculativeConnect = gURLBar.popup.maybeSetupSpeculativeConnect.bind(gURLBar.popup);
-  let newSpeculativeConnect = (uriString) => {
-    gIsSpeculativeConnected = true;
-    oldSpeculativeConnect(uriString);
-  };
-  gURLBar.popup.maybeSetupSpeculativeConnect = newSpeculativeConnect;
-  gPrivateWin.gURLBar.popup.maybeSetupSpeculativeConnect = newSpeculativeConnect;
-
-  registerCleanupFunction(async function() {
-    await PlacesUtils.history.clear();
-    gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
-    gPrivateWin.gURLBar.popup.maybeSetupSpeculativeConnect = oldSpeculativeConnect;
-    gHttpServer.identity.remove(gScheme, gHost, gPort);
-    await new Promise(resolve => {
-      gHttpServer.stop(() => {
-        gHttpServer = null;
-        resolve();
-      });
-    });
-    await BrowserTestUtils.closeWindow(gPrivateWin);
-    gScheme = null;
-    gHost = null;
-    gPort = null;
-    gPrivateWin = null;
-    gIsSpeculativeConnected = null;
-    gTest = null;
-  });
-});
-
-add_task(async function autofill_tests() {
-  gIsSpeculativeConnected = false;
-  info(`Searching for '${gTest.search}'`);
-  await promiseAutocompleteResultPopup(gTest.search, window, true);
-  is(gURLBar.inputField.value, gTest.autofilledValue,
-     `Autofilled value is as expected for search '${gTest.search}'`);
-  is(gIsSpeculativeConnected, true, "Speculative connection should be called");
-  await promiseSpeculativeConnection(gHttpServer);
-  is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
-});
-
-add_task(async function privateContext_test() {
-  info("In private context.");
-  gIsSpeculativeConnected = false;
-  info(`Searching for '${gTest.search}'`);
-  await promiseAutocompleteResultPopup(gTest.search, gPrivateWin, true);
-  is(gPrivateWin.gURLBar.inputField.value, gTest.autofilledValue,
-     `Autofilled value is as expected for search '${gTest.search}'`);
-  is(gIsSpeculativeConnected, false, "Speculative connection shouldn't be called");
-});
deleted file mode 100644
--- a/browser/components/urlbar/tests/legacy/browser_urlbar_search_speculative_connect_engine.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/* 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 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() {
-  gHttpServer = runHttpServer(gScheme, gHost, gPort);
-
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.autoFill", true],
-          // Make sure search suggestion for location bar is enabled
-          ["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 SearchTestUtils.promiseNewSearchEngine(
-    getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
-
-  registerCleanupFunction(async function() {
-    await PlacesUtils.history.clear();
-    Services.search.defaultEngine = oldCurrentEngine;
-    gHttpServer.identity.remove(gScheme, gHost, gPort);
-    gHttpServer.stop(() => {
-      gHttpServer = null;
-    });
-  });
-});
-
-add_task(async function connect_search_engine_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);
-  is(gHttpServer.connectionNumber, 1, `${gHttpServer.connectionNumber} speculative connection has been setup.`);
-});