Bug 1658964 - Convert local restriction chars to search mode and make other improvements to search mode. r=harry
authorDrew Willcoxon <adw@mozilla.com>
Wed, 02 Sep 2020 00:52:12 +0000
changeset 547543 8c777bf670c9c47fd369db04ca551147e8b826a6
parent 547542 2efb7a933a9cd599121a9f85b6295d2fd73cb227
child 547544 da8cafa7dbb7773f1a43bbd0d1fe38b03baf3e68
push id125603
push userdwillcoxon@mozilla.com
push dateWed, 02 Sep 2020 18:29:33 +0000
treeherderautoland@8c777bf670c9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersharry
bugs1658964, 1658605
milestone82.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 1658964 - Convert local restriction chars to search mode and make other improvements to search mode. r=harry Summary of major changes: * Bookmarks, history, and tabs restriction chars now enter search mode. I added a method to UrlbarProviderHeuristicFallback to return a result with a keyword when one of these is used. * This fixes other bugs like recognizing aliases that are entered at the beginning of non-empty search strings, and not quasi-re-entering search mode when search mode is already entered and you type another alias. * The heuristic now determines whether we enter search mode, similar to how it also determines whether we autofill. When the heuristic has a keyword but no keyword offer, and the keyword is one of the recognized search mode keywords, then we enter search mode, cancel the current query, and start a new query with the remainder of the search string after the keyword. * I slightly changed how we detect an alias, but only when update2 is enabled. Now, an alias must be followed by a space; otherwise, the alias is not recognized and instead just remains part of the seach string. Because if we don't do that, then you end up in a strange situation after typing an alias but before pressing space: The heuristic says "Search with <engine with the alias>", but we haven't entered search mode yet because you haven't typed a space yet. This is true for both @aliaes and non-@aliases. * A consequence of the previous point is that we can still autofill @aliases with a trailing space, which IMO is important. Then, once the user types any char (space or not), we immediately enter search mode with the query being whatever char they typed. This is less important after bug 1658605 landed, but it's still good to have. * Previously, `UrlbarView.onQueryResults` called UrlbarInput in order to autofill after the first result is received. This is circuitous becaue the input already has an `onFirstResult` method, which I now use to enter search mode when appropriate. So I moved the autofill call from UrlbarView to `UrlbarInput.onFirstResult`. * As I mentioned, I improved some test framework and simplified some related product (non-test) code. For example: * I removed `UrlbarUtils.KEYWORD_OFFER.NONE` in favor of just leaving `keywordOffer` as `undefined`. * `tailOffsetIndex` can now be `undefined` if it's not relevant. * I removed empty-string `icon` properties from payloads in favor of `undefined`. * In tests, I ignore `undefined` but present properties in payloads so they don't count when comparing payloads with `deepEqual`. * We weren't previously comparing `result.source` and `result.type` in xpcshell tests, and that's important IMO, so I added checks for those and updated tests. * `isSearchHistory` is redundant, so I removed it. For form history, we should be checking `result.source == HISTORY` and `result.type == SEARCH`. * A bunch of tests needed to be updated for this new behavior. Differential Revision: https://phabricator.services.mozilla.com/D87944
browser/components/extensions/test/xpcshell/test_ext_urlbar.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
browser/components/urlbar/UrlbarController.jsm
browser/components/urlbar/UrlbarInput.jsm
browser/components/urlbar/UrlbarProviderAutofill.jsm
browser/components/urlbar/UrlbarProviderHeuristicFallback.jsm
browser/components/urlbar/UrlbarProviderPrivateSearch.jsm
browser/components/urlbar/UrlbarProviderSearchSuggestions.jsm
browser/components/urlbar/UrlbarProviderTokenAliasEngines.jsm
browser/components/urlbar/UrlbarProviderTopSites.jsm
browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
browser/components/urlbar/UrlbarProvidersManager.jsm
browser/components/urlbar/UrlbarSearchUtils.jsm
browser/components/urlbar/UrlbarTokenizer.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_action_searchengine.js
browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
browser/components/urlbar/tests/browser/browser_action_searchengine_alias_legacy.js
browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js
browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js
browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js
browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js
browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js
browser/components/urlbar/tests/browser/browser_searchMode_no_results.js
browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js
browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js
browser/components/urlbar/tests/browser/browser_tokenAlias.js
browser/components/urlbar/tests/browser/browser_tokenAlias_legacy.js
browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry.js
browser/components/urlbar/tests/browser/head.js
browser/components/urlbar/tests/unit/head.js
browser/components/urlbar/tests/unit/test_autofill_origins.js
browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js
browser/components/urlbar/tests/unit/test_autofill_search_engine_aliases.js
browser/components/urlbar/tests/unit/test_autofill_search_engines.js
browser/components/urlbar/tests/unit/test_autofill_urls.js
browser/components/urlbar/tests/unit/test_avoid_middle_complete.js
browser/components/urlbar/tests/unit/test_avoid_stripping_to_empty_tokens.js
browser/components/urlbar/tests/unit/test_casing.js
browser/components/urlbar/tests/unit/test_providerHeuristicFallback.js
browser/components/urlbar/tests/unit/test_search_suggestions.js
browser/components/urlbar/tests/unit/test_search_suggestions_aliases.js
browser/components/urlbar/tests/unit/test_trimming.js
browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
browser/modules/test/browser/browser_UsageTelemetry_urlbar_searchmode.js
toolkit/components/places/UnifiedComplete.jsm
toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias_legacy.js
toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
--- a/browser/components/extensions/test/xpcshell/test_ext_urlbar.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_urlbar.js
@@ -32,16 +32,26 @@ function promiseUninstallCompleted(exten
     ExtensionParent.apiManager.on("uninstall-complete", (type, { id }) => {
       if (id === extensionId) {
         executeSoon(resolve);
       }
     });
   });
 }
 
+function getPayload(result) {
+  let payload = {};
+  for (let [key, value] of Object.entries(result.payload)) {
+    if (value !== undefined) {
+      payload[key] = value;
+    }
+  }
+  return payload;
+}
+
 const ORIGINAL_NOTIFICATION_TIMEOUT =
   UrlbarProviderExtension.notificationTimeout;
 
 add_task(async function startup() {
   Services.prefs.setCharPref("browser.search.region", "US");
   Services.prefs.setIntPref("browser.search.addonLoadTimeout", 0);
   Services.prefs.setBoolPref(
     "browser.search.separatePrivateDefault.ui.enabled",
@@ -279,24 +289,16 @@ add_task(async function test_onProviderR
     {
       type: UrlbarUtils.RESULT_TYPE.SEARCH,
       source: UrlbarUtils.RESULT_SOURCE.SEARCH,
       title: "test",
       heuristic: true,
       payload: {
         query: "test",
         engine: "Test engine",
-        suggestion: undefined,
-        tailPrefix: undefined,
-        tail: undefined,
-        tailOffsetIndex: -1,
-        keyword: undefined,
-        isSearchHistory: false,
-        icon: "",
-        keywordOffer: false,
       },
     },
     // The second result should be our search suggestion result since the
     // default muxer sorts search suggestion results before other types.
     {
       type: UrlbarUtils.RESULT_TYPE.SEARCH,
       source: UrlbarUtils.RESULT_SOURCE.SEARCH,
       title: "Test search-search result",
@@ -358,17 +360,17 @@ add_task(async function test_onProviderR
   ];
 
   Assert.ok(context.results.every(r => r.suggestedIndex == -1));
   let actualResults = context.results.map(r => ({
     type: r.type,
     source: r.source,
     title: r.title,
     heuristic: r.heuristic,
-    payload: r.payload,
+    payload: getPayload(r),
   }));
 
   Assert.deepEqual(actualResults, expectedResults);
 
   await ext.unload();
 });
 
 // Extensions can specify search engines using engine names, aliases, and URLs.
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
@@ -1,15 +1,23 @@
 /* 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/. */
 
-const { UrlbarTestUtils } = ChromeUtils.import(
-  "resource://testing-common/UrlbarTestUtils.jsm"
-);
+XPCOMUtils.defineLazyModuleGetters(this, {
+  UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
+  const { UrlbarTestUtils: module } = ChromeUtils.import(
+    "resource://testing-common/UrlbarTestUtils.jsm"
+  );
+  module.init(this);
+  return module;
+});
 
 /**
  * Clicks the given link and checks this opens the given URI in the same tab.
  *
  * This function does not return to the previous page.
  */
 async function testLinkOpensUrl({ win, tab, elementId, expectedUrl }) {
   let loadedPromise = BrowserTestUtils.browserLoaded(tab);
@@ -89,16 +97,80 @@ add_task(async function test_search_icon
 
   await BrowserTestUtils.closeWindow(win);
 });
 
 /**
  * Tests the search hand-off on character keydown in "about:privatebrowsing".
  */
 add_task(async function test_search_handoff_on_keydown() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", true]],
+  });
+
+  let { win, tab } = await openAboutPrivateBrowsing();
+
+  await SpecialPowers.spawn(tab, [], async function() {
+    let btn = content.document.getElementById("search-handoff-button");
+    btn.click();
+    ok(btn.classList.contains("focused"), "in-content search has focus styles");
+  });
+  ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
+
+  // Expect two searches, one to enter search mode and then another in search
+  // mode.
+  let searchPromise = UrlbarTestUtils.promiseSearchComplete(win, 2);
+
+  await new Promise(r => EventUtils.synthesizeKey("f", {}, win, r));
+  await SpecialPowers.spawn(tab, [], async function() {
+    ok(
+      content.document
+        .getElementById("search-handoff-button")
+        .classList.contains("hidden"),
+      "in-content search is hidden"
+    );
+  });
+  await searchPromise;
+  ok(urlBarHasNormalFocus(win), "url bar has normal focused");
+  await UrlbarTestUtils.assertSearchMode(win, {
+    engineName: "DuckDuckGo",
+    source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+    entry: "typed",
+  });
+  is(win.gURLBar.value, "f", "url bar has search text");
+
+  // Close the popup.
+  await UrlbarTestUtils.exitSearchMode(win);
+  await UrlbarTestUtils.promisePopupClose(win);
+
+  // Hitting ESC should reshow the in-content search
+  await new Promise(r => EventUtils.synthesizeKey("KEY_Escape", {}, win, r));
+  await SpecialPowers.spawn(tab, [], async function() {
+    ok(
+      !content.document
+        .getElementById("search-handoff-button")
+        .classList.contains("hidden"),
+      "in-content search is not hidden"
+    );
+  });
+
+  await BrowserTestUtils.closeWindow(win);
+  await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * This task can be removed when browser.urlbar.update2 is enabled by default.
+ *
+ * Tests the search hand-off on character keydown in "about:privatebrowsing".
+ */
+add_task(async function test_search_handoff_on_keydown_legacy() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", false]],
+  });
+
   let { win, tab } = await openAboutPrivateBrowsing();
 
   await SpecialPowers.spawn(tab, [], async function() {
     let btn = content.document.getElementById("search-handoff-button");
     btn.click();
     ok(btn.classList.contains("focused"), "in-content search has focus styles");
   });
   ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
@@ -124,16 +196,17 @@ add_task(async function test_search_hand
       !content.document
         .getElementById("search-handoff-button")
         .classList.contains("hidden"),
       "in-content search is not hidden"
     );
   });
 
   await BrowserTestUtils.closeWindow(win);
+  await SpecialPowers.popPrefEnv();
 });
 
 /**
  * Tests the search hand-off on composition start in "about:privatebrowsing".
  */
 add_task(async function test_search_handoff_on_composition_start() {
   let { win, tab } = await openAboutPrivateBrowsing();
 
@@ -148,16 +221,63 @@ add_task(async function test_search_hand
 
   await BrowserTestUtils.closeWindow(win);
 });
 
 /**
  * Tests the search hand-off on paste in "about:privatebrowsing".
  */
 add_task(async function test_search_handoff_on_paste() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", true]],
+  });
+
+  let { win, tab } = await openAboutPrivateBrowsing();
+
+  await SpecialPowers.spawn(tab, [], async function() {
+    content.document.getElementById("search-handoff-button").click();
+  });
+  ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
+  var helper = SpecialPowers.Cc[
+    "@mozilla.org/widget/clipboardhelper;1"
+  ].getService(SpecialPowers.Ci.nsIClipboardHelper);
+  helper.copyString("words");
+
+  // Expect two searches, one to enter search mode and then another in search
+  // mode.
+  let searchPromise = UrlbarTestUtils.promiseSearchComplete(win, 2);
+
+  await new Promise(r =>
+    EventUtils.synthesizeKey("v", { accelKey: true }, win, r)
+  );
+
+  await searchPromise;
+
+  ok(urlBarHasNormalFocus(win), "url bar has normal focused");
+  await UrlbarTestUtils.assertSearchMode(win, {
+    engineName: "DuckDuckGo",
+    source: UrlbarUtils.RESULT_SOURCE.SEARCH,
+    entry: "typed",
+  });
+  is(win.gURLBar.value, "words", "url bar has search text");
+
+  await BrowserTestUtils.closeWindow(win);
+  await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * This task can be removed when browser.urlbar.update2 is enabled by default.
+ *
+ * Tests the search hand-off on paste in "about:privatebrowsing".
+ */
+add_task(async function test_search_handoff_on_paste_legacy() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", false]],
+  });
+
   let { win, tab } = await openAboutPrivateBrowsing();
 
   await SpecialPowers.spawn(tab, [], async function() {
     content.document.getElementById("search-handoff-button").click();
   });
   ok(urlBarHasHiddenFocus(win), "url bar has hidden focused");
   var helper = SpecialPowers.Cc[
     "@mozilla.org/widget/clipboardhelper;1"
@@ -172,9 +292,10 @@ add_task(async function test_search_hand
   ok(urlBarHasNormalFocus(win), "url bar has normal focused");
   is(
     win.gURLBar.value,
     `${expectedEngineAlias} words`,
     "url bar has search text"
   );
 
   await BrowserTestUtils.closeWindow(win);
+  await SpecialPowers.popPrefEnv();
 });
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -174,20 +174,23 @@ class UrlbarController {
   receiveResults(queryContext) {
     if (queryContext.lastResultCount < 1 && queryContext.results.length >= 1) {
       TelemetryStopwatch.finish(TELEMETRY_1ST_RESULT, queryContext);
     }
     if (queryContext.lastResultCount < 6 && queryContext.results.length >= 6) {
       TelemetryStopwatch.finish(TELEMETRY_6_FIRST_RESULTS, queryContext);
     }
 
-    if (queryContext.lastResultCount == 0 && queryContext.results.length) {
-      if (queryContext.results[0].autofill) {
-        this.input.autofillFirstResult(queryContext.results[0]);
+    if (queryContext.firstResultChanged) {
+      // Notify the input so it can make adjustments based on the first result.
+      if (this.input.onFirstResult(queryContext.results[0])) {
+        // The input canceled the query and started a new one.
+        return;
       }
+
       // The first time we receive results try to connect to the heuristic
       // result.
       this.speculativeConnect(
         queryContext.results[0],
         queryContext,
         "resultsadded"
       );
     }
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -987,17 +987,17 @@ class UrlbarInput {
   /**
    * Called by the controller when the first result of a new search is received.
    * If it's an autofill result, then it may need to be autofilled, subject to a
    * few restrictions.
    *
    * @param {UrlbarResult} result
    *   The first result.
    */
-  autofillFirstResult(result) {
+  _autofillFirstResult(result) {
     if (!result.autofill) {
       return;
     }
 
     let isPlaceholderSelected =
       this.selectionEnd == this._autofillPlaceholder.length &&
       this.selectionStart == this._lastSearchString.length &&
       this._autofillPlaceholder
@@ -1015,39 +1015,58 @@ class UrlbarInput {
     ) {
       return;
     }
 
     this.setValueFromResult(result);
   }
 
   /**
-   * Invoked by the view when the first result is received.
-   * @param {UrlbarResult} firstResult The first result received.
+   * Invoked by the controller when the first result is received.
+   *
+   * @param {UrlbarResult} firstResult
+   *   The first result received.
+   * @returns {boolean}
+   *   True if this method canceled the query and started a new one.  False
+   *   otherwise.
    */
   onFirstResult(firstResult) {
+    // If the heuristic result has a keyword but isn't a keyword offer, we may
+    // need to enter search mode.
+    if (
+      firstResult.heuristic &&
+      firstResult.payload.keyword &&
+      !firstResult.payload.keywordOffer &&
+      this.maybePromoteKeywordToSearchMode(firstResult, false)
+    ) {
+      return true;
+    }
+
     // To prevent selection flickering, we apply autofill on input through a
     // placeholder, without waiting for results. But, if the first result is
     // not an autofill one, the autofill prediction was wrong and we should
     // restore the original user typed string.
-    if (
+    if (firstResult.autofill) {
+      this._autofillFirstResult(firstResult);
+    } else if (
       this._autofillPlaceholder &&
-      !firstResult.autofill &&
       // Avoid clobbering added spaces (for token aliases, for example).
       !this.value.endsWith(" ")
     ) {
       this._setValue(this.window.gBrowser.userTypedValue, false);
     }
 
     // Heuristic tip results do not set the Urlbar value.
     if (firstResult.type == UrlbarUtils.RESULT_TYPE.TIP) {
       this._resultForCurrentValue = null;
     } else if (firstResult.heuristic) {
       this._resultForCurrentValue = firstResult;
     }
+
+    return false;
   }
 
   /**
    * Starts a query based on the current input value.
    *
    * @param {boolean} [options.allowAutofill]
    *   Whether or not to allow providers to include autofill results.
    * @param {boolean} [options.autofillIgnoresSelection]
@@ -1506,28 +1525,44 @@ class UrlbarInput {
    * tab, we end up flickering the results pane briefly.
    */
   afterTabSwitchFocusChange() {
     this._gotFocusChange = true;
     this._afterTabSelectAndFocusChange();
   }
 
   /**
-   * Certain actions can autofill a keyword into searchMode
-   * @param {UrlbarResult} [result] The currently selected urlbar result.
-   * @returns {boolean} Whether Search Mode was started.
+   * Enters search mode and starts a new search if appropriate for the given
+   * result.  See also _searchModeForResult.
+   *
+   * @param {UrlbarResult} result
+   *   The currently selected urlbar result.
+   * @param {boolean} checkValue
+   *   If true, the trimmed input value must equal the result's keyword in order
+   *   to enter search mode.
+   * @returns {boolean}
+   *   True if we entered search mode and false if not.
    */
-  maybePromoteKeywordToSearchMode(result = this._resultForCurrentValue) {
+  maybePromoteKeywordToSearchMode(
+    result = this._resultForCurrentValue,
+    checkValue = true
+  ) {
+    if (checkValue && this.value.trim() != result.payload.keyword?.trim()) {
+      return false;
+    }
+
     let searchMode = this._searchModeForResult(result, "typed");
-    if (searchMode && this.value.trim() == result.payload.keyword.trim()) {
-      this.setSearchMode(searchMode);
-      this.value = "";
-      return true;
+    if (!searchMode) {
+      return false;
     }
-    return false;
+
+    this.setSearchMode(searchMode);
+    this._setValue(result.payload.query?.trimStart() || "", false);
+    this.startQuery({ allowAutofill: false });
+    return true;
   }
 
   // Private methods below.
 
   _getURIFixupInfo(searchString) {
     let flags =
       Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
       Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
@@ -2237,41 +2272,57 @@ class UrlbarInput {
    * @returns {object} A search mode object. Null if search mode should not be
    *   entered. See setSearchMode documentation for details.
    */
   _searchModeForResult(result, entry = null) {
     if (!UrlbarPrefs.get("update2")) {
       return null;
     }
 
-    // If result.originalEngine is set, then the user is Alt+Tabbing through the
-    // one-offs, so the keyword doesn't match the engine.
-    if (
-      result &&
-      result.payload.keywordOffer &&
-      (!result.payload.originalEngine ||
-        result.payload.engine == result.payload.originalEngine)
-    ) {
-      let searchModeEntry;
+    // Search mode is determined by the result's keyword.
+    if (!result.payload.keyword) {
+      return null;
+    }
+
+    let searchMode = null;
+    switch (result.payload.keyword) {
+      case UrlbarTokenizer.RESTRICT.BOOKMARK:
+        searchMode = { source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS };
+        break;
+      case UrlbarTokenizer.RESTRICT.HISTORY:
+        searchMode = { source: UrlbarUtils.RESULT_SOURCE.HISTORY };
+        break;
+      case UrlbarTokenizer.RESTRICT.OPENPAGE:
+        searchMode = { source: UrlbarUtils.RESULT_SOURCE.TABS };
+        break;
+      default:
+        // If result.originalEngine is set, then the user is Alt+Tabbing
+        // through the one-offs, so the keyword doesn't match the engine.
+        if (
+          result.payload.engine &&
+          (!result.payload.originalEngine ||
+            result.payload.engine == result.payload.originalEngine)
+        ) {
+          searchMode = { engineName: result.payload.engine };
+        }
+        break;
+    }
+
+    if (searchMode) {
       if (entry) {
-        searchModeEntry = entry;
+        searchMode.entry = entry;
       } else {
-        searchModeEntry =
+        searchMode.entry =
           result.providerName == "UrlbarProviderTopSites"
             ? "topsites_urlbar"
             : "keywordoffer";
       }
-
-      return {
-        engineName: result.payload.engine,
-        entry: searchModeEntry,
-      };
     }
 
-    return null;
+    return searchMode;
   }
 
   /**
    * Determines if we should select all the text in the Urlbar based on the
    *  Urlbar state, and whether the selection is empty.
    */
   _maybeSelectAll() {
     if (
@@ -2500,23 +2551,16 @@ class UrlbarInput {
         if (!UrlbarPrefs.get("ui.popup.disable_autohide")) {
           this.view.close();
         }
         break;
     }
   }
 
   _on_input(event) {
-    // We enter search mode when space is typed if there is a selected keyword
-    // offer result.
-    let enteredSearchMode = false;
-    if (event.data == " ") {
-      enteredSearchMode = this.maybePromoteKeywordToSearchMode();
-    }
-
     let value = this.value;
     this.valueIsTyped = true;
     this._untrimmedValue = value;
     this.window.gBrowser.userTypedValue = value;
     // Unset userSelectionBehavior because the user is modifying the search
     // string, thus there's no valid selection. This is also used by the view
     // to set "aria-activedescendant", thus it should never get stale.
     this.controller.userSelectionBehavior = "none";
@@ -2544,17 +2588,17 @@ class UrlbarInput {
       this.setPageProxyState("invalid", true);
     }
 
     let canShowTopSites =
       !this.isPrivate && UrlbarPrefs.get("suggest.topsites");
 
     if (!this.view.isOpen) {
       this.view.clear();
-    } else if (!value && !canShowTopSites && !enteredSearchMode) {
+    } else if (!value && !canShowTopSites) {
       this.view.clear();
       if (!this.searchMode) {
         this.view.close();
         return;
       }
     }
 
     this.view.removeAccessibleFocus();
--- a/browser/components/urlbar/UrlbarProviderAutofill.jsm
+++ b/browser/components/urlbar/UrlbarProviderAutofill.jsm
@@ -692,17 +692,17 @@ class ProviderAutofill extends UrlbarPro
     let value =
       this._strippedPrefix + domain.substr(domain.indexOf(searchStr)) + "/";
 
     let result = new UrlbarResult(
       UrlbarUtils.RESULT_TYPE.SEARCH,
       UrlbarUtils.RESULT_SOURCE.SEARCH,
       ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
         engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
-        icon: engine.iconURI ? engine.iconURI.spec : "",
+        icon: engine.iconURI?.spec,
       })
     );
     let autofilledValue =
       queryContext.searchString +
       value.substring(queryContext.searchString.length);
     result.autofill = {
       value: autofilledValue,
       selectionStart: queryContext.searchString.length,
--- a/browser/components/urlbar/UrlbarProviderHeuristicFallback.jsm
+++ b/browser/components/urlbar/UrlbarProviderHeuristicFallback.jsm
@@ -25,16 +25,26 @@ XPCOMUtils.defineLazyModuleGetters(this,
 });
 
 /**
  * Class used to create the provider.
  */
 class ProviderHeuristicFallback extends UrlbarProvider {
   constructor() {
     super();
+
+    // The Set of local search mode keywords/restriction characters.  We use
+    // this to quickly look them up.
+    XPCOMUtils.defineLazyGetter(this, "_localSearchModeKeywords", () => {
+      return new Set(
+        [...UrlbarTokenizer.SEARCH_MODE_RESTRICT].map(
+          r => UrlbarTokenizer.RESTRICT[r]
+        )
+      );
+    });
   }
 
   /**
    * Returns the name of this provider.
    * @returns {string} the name of this provider.
    */
   get name() {
     return "HeuristicFallback";
@@ -100,21 +110,30 @@ class ProviderHeuristicFallback extends 
             queryContext
           );
           if (instance != this.queryInstance) {
             return;
           }
           addCallback(this, searchResult);
         }
       }
-    } else {
-      result = await this._defaultEngineSearchResult(queryContext);
-      if (!result || instance != this.queryInstance) {
-        return;
-      }
+      return;
+    }
+
+    result = this._localSearchModeKeywordResult(queryContext);
+    if (result) {
+      addCallback(this, result);
+      return;
+    }
+
+    result = await this._defaultEngineSearchResult(queryContext);
+    if (instance != this.queryInstance) {
+      return;
+    }
+    if (result) {
       result.heuristic = true;
       addCallback(this, result);
     }
   }
 
   // TODO (bug 1054814): Use visited URLs to inform which scheme to use, if the
   // scheme isn't specificed.
   _matchUnknownUrl(queryContext) {
@@ -147,17 +166,16 @@ class ProviderHeuristicFallback extends 
         !UrlbarPrefs.get("keyword.enabled")
       ) {
         let result = new UrlbarResult(
           UrlbarUtils.RESULT_TYPE.URL,
           UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
             title: [searchUrl, UrlbarUtils.HIGHLIGHT.TYPED],
             url: [searchUrl, UrlbarUtils.HIGHLIGHT.TYPED],
-            icon: "",
           })
         );
         result.heuristic = true;
         return result;
       }
 
       return null;
     }
@@ -191,17 +209,17 @@ class ProviderHeuristicFallback extends 
     let displayURL = decodeURI(uri);
 
     // We don't know if this url is in Places or not, and checking that would
     // be expensive. Thus we also don't know if we may have an icon.
     // If we'd just try to fetch the icon for the typed string, we'd cause icon
     // flicker, since the url keeps changing while the user types.
     // By default we won't provide an icon, but for the subset of urls with a
     // host we'll check for a typed slash and set favicon for the host part.
-    let iconUri = "";
+    let iconUri;
     if (hostExpected && (searchUrl.endsWith("/") || uri.pathname.length > 1)) {
       // Look for an icon with the entire URL except for the pathname, including
       // scheme, usernames, passwords, hostname, and port.
       let pathIndex = uri.toString().lastIndexOf(uri.pathname);
       let prePath = uri.toString().slice(0, pathIndex);
       iconUri = `page-icon:${prePath}/`;
     }
 
@@ -213,16 +231,67 @@ class ProviderHeuristicFallback extends 
         url: [escapedURL, UrlbarUtils.HIGHLIGHT.TYPED],
         icon: iconUri,
       })
     );
     result.heuristic = true;
     return result;
   }
 
+  _localSearchModeKeywordResult(queryContext) {
+    if (!UrlbarPrefs.get("update2")) {
+      return null;
+    }
+
+    if (!queryContext.tokens.length) {
+      return null;
+    }
+
+    let firstToken = queryContext.tokens[0].value;
+    if (!this._localSearchModeKeywords.has(firstToken)) {
+      return null;
+    }
+
+    // At this point, the search string starts with a local search mode token.
+    // Now we need to determine what to do based on the remainder of the search
+    // string.  If the remainder starts with a space, then we should enter
+    // search mode, so we should continue below and create the result.
+    // Otherwise, we should not enter search mode, and in that case, the search
+    // string will look like one of the following:
+    //
+    // * The search string ends with the local search mode token (e.g., the user
+    //   has typed only the token by itself, with no trailing spaces).
+    // * More tokens exist, but there's no space between the local search mode
+    //   token and the following token.  This is possible because the tokenizer
+    //   does not require spaces between a restriction token and the remainder
+    //   of the search string.  In this case, we should not enter search mode.
+    //
+    // If we return null here and thereby do not enter search mode, then we'll
+    // continue on to _defaultEngineSearchResult, and the heuristic will be a
+    // default engine search result.
+    let query = UrlbarUtils.substringAfter(
+      queryContext.searchString,
+      firstToken
+    );
+    if (!query.startsWith(" ")) {
+      return null;
+    }
+
+    let result = new UrlbarResult(
+      UrlbarUtils.RESULT_TYPE.SEARCH,
+      UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+      ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
+        query: [query.trimStart(), UrlbarUtils.HIGHLIGHT.NONE],
+        keyword: [firstToken, UrlbarUtils.HIGHLIGHT.NONE],
+      })
+    );
+    result.heuristic = true;
+    return result;
+  }
+
   async _defaultEngineSearchResult(queryContext) {
     let engine;
     if (queryContext.searchMode?.engineName) {
       engine = Services.search.getEngineByName(
         queryContext.searchMode.engineName
       );
     } else if (queryContext.isPrivate) {
       engine = Services.search.defaultPrivateEngine;
@@ -244,32 +313,23 @@ class ProviderHeuristicFallback extends 
       queryContext.tokens[0].value === UrlbarTokenizer.RESTRICT.SEARCH
     ) {
       query = UrlbarUtils.substringAfter(
         query,
         queryContext.tokens[0].value
       ).trim();
     }
 
-    let result = new UrlbarResult(
+    return new UrlbarResult(
       UrlbarUtils.RESULT_TYPE.SEARCH,
       UrlbarUtils.RESULT_SOURCE.SEARCH,
       ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
         engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
-        icon: [engine.iconURI?.spec || ""],
+        icon: engine.iconURI?.spec,
         query: [query, UrlbarUtils.HIGHLIGHT.NONE],
         // We're confident that there is no alias, since UnifiedComplete
         // handles heuristic searches with aliases.
-        keyword: undefined,
-        keywordOffer: UrlbarUtils.KEYWORD_OFFER.NONE,
-        // For test interoperabilty with UrlbarProviderSearchSuggestions.
-        suggestion: undefined,
-        tailPrefix: undefined,
-        tail: undefined,
-        tailOffsetIndex: -1,
-        isSearchHistory: false,
       })
     );
-    return result;
   }
 }
 
 var UrlbarProviderHeuristicFallback = new ProviderHeuristicFallback();
--- a/browser/components/urlbar/UrlbarProviderPrivateSearch.jsm
+++ b/browser/components/urlbar/UrlbarProviderPrivateSearch.jsm
@@ -126,17 +126,17 @@ class ProviderPrivateSearch extends Urlb
     }
 
     let result = new UrlbarResult(
       UrlbarUtils.RESULT_TYPE.SEARCH,
       UrlbarUtils.RESULT_SOURCE.SEARCH,
       ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
         engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
         query: [searchString, UrlbarUtils.HIGHLIGHT.NONE],
-        icon: [engine.iconURI ? engine.iconURI.spec : null],
+        icon: engine.iconURI?.spec,
         inPrivateWindow: true,
         isPrivateEngine,
       })
     );
     result.suggestedIndex = 1;
     addCallback(this, result);
   }
 }
--- a/browser/components/urlbar/UrlbarProviderSearchSuggestions.jsm
+++ b/browser/components/urlbar/UrlbarProviderSearchSuggestions.jsm
@@ -421,22 +421,20 @@ class ProviderSearchSuggestions extends 
             UrlbarUtils.RESULT_TYPE.SEARCH,
             UrlbarUtils.RESULT_SOURCE.SEARCH,
             ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
               engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
               suggestion: [entry.value, UrlbarUtils.HIGHLIGHT.SUGGESTED],
               lowerCaseSuggestion: entry.value.toLocaleLowerCase(),
               tailPrefix,
               tail: [tail, UrlbarUtils.HIGHLIGHT.SUGGESTED],
-              tailOffsetIndex: entry.tailOffsetIndex,
+              tailOffsetIndex: tail ? entry.tailOffsetIndex : undefined,
               keyword: [alias ? alias : undefined, UrlbarUtils.HIGHLIGHT.TYPED],
               query: [searchString.trim(), UrlbarUtils.HIGHLIGHT.NONE],
-              isSearchHistory: false,
-              icon: [engine.iconURI && !entry.value ? engine.iconURI.spec : ""],
-              keywordOffer: UrlbarUtils.KEYWORD_OFFER.NONE,
+              icon: !entry.value ? engine.iconURI?.spec : undefined,
             })
           )
         );
         seenSuggestions.add(entry.value);
       } catch (err) {
         Cu.reportError(err);
         continue;
       }
@@ -460,62 +458,62 @@ class ProviderSearchSuggestions extends 
   /**
    * Searches for an engine alias given the queryContext.
    * @param {UrlbarQueryContext} queryContext
    * @returns {object} aliasEngine
    *   A representation of the aliased engine. Null if there's no match.
    * @returns {nsISearchEngine} aliasEngine.engine
    * @returns {string} aliasEngine.alias
    * @returns {string} aliasEngine.query
-   * @returns {boolean} aliasEngine.isTokenAlias
+   * @returns {object} { engine, alias, query }
    *
    */
   async _maybeGetAlias(queryContext) {
-    if (
-      queryContext.restrictSource &&
-      queryContext.restrictSource == UrlbarUtils.RESULT_SOURCE.SEARCH &&
-      queryContext.searchMode?.engineName &&
-      !queryContext.searchString.startsWith("@")
-    ) {
-      // If an engineName was passed in from the queryContext in restrict mode,
-      // we'll set our engine in startQuery based on engineName.
+    if (queryContext.searchMode) {
+      // If we're in search mode, don't try to parse an alias at all.
       return null;
     }
 
-    let possibleAlias = queryContext.tokens[0]?.value.trim();
-    // The "@" character on its own is handled by UnifiedComplete and returns a
-    // list of every available token alias.
+    let possibleAlias = queryContext.tokens[0]?.value;
+    // "@" on its own is handled by UrlbarProviderTokenAliasEngines and returns
+    // a list of every available token alias.
     if (!possibleAlias || possibleAlias == "@") {
       return null;
     }
 
+    let query = UrlbarUtils.substringAfter(
+      queryContext.searchString,
+      possibleAlias
+    );
+
+    // Match an alias only when it has a space after it.  If there's no trailing
+    // space, then continue to treat it as part of the search string.
+    if (UrlbarPrefs.get("update2") && !query.startsWith(" ")) {
+      return null;
+    }
+
     // Check if the user entered an engine alias directly.
     let engineMatch = await UrlbarSearchUtils.engineForAlias(possibleAlias);
     if (engineMatch) {
       return {
         engine: engineMatch,
         alias: possibleAlias,
-        query: UrlbarUtils.substringAfter(
-          queryContext.searchString,
-          possibleAlias
-        ).trim(),
-        isTokenAlias: possibleAlias.startsWith("@"),
+        query: query.trim(),
       };
     }
 
     return null;
   }
 }
 
 function makeFormHistoryResult(queryContext, engine, entry) {
   return new UrlbarResult(
     UrlbarUtils.RESULT_TYPE.SEARCH,
     UrlbarUtils.RESULT_SOURCE.HISTORY,
     ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
       engine: engine.name,
-      isSearchHistory: true,
       suggestion: [entry.value, UrlbarUtils.HIGHLIGHT.SUGGESTED],
       lowerCaseSuggestion: entry.value.toLocaleLowerCase(),
     })
   );
 }
 
 var UrlbarProviderSearchSuggestions = new ProviderSearchSuggestions();
--- a/browser/components/urlbar/UrlbarProviderTokenAliasEngines.jsm
+++ b/browser/components/urlbar/UrlbarProviderTokenAliasEngines.jsm
@@ -116,17 +116,17 @@ class ProviderTokenAliasEngines extends 
       for (let { engine, tokenAliases } of this._engines) {
         let result = new UrlbarResult(
           UrlbarUtils.RESULT_TYPE.SEARCH,
           UrlbarUtils.RESULT_SOURCE.SEARCH,
           ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
             engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
             keyword: [tokenAliases[0], UrlbarUtils.HIGHLIGHT.TYPED],
             query: ["", UrlbarUtils.HIGHLIGHT.TYPED],
-            icon: engine.iconURI ? engine.iconURI.spec : "",
+            icon: engine.iconURI?.spec,
             keywordOffer: UrlbarUtils.KEYWORD_OFFER.SHOW,
           })
         );
         addCallback(this, result);
       }
     } else if (
       this._autofillData &&
       this._autofillData.instance == this.queryInstance
@@ -151,43 +151,49 @@ class ProviderTokenAliasEngines extends 
    */
   cancelQuery(queryContext) {
     if (this._autofillData?.instance == this.queryInstance) {
       this._autofillData = null;
     }
   }
 
   _getAutofillResult(queryContext) {
+    let lowerCaseSearchString = queryContext.searchString.toLowerCase();
+
     // The user is typing a specific engine. We should show a heuristic result.
     for (let { engine, tokenAliases } of this._engines) {
       for (let alias of tokenAliases) {
-        if (alias.startsWith(queryContext.searchString.toLowerCase())) {
-          // We found a specific engine. We will add an autofill result.
+        if (alias.startsWith(lowerCaseSearchString)) {
+          // We found the engine.
+
+          // Stop adding an autofill result once the user has typed the full
+          // alias followed by a space.  UrlbarProviderUnifiedComplete will take
+          // over at this point.
+          if (
+            UrlbarPrefs.get("update2") &&
+            lowerCaseSearchString.startsWith(alias + " ")
+          ) {
+            return null;
+          }
+
+          // Add an autofill result.  Append a space so the user can hit enter
+          // or the right arrow key and immediately start typing their query.
           let aliasPreservingUserCase =
             queryContext.searchString +
             alias.substr(queryContext.searchString.length);
-          // Don't append a space if update2 is on since selecting this result
-          // will just enter search mode.
-          let value =
-            aliasPreservingUserCase + (UrlbarPrefs.get("update2") ? "" : " ");
+          let value = aliasPreservingUserCase + " ";
           let result = new UrlbarResult(
             UrlbarUtils.RESULT_TYPE.SEARCH,
             UrlbarUtils.RESULT_SOURCE.SEARCH,
             ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
               engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED],
               keyword: [aliasPreservingUserCase, UrlbarUtils.HIGHLIGHT.TYPED],
               query: ["", UrlbarUtils.HIGHLIGHT.TYPED],
-              icon: engine.iconURI ? engine.iconURI.spec : "",
+              icon: engine.iconURI?.spec,
               keywordOffer: UrlbarUtils.KEYWORD_OFFER.HIDE,
-              // For test interoperabilty with UrlbarProviderSearchSuggestions.
-              suggestion: undefined,
-              tailPrefix: undefined,
-              tail: undefined,
-              tailOffsetIndex: -1,
-              isSearchHistory: false,
             })
           );
           result.heuristic = true;
           result.autofill = {
             value,
             selectionStart: queryContext.searchString.length,
             selectionEnd: value.length,
           };
--- a/browser/components/urlbar/UrlbarProviderTopSites.jsm
+++ b/browser/components/urlbar/UrlbarProviderTopSites.jsm
@@ -139,17 +139,17 @@ class ProviderTopSites extends UrlbarPro
     sites = sites.map(link => ({
       type: link.searchTopSite ? "search" : "url",
       url: link.url_urlbar || link.url,
       isPinned: link.isPinned,
       // The newtab page allows the user to set custom site titles, which
       // are stored in `label`, so prefer it.  Search top sites currently
       // don't have titles but `hostname` instead.
       title: link.label || link.title || link.hostname || "",
-      favicon: link.smallFavicon || link.favicon || null,
+      favicon: link.smallFavicon || link.favicon || undefined,
       sendAttributionRequest: link.sendAttributionRequest,
     }));
 
     for (let site of sites) {
       switch (site.type) {
         case "url": {
           let result = new UrlbarResult(
             UrlbarUtils.RESULT_TYPE.URL,
--- a/browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
+++ b/browser/components/urlbar/UrlbarProviderUnifiedComplete.jsm
@@ -127,17 +127,20 @@ function convertLegacyAutocompleteResult
     if (urls.has(url)) {
       continue;
     }
     urls.add(url);
     let style = acResult.getStyleAt(i);
     let isHeuristic = i == 0 && style.includes("heuristic");
     let result = makeUrlbarResult(context.tokens, {
       url,
-      icon: acResult.getImageAt(i),
+      // getImageAt returns an empty string if there is no icon.  Use undefined
+      // instead so that tests can be simplified by not including `icon: ""` in
+      // all their payloads.
+      icon: acResult.getImageAt(i) || undefined,
       style,
       comment: acResult.getCommentAt(i),
       firstToken: context.tokens[0],
       isHeuristic,
     });
     // Should not happen, but better safe than sorry.
     if (!result) {
       continue;
@@ -175,46 +178,70 @@ function convertLegacyAutocompleteResult
  * @param {object} info includes properties from the legacy result.
  * @returns {object} an UrlbarResult
  */
 function makeUrlbarResult(tokens, info) {
   let action = PlacesUtils.parseActionUrl(info.url);
   if (action) {
     switch (action.type) {
       case "searchengine": {
-        let keywordOffer = UrlbarUtils.KEYWORD_OFFER.NONE;
+        if (action.params.isSearchHistory) {
+          // Return a form history result.
+          return new UrlbarResult(
+            UrlbarUtils.RESULT_TYPE.SEARCH,
+            UrlbarUtils.RESULT_SOURCE.HISTORY,
+            ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
+              engine: action.params.engineName,
+              suggestion: [
+                action.params.searchSuggestion,
+                UrlbarUtils.HIGHLIGHT.SUGGESTED,
+              ],
+              lowerCaseSuggestion: action.params.searchSuggestion.toLocaleLowerCase(),
+            })
+          );
+        }
+
+        let keywordOffer;
         if (
-          action.params.alias &&
-          !action.params.searchQuery.trim() &&
-          (UrlbarPrefs.get("update2") || action.params.alias.startsWith("@"))
+          !UrlbarPrefs.get("update2") &&
+          action.params.alias?.startsWith("@") &&
+          !action.params.searchQuery.trim()
         ) {
-          keywordOffer = info.isHeuristic
-            ? UrlbarUtils.KEYWORD_OFFER.HIDE
-            : UrlbarUtils.KEYWORD_OFFER.SHOW;
+          // This conditional is true only for the heuristic result, when the
+          // search string is "@alias" followed by any number of spaces.  There
+          // are only three other cases where results will have a token alias
+          // and empty search string: When autofilling a token alias, which
+          // UrlbarProviderTokenAliasEngines handles; when the search string is
+          // "@" and we show all token aliases, which
+          // UrlbarProviderTokenAliasEngines also handles; and when a top site
+          // is an alias, which UrlbarProviderTopSites handles.
+          //
+          // When update2 is disabled, we want this result to be a keyword offer
+          // so that the user can pick it and it behaves like an autofilled
+          // @alias result.  The keyword should be hidden.  When update2 is
+          // enabled, we want it not to be an offer so that it causes the input
+          // to enter search mode.
+          keywordOffer = UrlbarUtils.KEYWORD_OFFER.HIDE;
         }
         return new UrlbarResult(
           UrlbarUtils.RESULT_TYPE.SEARCH,
           UrlbarUtils.RESULT_SOURCE.SEARCH,
           ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
             engine: [action.params.engineName, UrlbarUtils.HIGHLIGHT.TYPED],
             suggestion: [
               action.params.searchSuggestion,
               UrlbarUtils.HIGHLIGHT.SUGGESTED,
             ],
-            // For test interoperabilty with UrlbarProviderSearchSuggestions.
-            tailPrefix: undefined,
-            tail: undefined,
-            tailOffsetIndex: -1,
-            keyword: [action.params.alias, UrlbarUtils.HIGHLIGHT.TYPED],
+            lowerCaseSuggestion: action.params.searchSuggestion?.toLocaleLowerCase(),
+            keyword: action.params.alias,
             query: [
               action.params.searchQuery.trim(),
               UrlbarUtils.HIGHLIGHT.NONE,
             ],
-            isSearchHistory: !!action.params.isSearchHistory,
-            icon: [info.icon],
+            icon: info.icon,
             keywordOffer,
           })
         );
       }
       case "keyword": {
         let title = info.comment;
         if (!title) {
           // If the url doesn't have an host (e.g. javascript urls), comment
@@ -236,64 +263,64 @@ function makeUrlbarResult(tokens, info) 
           UrlbarUtils.RESULT_TYPE.KEYWORD,
           UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
           ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
             title: [title, UrlbarUtils.HIGHLIGHT.TYPED],
             url: [action.params.url, UrlbarUtils.HIGHLIGHT.TYPED],
             keyword: [info.firstToken.value, UrlbarUtils.HIGHLIGHT.TYPED],
             input: [action.params.input],
             postData: [action.params.postData],
-            icon: [info.icon],
+            icon: info.icon,
           })
         );
       }
       case "remotetab":
         return new UrlbarResult(
           UrlbarUtils.RESULT_TYPE.REMOTE_TAB,
           UrlbarUtils.RESULT_SOURCE.TABS,
           ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
             url: [action.params.url, UrlbarUtils.HIGHLIGHT.TYPED],
             title: [info.comment, UrlbarUtils.HIGHLIGHT.TYPED],
             device: [action.params.deviceName, UrlbarUtils.HIGHLIGHT.TYPED],
-            icon: [info.icon],
+            icon: info.icon,
           })
         );
       case "switchtab":
         return new UrlbarResult(
           UrlbarUtils.RESULT_TYPE.TAB_SWITCH,
           UrlbarUtils.RESULT_SOURCE.TABS,
           ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
             url: [action.params.url, UrlbarUtils.HIGHLIGHT.TYPED],
             title: [info.comment, UrlbarUtils.HIGHLIGHT.TYPED],
-            icon: [info.icon],
+            icon: info.icon,
           })
         );
       case "visiturl":
         return new UrlbarResult(
           UrlbarUtils.RESULT_TYPE.URL,
           UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
             title: [info.comment, UrlbarUtils.HIGHLIGHT.TYPED],
             url: [action.params.url, UrlbarUtils.HIGHLIGHT.TYPED],
-            icon: [info.icon],
+            icon: info.icon,
           })
         );
       default:
         Cu.reportError(`Unexpected action type: ${action.type}`);
         return null;
     }
   }
 
   if (info.style.includes("priority-search")) {
     return new UrlbarResult(
       UrlbarUtils.RESULT_TYPE.SEARCH,
       UrlbarUtils.RESULT_SOURCE.SEARCH,
       ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
         engine: [info.comment, UrlbarUtils.HIGHLIGHT.TYPED],
-        icon: [info.icon],
+        icon: info.icon,
       })
     );
   }
 
   // This is a normal url/title tuple.
   let source;
   let tags = [];
   let comment = info.comment;
@@ -335,14 +362,14 @@ function makeUrlbarResult(tokens, info) 
       .sort();
   }
 
   return new UrlbarResult(
     UrlbarUtils.RESULT_TYPE.URL,
     source,
     ...UrlbarResult.payloadAndSimpleHighlights(tokens, {
       url: [info.url, UrlbarUtils.HIGHLIGHT.TYPED],
-      icon: [info.icon],
+      icon: info.icon,
       title: [comment, UrlbarUtils.HIGHLIGHT.TYPED],
       tags: [tags, UrlbarUtils.HIGHLIGHT.TYPED],
     })
   );
 }
--- a/browser/components/urlbar/UrlbarProvidersManager.jsm
+++ b/browser/components/urlbar/UrlbarProvidersManager.jsm
@@ -10,16 +10,17 @@
  */
 
 var EXPORTED_SYMBOLS = ["UrlbarProvidersManager"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
+  ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   SkippableTimer: "resource:///modules/UrlbarUtils.jsm",
   UrlbarMuxer: "resource:///modules/UrlbarUtils.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
   UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
@@ -584,16 +585,22 @@ class Query {
         logger.debug(
           `Splicing results from ${i} to crop results to ${this.context.maxResults}`
         );
         this.context.results.splice(i, this.context.results.length - i);
         break;
       }
     }
 
+    this.context.firstResultChanged = !ObjectUtils.deepEqual(
+      this.context.firstResult,
+      this.context.results[0]
+    );
+    this.context.firstResult = this.context.results[0];
+
     if (this.controller) {
       this.controller.receiveResults(this.context);
     }
   }
 }
 
 /**
  * Updates in place the sources for a given UrlbarQueryContext.
--- a/browser/components/urlbar/UrlbarSearchUtils.jsm
+++ b/browser/components/urlbar/UrlbarSearchUtils.jsm
@@ -76,22 +76,19 @@ class SearchUtils {
    *
    * @returns {array}
    *   Array of objects { engine, tokenAliases } for token alias engines.
    */
   async tokenAliasEngines() {
     await this.init();
     let tokenAliasEngines = [];
     for (let engine of await Services.search.getVisibleEngines()) {
-      let tokenAliases = engine.aliases.map(alias => {
-        if (!alias.startsWith("@")) {
-          alias = "@" + alias;
-        }
-        return alias;
-      });
+      let tokenAliases = this._aliasesForEngine(engine).filter(a =>
+        a.startsWith("@")
+      );
       if (tokenAliases.length) {
         tokenAliasEngines.push({ engine, tokenAliases });
       }
     }
     return tokenAliasEngines;
   }
 
   async _initInternal() {
@@ -102,23 +99,48 @@ class SearchUtils {
 
   async _refreshEnginesByAlias() {
     // See the comment at the top of this file.  The only reason we need this
     // class is for O(1) case-insensitive lookup for search aliases, which is
     // facilitated by _enginesByAlias.
     this._enginesByAlias = new Map();
     for (let engine of await Services.search.getVisibleEngines()) {
       if (!engine.hidden) {
-        for (let alias of engine.aliases) {
-          this._enginesByAlias.set(alias.toLocaleLowerCase(), engine);
+        for (let alias of this._aliasesForEngine(engine)) {
+          this._enginesByAlias.set(alias, engine);
         }
       }
     }
   }
 
+  /**
+   * Gets the aliases of an engine.  For the user's convenience, we recognize
+   * token versions of all non-token aliases.  For example, if the user has an
+   * alias of "foo", then we recognize both "foo" and "@foo" as aliases for
+   * foo's engine.  The returned list is therefore a superset of
+   * `engine.aliases`.  Additionally, the returned aliases will be lower-cased
+   * to make lookups and comparisons easier.
+   *
+   * @param {nsISearchEngine} engine
+   *   The aliases of this search engine will be returned.
+   * @returns {array}
+   *   An array of lower-cased string aliases as described above.
+   */
+  _aliasesForEngine(engine) {
+    return engine.aliases.reduce((aliases, aliasWithCase) => {
+      // We store lower-cased aliases to make lookups and comparisons easier.
+      let alias = aliasWithCase.toLocaleLowerCase();
+      aliases.push(alias);
+      if (!alias.startsWith("@")) {
+        aliases.push("@" + alias);
+      }
+      return aliases;
+    }, []);
+  }
+
   observe(subject, topic, data) {
     switch (data) {
       case "engine-added":
       case "engine-changed":
       case "engine-removed":
       case "engine-default":
         this._refreshEnginesByAliasPromise = this._refreshEnginesByAlias();
         break;
--- a/browser/components/urlbar/UrlbarTokenizer.jsm
+++ b/browser/components/urlbar/UrlbarTokenizer.jsm
@@ -71,16 +71,19 @@ var UrlbarTokenizer = {
     BOOKMARK: "*",
     TAG: "+",
     OPENPAGE: "%",
     SEARCH: "?",
     TITLE: "#",
     URL: "$",
   },
 
+  // The keys of characters in RESTRICT that will enter search mode.
+  SEARCH_MODE_RESTRICT: new Set(["HISTORY", "BOOKMARK", "OPENPAGE"]),
+
   /**
    * Returns whether the passed in token looks like a URL.
    * This is based on guessing and heuristics, that means if this function
    * returns false, it's surely not a URL, if it returns true, the result must
    * still be verified through URIFixup.
    *
    * @param {string} token
    *        The string token to verify
--- a/browser/components/urlbar/UrlbarUtils.jsm
+++ b/browser/components/urlbar/UrlbarUtils.jsm
@@ -151,23 +151,24 @@ var UrlbarUtils = {
 
   // Whether a result should be highlighted up to the point the user has typed
   // or after that point.
   HIGHLIGHT: {
     TYPED: 1,
     SUGGESTED: 2,
   },
 
-  // Search results with keywords and empty queries are called "keyword offers".
-  // When the user selects a keyword offer, the keyword followed by a space is
-  // put in the input as a hint that the user can search using the keyword.
-  // Depending on the use case, keyword-offer results can show or not show the
-  // keyword itself.
+  // "Keyword offers" are search results with keywords that enter search mode
+  // when the user picks them.  Depending on the use case, a keyword offer can
+  // visually show or hide the keyword itself in its result.  For example,
+  // typing "@" by itself will show keyword offers for all engines with @
+  // aliases, and those results will visually show their keywords -- @google,
+  // @bing, etc.  When a keyword offer is a heuristic -- like an autofilled @
+  // alias -- usually it hides its keyword since the user is already typing it.
   KEYWORD_OFFER: {
-    NONE: 0,
     SHOW: 1,
     HIDE: 2,
   },
 
   // UnifiedComplete's autocomplete results store their titles and tags together
   // in their comments.  This separator is used to separate them.  When we
   // rewrite UnifiedComplete for quantumbar, we should stop using this old hack
   // and store titles and tags separately.  It's important that this be a
@@ -459,22 +460,25 @@ var UrlbarUtils = {
       case UrlbarUtils.RESULT_TYPE.KEYWORD:
         return {
           url: result.payload.url,
           postData: result.payload.postData
             ? this.getPostDataStream(result.payload.postData)
             : null,
         };
       case UrlbarUtils.RESULT_TYPE.SEARCH: {
-        const engine = Services.search.getEngineByName(result.payload.engine);
-        let [url, postData] = this.getSearchQueryUrl(
-          engine,
-          result.payload.suggestion || result.payload.query
-        );
-        return { url, postData };
+        if (result.payload.engine) {
+          const engine = Services.search.getEngineByName(result.payload.engine);
+          let [url, postData] = this.getSearchQueryUrl(
+            engine,
+            result.payload.suggestion || result.payload.query
+          );
+          return { url, postData };
+        }
+        break;
       }
       case UrlbarUtils.RESULT_TYPE.TIP: {
         // Return the button URL. Consumers must check payload.helpUrl
         // themselves if they need the tip's help link.
         return { url: result.payload.buttonUrl, postData: null };
       }
     }
     return { url: null, postData: null };
@@ -902,19 +906,16 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
         type: "boolean",
       },
       isPinned: {
         type: "boolean",
       },
       isPrivateEngine: {
         type: "boolean",
       },
-      isSearchHistory: {
-        type: "boolean",
-      },
       keyword: {
         type: "string",
       },
       keywordOffer: {
         type: "number", // UrlbarUtils.KEYWORD_OFFER
       },
       lowerCaseSuggestion: {
         type: "string",
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -553,19 +553,16 @@ class UrlbarView {
         ((this.oneOffsRefresh &&
           firstResult.providerName != "UrlbarProviderSearchTips") ||
           queryContext.trimmedSearchString) &&
           queryContext.trimmedSearchString[0] != "@" &&
           (queryContext.trimmedSearchString[0] !=
             UrlbarTokenizer.RESTRICT.SEARCH ||
             queryContext.trimmedSearchString.length != 1)
       );
-
-      // Notify the input, so it can make adjustments based on the first result.
-      this.input.onFirstResult(firstResult);
     }
 
     if (
       firstResult.heuristic &&
       !this.selectedElement &&
       !this.oneOffSearchButtons.selectedButton
     ) {
       // Select the heuristic result.  The heuristic may not be the first result
@@ -1713,17 +1710,19 @@ class UrlbarView {
       this.oneOffSearchButtons.selectedButton &&
       this.oneOffSearchButtons.selectedButton.engine;
 
     for (let item of this._rows.children) {
       let result = item.result;
       if (
         result.type != UrlbarUtils.RESULT_TYPE.SEARCH ||
         (!result.heuristic &&
-          (!result.payload.suggestion || result.payload.isSearchHistory) &&
+          (!result.payload.suggestion ||
+            (result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+              result.source == UrlbarUtils.RESULT_SOURCE.HISTORY)) &&
           (!result.payload.inPrivateWindow || result.payload.isPrivateEngine))
       ) {
         continue;
       }
 
       if (engine) {
         if (!result.payload.originalEngine) {
           result.payload.originalEngine = result.payload.engine;
--- a/browser/components/urlbar/tests/UrlbarTestUtils.jsm
+++ b/browser/components/urlbar/tests/UrlbarTestUtils.jsm
@@ -64,22 +64,27 @@ var UrlbarTestUtils = {
    */
   uninit() {
     this._testScope = null;
   },
 
   /**
    * Waits to a search to be complete.
    * @param {object} win The window containing the urlbar
+   * @param {number} count The number of expected searches to wait for.
    * @returns {Promise} Resolved when done.
    */
-  async promiseSearchComplete(win) {
-    return this.promisePopupOpen(win, () => {}).then(
+  async promiseSearchComplete(win, count = 1) {
+    let promise = this.promisePopupOpen(win, () => {}).then(
       () => win.gURLBar.lastQueryContextPromise
     );
+    if (--count > 0) {
+      promise = promise.then(() => this.promiseSearchComplete(win, count));
+    }
+    return promise;
   },
 
   /**
    * Starts a search for a given string and waits for the search to be complete.
    * @param {object} options.window The window containing the urlbar
    * @param {string} options.value the search string
    * @param {function} options.waitForFocus The SimpleTest function
    * @param {boolean} [options.fireInputEvent] whether an input event should be
@@ -133,17 +138,17 @@ var UrlbarTestUtils = {
    * 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
    * @returns {HtmlElement|XulElement} the result's element.
    */
   async waitForAutocompleteResultAt(win, index) {
     // TODO Bug 1530338: Quantum Bar doesn't yet implement lazy results replacement.
     await this.promiseSearchComplete(win);
-    if (index >= win.gURLBar.view._rows.length) {
+    if (index >= win.gURLBar.view._rows.children.length) {
       throw new Error("Not enough results");
     }
     return win.gURLBar.view._rows.children[index];
   },
 
   /**
    * Returns the oneOffSearchButtons object for the urlbar.
    * @param {object} win The window containing the urlbar
@@ -205,17 +210,16 @@ var UrlbarTestUtils = {
       url: element.getElementsByClassName("urlbarView-url")[0],
     };
     if (details.type == UrlbarUtils.RESULT_TYPE.SEARCH) {
       details.searchParams = {
         engine: result.payload.engine,
         keyword: result.payload.keyword,
         query: result.payload.query,
         suggestion: result.payload.suggestion,
-        isSearchHistory: result.payload.isSearchHistory,
         inPrivateWindow: result.payload.inPrivateWindow,
         isPrivateEngine: result.payload.isPrivateEngine,
       };
     } else if (details.type == UrlbarUtils.RESULT_TYPE.KEYWORD) {
       details.keyword = result.payload.keyword;
     }
     return details;
   },
@@ -498,17 +502,18 @@ var UrlbarTestUtils = {
             "Search mode result matches engine host."
           );
         }
       }
     }
   },
 
   /**
-   * Enters search mode by clicking a one-off.
+   * Enters search mode by clicking a one-off.  The view must already be open
+   * before you call this.
    * @param {object} window
    * @param {object} searchMode
    *   If given, the one-off matching this search mode will be clicked; it
    *   should be a full search mode object as described in
    *   UrlbarInput.setSearchMode.  If not given, the first one-off is clicked.
    * @note Can only be used if UrlbarTestUtils has been initialized with init().
    */
   async enterSearchMode(window, searchMode = null) {
@@ -557,39 +562,43 @@ var UrlbarTestUtils = {
    * @param {boolean} options.backspace
    *   Exits search mode by backspacing at the beginning of the search string.
    * @param {boolean} options.clickClose
    *   Exits search mode by clicking the close button on the search mode
    *   indicator.
    * @param {boolean} [waitForSearch]
    *   Whether the test should wait for a search after exiting search mode.
    *   Defaults to true.
-   * @note One and only one of `backspace` and `clickClose` should be passed
-   *       as true.
+   * @note If neither `backspace` nor `clickClose` is given, we'll default to
+   *       backspacing.
    * @note Can only be used if UrlbarTestUtils has been initialized with init().
    */
   async exitSearchMode(
     window,
-    { backspace, clickClose, waitForSearch = true }
+    { backspace, clickClose, waitForSearch = true } = {}
   ) {
     let urlbar = window.gURLBar;
     // If the Urlbar is not extended, ignore the clickClose parameter. The close
     // button is not clickable in this state. This state might be encountered on
     // Linux, where prefers-reduced-motion is enabled in automation.
     if (!urlbar.hasAttribute("breakout-extend") && clickClose) {
       if (waitForSearch) {
         let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
         urlbar.setSearchMode({});
         await searchPromise;
       } else {
         urlbar.setSearchMode({});
       }
       return;
     }
 
+    if (!backspace && !clickClose) {
+      backspace = true;
+    }
+
     if (backspace) {
       let urlbarValue = urlbar.value;
       urlbar.selectionStart = urlbar.selectionEnd = 0;
       if (waitForSearch) {
         let searchPromise = this.promiseSearchComplete(window);
         this.EventUtils.synthesizeKey("KEY_Backspace", {}, window);
         await searchPromise;
       } else {
@@ -656,16 +665,19 @@ var UrlbarTestUtils = {
    * @returns {UrlbarController} A new controller.
    */
   newMockController(options = {}) {
     return new UrlbarController(
       Object.assign(
         {
           input: {
             isPrivate: false,
+            onFirstResult() {
+              return false;
+            },
             window: {
               location: {
                 href: AppConstants.BROWSER_CHROME_URL,
               },
             },
           },
         },
         options
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -6,16 +6,17 @@
 support-files =
   dummy_page.html
   head.js
   head-common.js
 
 [browser_aboutHomeLoading.js]
 [browser_action_searchengine.js]
 [browser_action_searchengine_alias.js]
+[browser_action_searchengine_alias_legacy.js]
 [browser_autocomplete_a11y_label.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_cursor.js]
 [browser_autocomplete_edit_completed.js]
 [browser_autocomplete_enter_race.js]
@@ -204,16 +205,17 @@ support-files =
 [browser_tabKeyBehavior.js]
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 [browser_tabMatchesInAwesomebar.js]
 skip-if = fission && os == 'linux' && debug # bug 1590880
 support-files =
   moz.png
 [browser_textruns.js]
 [browser_tokenAlias.js]
+[browser_tokenAlias_legacy.js]
 [browser_top_sites.js]
 [browser_typed_value.js]
 [browser_updateForDomainCompletion.js]
 [browser_updateRows.js]
 [browser_urlbar_event_telemetry.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine.js
@@ -51,17 +51,16 @@ async function testSearch(win, expectedN
   );
   Assert.deepEqual(
     result.searchParams,
     {
       engine: expectedName,
       keyword: undefined,
       query: "open a search",
       suggestion: undefined,
-      isSearchHistory: false,
       inPrivateWindow: undefined,
       isPrivateEngine: undefined,
     },
     "Should have the correct result parameters."
   );
 
   Assert.equal(
     result.image,
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
@@ -2,16 +2,22 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that search result obtained using a search keyword gives an entry with
  * the correct attributes and visits the expected URL for the engine.
  */
 
 add_task(async function() {
+  // This test requires update2.  See also
+  // browser_action_searchengine_alias_legacy.js.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", true]],
+  });
+
   const ICON_URI =
     "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAA" +
     "CQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BL" +
     "i4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkb" +
     "G7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1v" +
     "bjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlA" +
     "fwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FA" +
     "EWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC";
@@ -25,44 +31,50 @@ add_task(async function() {
   let originalEngine = await Services.search.getDefault();
   await Services.search.setDefault(engine);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser,
     "about:mozilla"
   );
 
+  // Disable autofill so mozilla.org isn't autofilled below.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", false]],
+  });
+
   registerCleanupFunction(async function() {
     await Services.search.setDefault(originalEngine);
     await Services.search.removeEngine(engine);
     try {
       BrowserTestUtils.removeTab(tab);
     } catch (ex) {
       /* tab may have already been closed in case of failure */
     }
     await PlacesUtils.history.clear();
     await UrlbarTestUtils.formHistory.clear();
   });
 
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value: "moz",
   });
-  Assert.equal(
-    gURLBar.value,
-    "moz",
-    "Preselected search keyword result shouldn't automatically add a space"
-  );
+  Assert.equal(gURLBar.value, "moz", "Value should be unchanged");
 
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value: "moz open a search",
   });
-  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
-  Assert.equal(result.image, ICON_URI, "Should have the correct image");
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: engine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "open a search", "value should be query");
 
   let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   EventUtils.synthesizeKey("KEY_Enter");
   await tabPromise;
 
   Assert.equal(
     gBrowser.selectedBrowser.currentURI.spec,
     "http://example.com/?q=open+a+search",
copy from browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
copy to browser/components/urlbar/tests/browser/browser_action_searchengine_alias_legacy.js
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine_alias_legacy.js
@@ -1,17 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
+ * This file can be deleted when update2 is enabled by default.
+ *
  * Tests that search result obtained using a search keyword gives an entry with
  * the correct attributes and visits the expected URL for the engine.
  */
 
 add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", false]],
+  });
+
   const ICON_URI =
     "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAA" +
     "CQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BL" +
     "i4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkb" +
     "G7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1v" +
     "bjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlA" +
     "fwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FA" +
     "EWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC";
--- a/browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_placeholder.js
@@ -209,17 +209,29 @@ add_task(async function clear_placeholde
 
   // The values are initially autofilled on input, then the placeholder is
   // removed when the first non-autofill result arrives.
 
   // Matches the keyword.
   await searchAndCheck("ex", "example.com/", "ex");
   await searchAndCheck("EXA", "EXAmple.com/", "EXAmple.com/");
   // Matches the alias.
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", true]],
+  });
+  await searchAndCheck("eXaM", "eXaMple.com/", "eXaMple.com/");
+  await SpecialPowers.popPrefEnv();
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", false]],
+  });
   await searchAndCheck("eXaM", "eXaMple.com/", "eXaM");
+  await SpecialPowers.popPrefEnv();
+
   await searchAndCheck("examp", "example.com/", "example.com/");
 
   await cleanUp();
 });
 
 async function searchAndCheck(
   searchString,
   expectedAutofillValue,
--- a/browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_trimURLs.js
@@ -102,29 +102,27 @@ const tests = [
     search: "http://",
     autofilledValue: "http://",
     resultListDisplayTitle: "http://",
     resultListActionText: "Search with Google",
     resultListType: UrlbarUtils.RESULT_TYPE.SEARCH,
     searchParams: {
       engine: "Google",
       query: "http://",
-      isSearchHistory: false,
     },
   },
   {
     search: "https://",
     autofilledValue: "https://",
     resultListDisplayTitle: "https://",
     resultListActionText: "Search with Google",
     resultListType: UrlbarUtils.RESULT_TYPE.SEARCH,
     searchParams: {
       engine: "Google",
       query: "https://",
-      isSearchHistory: false,
     },
   },
   {
     search: "au",
     autofilledValue: "autofilltrimurl.com/",
     resultListDisplayTitle: "www.autofilltrimurl.com",
     resultListActionText: "Visit",
     resultListType: UrlbarUtils.RESULT_TYPE.URL,
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_a11y_label.js
@@ -67,16 +67,17 @@ add_task(async function switchToTab() {
   let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
   is(
     await getResultText(element),
     "about: robots— Switch to Tab",
     "Result a11y label should be: <title>— Switch to Tab"
   );
 
   await UrlbarTestUtils.promisePopupClose(window);
+  gURLBar.handleRevert();
   gBrowser.removeTab(tab);
 });
 
 add_task(async function searchSuggestions() {
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME
   );
   let oldDefaultEngine = await Services.search.getDefault();
--- a/browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js
+++ b/browser/components/urlbar/tests/browser/browser_autocomplete_tag_star_visibility.js
@@ -150,10 +150,11 @@ add_task(async function() {
       Assert.equal(
         result.displayed.typeIcon,
         "none",
         "Should have the star image displayed or not as expected"
       );
     }
 
     await UrlbarTestUtils.promisePopupClose(window);
+    gURLBar.handleRevert();
   }
 });
--- a/browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js
+++ b/browser/components/urlbar/tests/browser/browser_handleCommand_fallback.js
@@ -92,16 +92,17 @@ add_task(async function() {
     // Set the value directly and Enter.
     promise = promiseLoadURL();
     gURLBar.value = value;
     let spy = sinon.spy(UrlbarUtils, "getHeuristicResultFor");
     EventUtils.synthesizeKey("KEY_Enter");
     spy.restore();
     Assert.ok(spy.called, "invoked getHeuristicResultFor");
     Assert.deepEqual(await promise, args, "Check arguments are coherent");
+    gURLBar.handleRevert();
   }
 });
 
 // This is testing the final fallback case that may happen when we can't
 // get a heuristic result, maybe because the Places database is corrupt.
 add_task(async function no_heuristic_test() {
   sandbox = sinon.createSandbox();
 
--- a/browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_alias_replacement.js
@@ -29,107 +29,130 @@ add_task(async function setup() {
     Services.search.setDefault(oldDefaultEngine);
   });
 
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.update2", true]],
   });
 });
 
-// Tests that a fully typed alias is replaced when space is pressed.
-add_task(async function replaced_on_space() {
+// An incomplete alias should not be replaced.
+add_task(async function incompleteAlias() {
   // Check that a non-fully typed alias is not replaced.
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value: ALIAS.slice(0, -1),
   });
+  await UrlbarTestUtils.assertSearchMode(window, null);
 
+  // Type a space just to make sure it's not replaced.
   let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-  EventUtils.synthesizeKey("VK_SPACE");
+  EventUtils.synthesizeKey(" ");
   await searchPromise;
 
   await UrlbarTestUtils.assertSearchMode(window, null);
   Assert.equal(
     gURLBar.value,
-    ALIAS.slice(0, -1),
-    "The typed value should be unchanged."
+    ALIAS.slice(0, -1) + " ",
+    "The typed value should be unchanged except for the space."
   );
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
+});
 
-  // Test that the alias is replaced when it is fully typed.
+// A complete alias without a trailing space should not be replaced.
+add_task(async function noTrailingSpace() {
+  let value = ALIAS;
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value,
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+  await UrlbarTestUtils.promisePopupClose(window, () =>
+    EventUtils.synthesizeKey("KEY_Escape")
+  );
+});
+
+// A complete typed alias without a trailing space should not be replaced.
+add_task(async function noTrailingSpace_typed() {
+  // Start by searching for the alias minus its last char.
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
-    value: `${ALIAS} `,
+    value: ALIAS.slice(0, -1),
   });
-  let keywordOfferResult = await UrlbarTestUtils.getDetailsOfResultAt(
-    window,
-    0
-  );
+  await UrlbarTestUtils.assertSearchMode(window, null);
+
+  // Now type the last char.
+  let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+  EventUtils.synthesizeKey(ALIAS.slice(-1));
+  await searchPromise;
+
+  await UrlbarTestUtils.assertSearchMode(window, null);
   Assert.equal(
-    keywordOfferResult.searchParams.keyword,
+    gURLBar.value,
     ALIAS,
-    "The first result should be a keyword search result with the correct alias."
-  );
-  Assert.equal(
-    keywordOfferResult.searchParams.engine,
-    aliasEngine.name,
-    "The first result should be a keyword search result with the correct engine."
+    "The typed value should be the full alias."
   );
 
-  searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-  // Fire an input event simulating typing a space after the ALIAS.
-  UrlbarTestUtils.fireInputEvent(window);
+  await UrlbarTestUtils.promisePopupClose(window, () =>
+    EventUtils.synthesizeKey("KEY_Escape")
+  );
+});
+
+// A complete alias with a trailing space should be replaced.
+add_task(async function trailingSpace() {
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS + " ",
+  });
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: aliasEngine.name,
+    entry: "typed",
+  });
+  Assert.ok(!gURLBar.value, "The urlbar value should be cleared.");
+  await UrlbarTestUtils.exitSearchMode(window);
+  await UrlbarTestUtils.promisePopupClose(window);
+});
+
+// A complete alias should be replaced after typing a space.
+add_task(async function trailingSpace_typed() {
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS,
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+
+  // We need to wait for two searches: The first enters search mode, the second
+  // does the search in search mode.
+  let searchPromise = UrlbarTestUtils.promiseSearchComplete(window, 2);
+  EventUtils.synthesizeKey(" ");
   await searchPromise;
 
   await UrlbarTestUtils.assertSearchMode(window, {
     engineName: aliasEngine.name,
     entry: "typed",
   });
-  Assert.ok(!gURLBar.value, "The Urlbar value should be cleared.");
-  await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
+  Assert.ok(!gURLBar.value, "The urlbar value should be cleared.");
+  await UrlbarTestUtils.exitSearchMode(window);
   await UrlbarTestUtils.promisePopupClose(window);
 });
 
-add_task(async function not_replaced_for_alt_tab() {
-  // Test that the alias is replaced when it is fully typed.
+// A complete alias with a trailing space should be replaced, and the query
+// after the trailing space should be the new value of the input.
+add_task(async function trailingSpace_query() {
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
-    value: `${ALIAS} `,
+    value: ALIAS + " query",
   });
-  let keywordOfferResult = await UrlbarTestUtils.getDetailsOfResultAt(
-    window,
-    0
-  );
-  Assert.equal(
-    keywordOfferResult.searchParams.keyword,
-    ALIAS,
-    "The first result should be a keyword search result with the correct alias."
-  );
-  Assert.equal(
-    keywordOfferResult.searchParams.engine,
-    aliasEngine.name,
-    "The first result should be a keyword search result with the correct engine."
-  );
 
-  EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, repeat: 2 });
-  keywordOfferResult = await UrlbarTestUtils.waitForAutocompleteResultAt(
-    window,
-    0
-  );
-  Assert.ok(
-    keywordOfferResult.result.payload.originalEngine,
-    "The keyword offer result now has the originalEngine property."
-  );
-  Assert.notEqual(
-    keywordOfferResult.result.payload.engine,
-    keywordOfferResult.result.payload.originalEngine,
-    "engine and originalEngine are different."
-  );
-
-  let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-  // Fire an input event simulating typing a space after the ALIAS.
-  UrlbarTestUtils.fireInputEvent(window);
-  await searchPromise;
-
-  await UrlbarTestUtils.assertSearchMode(window, null);
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: aliasEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "query", "The urlbar value should be the query.");
+  await UrlbarTestUtils.exitSearchMode(window);
+  await UrlbarTestUtils.promisePopupClose(window);
 });
--- a/browser/components/urlbar/tests/browser/browser_searchMode_no_results.js
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_no_results.js
@@ -251,17 +251,19 @@ add_task(async function spaceToEnterSear
   engine.alias = "@test";
 
   await withNewWindow(async win => {
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window: win,
       value: engine.alias,
     });
 
-    let searchPromise = UrlbarTestUtils.promiseSearchComplete(win);
+    // We need to wait for two searches: The first enters search mode, the
+    // second does the search in search mode.
+    let searchPromise = UrlbarTestUtils.promiseSearchComplete(win, 2);
     EventUtils.synthesizeKey(" ", {}, win);
     await searchPromise;
 
     Assert.equal(UrlbarTestUtils.getResultCount(win), 0, "Zero results");
     Assert.equal(
       win.gURLBar.panel.getAttribute("noresults"),
       "true",
       "Panel has no results, therefore should have noresults attribute"
--- a/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js
+++ b/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js
@@ -43,17 +43,16 @@ add_task(async function setup() {
     await UrlbarTestUtils.formHistory.add([
       { value, source: suggestionsEngine.name },
     ]);
     expectedFormHistoryResults.push({
       heuristic: false,
       type: UrlbarUtils.RESULT_TYPE.SEARCH,
       source: UrlbarUtils.RESULT_SOURCE.HISTORY,
       searchParams: {
-        isSearchHistory: true,
         suggestion: value,
         engine: suggestionsEngine.name,
       },
     });
   }
 
   // Add other form history.
   await UrlbarTestUtils.formHistory.add([
@@ -104,39 +103,36 @@ add_task(async function nonEmptySearch()
     // We expect to get the heuristic and all the suggestions.
     await checkResults([
       {
         heuristic: true,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           engine: suggestionsEngine.name,
         },
       },
       ...expectedFormHistoryResults.slice(0, 2),
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           suggestion: `${query}foo`,
           engine: suggestionsEngine.name,
         },
       },
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           suggestion: `${query}bar`,
           engine: suggestionsEngine.name,
         },
       },
       ...expectedFormHistoryResults.slice(2, 4),
     ]);
 
     await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
@@ -157,38 +153,35 @@ add_task(async function nonEmptySearch_n
     // ones don't match.
     await checkResults([
       {
         heuristic: true,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           engine: suggestionsEngine.name,
         },
       },
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           suggestion: `${query}foo`,
           engine: suggestionsEngine.name,
         },
       },
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           suggestion: `${query}bar`,
           engine: suggestionsEngine.name,
         },
       },
     ]);
 
     await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
     await UrlbarTestUtils.promisePopupClose(window);
@@ -213,38 +206,35 @@ add_task(async function nonEmptySearch_w
     Assert.equal(gURLBar.value, query, "Urlbar value should be set.");
     await checkResults([
       {
         heuristic: true,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           engine: suggestionsEngine.name,
         },
       },
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           suggestion: `${query}foo`,
           engine: suggestionsEngine.name,
         },
       },
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           suggestion: `${query}bar`,
           engine: suggestionsEngine.name,
         },
       },
       {
         heuristic: false,
         type: UrlbarUtils.RESULT_TYPE.URL,
         source: UrlbarUtils.RESULT_SOURCE.HISTORY,
@@ -278,17 +268,16 @@ add_task(async function nonEmptySearch_u
     // result, not a URL result.
     await checkResults([
       {
         heuristic: true,
         type: UrlbarUtils.RESULT_TYPE.SEARCH,
         source: UrlbarUtils.RESULT_SOURCE.SEARCH,
         searchParams: {
           query,
-          isSearchHistory: false,
           engine: suggestionsEngine.name,
         },
       },
     ]);
 
     await UrlbarTestUtils.exitSearchMode(window, { clickClose: true });
     await UrlbarTestUtils.promisePopupClose(window);
   });
--- a/browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js
+++ b/browser/components/urlbar/tests/browser/browser_separatePrivateDefault.js
@@ -6,16 +6,18 @@
 // Tests the 'Search in a Private Window' result of the address bar.
 
 const serverInfo = {
   scheme: "http",
   host: "localhost",
   port: 20709, // Must be identical to what is in searchSuggestionEngine2.xml
 };
 
+let aliasEngine;
+
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["browser.search.separatePrivateDefault.ui.enabled", true],
       ["browser.search.separatePrivateDefault", true],
       ["browser.urlbar.suggest.searches", true],
     ],
   });
@@ -40,17 +42,17 @@ add_task(async function setup() {
 
   // Add another engine in the first one-off position.
   let engine2 = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + "POSTSearchEngine.xml"
   );
   await Services.search.moveEngine(engine2, 0);
 
   // Add an engine with an alias.
-  let aliasEngine = await Services.search.addEngineWithDetails("MozSearch", {
+  aliasEngine = await Services.search.addEngineWithDetails("MozSearch", {
     alias: "alias",
     method: "GET",
     template: "http://example.com/?q={searchTerms}",
   });
 
   registerCleanupFunction(async () => {
     await Services.search.setDefault(oldDefaultEngine);
     await Services.search.setDefaultPrivate(oldDefaultPrivateEngine);
@@ -376,31 +378,81 @@ add_task(async function test_oneoff_sele
       PrivateBrowsingUtils.isWindowPrivate(win),
       "Should open a private window"
     );
     await BrowserTestUtils.closeWindow(win);
   });
   await SpecialPowers.popPrefEnv();
 });
 
-add_task(async function test_alias() {
+add_task(async function test_alias_no_query() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", true]],
+  });
+  info(
+    "Test that 'Search in a Private Window' doesn't appear if an alias is typed with no query"
+  );
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "alias ",
+  });
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: aliasEngine.name,
+    entry: "typed",
+  });
+  await AssertNoPrivateResult(window);
+  await UrlbarTestUtils.exitSearchMode(window);
+  await UrlbarTestUtils.promisePopupClose(window);
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_alias_query() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", true]],
+  });
+  info(
+    "Test that 'Search in a Private Window' appears when an alias is typed with a query"
+  );
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "alias something",
+  });
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: "MozSearch",
+    entry: "typed",
+  });
+  await AssertPrivateResult(window, aliasEngine, true);
+  await UrlbarTestUtils.exitSearchMode(window);
+  await UrlbarTestUtils.promisePopupClose(window);
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_alias_legacy() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.update2", false]],
+  });
   info(
     "Test that 'Search in a Private Window' doesn't appear if an alias is typed"
   );
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value: "alias",
   });
   await AssertNoPrivateResult(window);
 
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value: "alias something",
   });
   await AssertNoPrivateResult(window);
+  await SpecialPowers.popPrefEnv();
 });
 
 add_task(async function test_restrict() {
   info(
     "Test that 'Search in a Private Window' doesn's appear for just the restriction token"
   );
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
--- a/browser/components/urlbar/tests/browser/browser_tokenAlias.js
+++ b/browser/components/urlbar/tests/browser/browser_tokenAlias.js
@@ -3,31 +3,42 @@
 
 // This test checks "@" search engine aliases ("token aliases") in the urlbar.
 
 "use strict";
 
 const ALIAS = "@test";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
+let testEngine;
+
 add_task(async function init() {
+  // This test requires update2.  See also browser_tokenAlias_legacy.js.
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.urlbar.update2", true],
+      ["browser.urlbar.update2.localOneOffs", true],
+      ["browser.urlbar.update2.oneOffsRefresh", true],
+    ],
+  });
+
   // Add a default engine with suggestions, to avoid hitting the network when
   // fetching them.
   let defaultEngine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME
   );
   defaultEngine.alias = "@default";
   let oldDefaultEngine = await Services.search.getDefault();
   Services.search.setDefault(defaultEngine);
-  let engine = await Services.search.addEngineWithDetails("Test", {
+  testEngine = await Services.search.addEngineWithDetails("Test", {
     alias: ALIAS,
     template: "http://example.com/?search={searchTerms}",
   });
   registerCleanupFunction(async function() {
-    await Services.search.removeEngine(engine);
+    await Services.search.removeEngine(testEngine);
     Services.search.setDefault(oldDefaultEngine);
   });
 
   // Search results aren't shown in quantumbar unless search suggestions are
   // enabled.
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.suggest.searches", true]],
   });
@@ -40,140 +51,271 @@ add_task(async function testNoRevert() {
 });
 
 // Simple test that tries different variations of an alias, reverting the urlbar
 // value in between.
 add_task(async function testRevert() {
   await doSimpleTest(true);
 });
 
-// An alias should be recognized and highlighted even when there are spaces
-// before it.
+async function doSimpleTest(revertBetweenSteps) {
+  // When autofill is enabled, searching for "@tes" will autofill to "@test",
+  // which gets in the way of this test task, so temporarily disable it.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", false]],
+  });
+
+  // "@tes" -- not an alias, no search mode
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS.substr(0, ALIAS.length - 1),
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+  Assert.equal(
+    gURLBar.value,
+    ALIAS.substr(0, ALIAS.length - 1),
+    "value should be alias substring"
+  );
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test" -- alias but no trailing space, no search mode
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS,
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+  Assert.equal(gURLBar.value, ALIAS, "value should be alias");
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test " -- alias with trailing space, search mode
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS + " ",
+  });
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: testEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "", "value should be empty");
+  await UrlbarTestUtils.exitSearchMode(window);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test foo" -- alias, search mode
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS + " foo",
+  });
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: testEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "foo", "value should be query");
+  await UrlbarTestUtils.exitSearchMode(window);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test " -- alias with trailing space, search mode
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS + " ",
+  });
+  // Wait for the second new search that starts when search mode is entered.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: testEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "", "value should be empty");
+  await UrlbarTestUtils.exitSearchMode(window);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test" -- alias but no trailing space, no highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS,
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+  Assert.equal(gURLBar.value, ALIAS, "value should be alias");
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@tes" -- not an alias, no highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS.substr(0, ALIAS.length - 1),
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+  Assert.equal(
+    gURLBar.value,
+    ALIAS.substr(0, ALIAS.length - 1),
+    "value should be alias substring"
+  );
+
+  await UrlbarTestUtils.promisePopupClose(window, () =>
+    EventUtils.synthesizeKey("KEY_Escape")
+  );
+
+  await SpecialPowers.popPrefEnv();
+}
+
+// An alias should be recognized even when there are spaces before it, and
+// search mode should be entered.
 add_task(async function spacesBeforeAlias() {
-  gURLBar.search("     " + ALIAS);
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "     " + ALIAS + " ",
+  });
+  // Wait for the second new search that starts when search mode is entered.
   await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: testEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "", "value should be empty");
+  await UrlbarTestUtils.exitSearchMode(window);
+  await UrlbarTestUtils.promisePopupClose(window, () =>
+    EventUtils.synthesizeKey("KEY_Escape")
+  );
+});
+
+// An alias in the middle of a string should not be recognized and search mode
+// should not be entered.
+add_task(async function charsBeforeAlias() {
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "not an alias " + ALIAS + " ",
+  });
+  await UrlbarTestUtils.assertSearchMode(window, null);
+  Assert.equal(
+    gURLBar.value,
+    "not an alias " + ALIAS + " ",
+    "value should be unchanged"
+  );
 
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
 });
 
-// An alias in the middle of a string should not be recognized or highlighted.
-add_task(async function charsBeforeAlias() {
-  gURLBar.search("not an alias " + ALIAS);
-  await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(false);
+// While already in search mode, an alias should not be recognized.
+add_task(async function alreadyInSearchMode() {
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "",
+  });
+  await UrlbarTestUtils.enterSearchMode(window, {
+    source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+  });
 
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-});
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS + " ",
+  });
 
-// In a search string that starts with a restriction character followed by an
-// alias, the alias should be neither recognized nor highlighted.
-add_task(async function restrictionCharBeforeAlias() {
-  gURLBar.search(UrlbarTokenizer.RESTRICT.BOOKMARK + " " + ALIAS);
-  await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(false);
+  // Search mode source should still be bookmarks.
+  await UrlbarTestUtils.assertSearchMode(window, {
+    source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+    entry: "oneoff",
+  });
+  Assert.equal(gURLBar.value, ALIAS + " ", "value should be unchanged");
 
+  // Exit search mode, but first remove the value in the input.  Since the value
+  // is "alias ", we'd actually immediately re-enter search mode otherwise.
+  gURLBar.value = "";
+
+  await UrlbarTestUtils.exitSearchMode(window);
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
 });
 
 // Types a space while typing an alias to ensure we stop autofilling.
 add_task(async function spaceWhileTypingAlias() {
   let value = ALIAS.substring(0, ALIAS.length - 1);
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value,
     selectionStart: value.length,
     selectionEnd: value.length,
   });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
+  Assert.equal(gURLBar.value, ALIAS + " ", "Alias should be autofilled");
 
-  gURLBar.value = value + " ";
   let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-  UrlbarTestUtils.fireInputEvent(window);
+  EventUtils.synthesizeKey(" ");
   await searchPromise;
 
-  await assertAlias(false);
+  Assert.equal(gURLBar.value, value + " ", "Alias should not be autofilled");
+  await UrlbarTestUtils.assertSearchMode(window, null);
+
+  await UrlbarTestUtils.promisePopupClose(window);
 });
 
-// Aliases are case insensitive, and the alias in the result uses the case that
-// the user typed in the input.  Make sure that searching with an alias using a
-// weird case still causes the alias to be highlighted.
+// Aliases are case insensitive.  Make sure that searching with an alias using a
+// weird case still causes the alias to be recognized and search mode entered.
 add_task(async function aliasCase() {
-  let alias = "@TeSt";
-  gURLBar.search(alias);
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "@TeSt ",
+  });
+  // Wait for the second new search that starts when search mode is entered.
   await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true, alias);
-
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: testEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "", "value should be empty");
+  await UrlbarTestUtils.exitSearchMode(window);
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
 });
 
-// Even when the heuristic result is a search engine result with an alias, if
-// the urlbar value does not match that result, then no alias substring in the
-// urlbar should be highlighted.  This is the case when the user uses an alias
-// to perform a search: The popup closes (preserving the results in it), the
-// urlbar value changes to the URL of the search results page, and the search
-// results page is loaded.
-add_task(async function inputDoesntMatchHeuristicResult() {
-  // Do a search using the alias.
-  let searchString = `${ALIAS} aaa`;
-  gURLBar.search(searchString);
+// Same as previous but with a query.
+add_task(async function aliasCase_query() {
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "@tEsT query",
+  });
+  // Wait for the second new search that starts when search mode is entered.
   await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
+  await UrlbarTestUtils.assertSearchMode(window, {
+    engineName: testEngine.name,
+    entry: "typed",
+  });
+  Assert.equal(gURLBar.value, "query", "value should be query");
+  await UrlbarTestUtils.exitSearchMode(window);
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-
-  // Manually set the urlbar value to a string that contains the alias at the
-  // beginning but is not the alias.
-  let value = `${ALIAS}xxx`;
-  gURLBar.value = `${ALIAS}xxx`;
-
-  // The alias substring should not be highlighted.
-  Assert.equal(gURLBar.untrimmedValue, value);
-  Assert.ok(gURLBar.untrimmedValue.includes(ALIAS));
-  assertHighlighted(false, ALIAS);
-
-  // Do another search using the alias.
-  searchString = `${ALIAS} bbb`;
-  gURLBar.search(searchString);
-  await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  // Manually set the urlbar value to a string that contains the alias, but not
-  // at the beginning and is not the same as the search string.
-  value = `bbb ${ALIAS}`;
-  gURLBar.value = `bbb ${ALIAS}`;
-
-  // The alias substring should not be highlighted.
-  Assert.equal(gURLBar.untrimmedValue, value);
-  Assert.ok(gURLBar.untrimmedValue.includes(ALIAS));
-  assertHighlighted(false, ALIAS);
-
-  // Reset for the next test.
-  gURLBar.search("");
 });
 
 // Selecting a non-heuristic (non-first) search engine result with an alias and
 // empty query should put the alias in the urlbar and highlight it.
 // Also checks that internal aliases appear with the "@" keyword.
 add_task(async function nonHeuristicAliases() {
   // Get the list of token alias engines (those with aliases that start with
   // "@").
@@ -194,18 +336,20 @@ add_task(async function nonHeuristicAlia
     return;
   }
   info(
     "Got token alias engines: " + tokenEngines.map(({ engine }) => engine.name)
   );
 
   // Populate the results with the list of token alias engines by searching for
   // "@".
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "@",
+  });
   await UrlbarTestUtils.waitForAutocompleteResultAt(
     window,
     tokenEngines.length - 1
   );
   // Key down to select each result in turn.  The urlbar value should be set to
   // each alias, and each should be highlighted.
   for (let { tokenAliases } of tokenEngines) {
     let alias = tokenAliases[0];
@@ -213,101 +357,25 @@ add_task(async function nonHeuristicAlia
     assertHighlighted(true, alias);
   }
 
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
 });
 
-// Aliases that don't start with @ shouldn't be highlighted.
-add_task(async function nonTokenAlias() {
-  let alias = "nontokenalias";
-  let engine = Services.search.getEngineByName("Test");
-  engine.alias = "nontokenalias";
-  Assert.equal(engine.alias, alias);
-
-  gURLBar.search(alias);
-  await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertFirstResultIsAlias(true, alias);
-  assertHighlighted(false);
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  engine.alias = ALIAS;
-});
-
-// Clicking on an @ alias offer (an @ alias with an empty search string) in the
-// view should fill it in the urlbar input.
-// This subtest can be removed when update2 is on by default.
-add_task(async function clickAndFillAlias_legacy() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", false]],
-  });
-  // Do a search for "@" to show all the @ aliases.
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
-
-  // Find our test engine in the results.  It's probably last, but for
-  // robustness don't assume it is.
-  let testEngineItem;
-  for (let i = 0; !testEngineItem; i++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
-    if (details.searchParams && details.searchParams.keyword == ALIAS) {
-      testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
-        window,
-        i
-      );
-    }
-  }
-
-  // Click it.
-  EventUtils.synthesizeMouseAtCenter(testEngineItem, {});
-
-  // A new search will start and its result should be the alias.
-  await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  // The urlbar input value should be the alias followed by a space so that it's
-  // ready for the user to start typing.
-  Assert.equal(gURLBar.value, `${ALIAS} `);
-
-  // Press the enter key a couple of times.  Nothing should happen except a new
-  // search will start and its result should be the alias again.  The urlbar
-  // should still contain the alias.  An empty search results page should not
-  // load.  The test will hang if that happens.
-  for (let i = 0; i < 2; i++) {
-    EventUtils.synthesizeKey("KEY_Enter");
-    await UrlbarTestUtils.promiseSearchComplete(window);
-    await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-    await assertAlias(true);
-    Assert.equal(gURLBar.value, `${ALIAS} `);
-  }
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
-});
-
 // Clicking on an @ alias offer (an @ alias with an empty search string) in the
 // view should enter search mode.
 add_task(async function clickAndFillAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
+  // Do a search for "@" to show all the @ aliases.
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "@",
   });
 
-  // Do a search for "@" to show all the @ aliases.
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
-
   // Find our test engine in the results.  It's probably last, but for
   // robustness don't assume it is.
   let testEngineItem;
   for (let i = 0; !testEngineItem; i++) {
     let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
     if (details.searchParams && details.searchParams.keyword == ALIAS) {
       testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
         window,
@@ -321,88 +389,31 @@ add_task(async function clickAndFillAlia
   EventUtils.synthesizeMouseAtCenter(testEngineItem, {});
   await searchPromise;
 
   await UrlbarTestUtils.assertSearchMode(window, {
     engineName: testEngineItem.result.payload.engine,
     entry: "keywordoffer",
   });
 
-  await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
-
+  await UrlbarTestUtils.exitSearchMode(window);
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-  await SpecialPowers.popPrefEnv();
-});
-
-// Pressing enter on an @ alias offer (an @ alias with an empty search string)
-// in the view should fill it in the urlbar input.
-// This subtest can be removed when update2 is on by default.
-add_task(async function enterAndFillAlias_legacy() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", false]],
-  });
-  // Do a search for "@" to show all the @ aliases.
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
-
-  // Find our test engine in the results.  It's probably last, but for
-  // robustness don't assume it is.
-  let index = 0;
-  for (; ; index++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
-    if (details.searchParams && details.searchParams.keyword == ALIAS) {
-      index++;
-      break;
-    }
-  }
-
-  // Key down to it and press enter.
-  EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: index });
-  EventUtils.synthesizeKey("KEY_Enter");
-
-  // A new search will start and its result should be the alias.
-  await UrlbarTestUtils.promiseSearchComplete(window);
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  // The urlbar input value should be the alias followed by a space so that it's
-  // ready for the user to start typing.
-  Assert.equal(gURLBar.value, `${ALIAS} `);
-
-  // Press the enter key a couple of times.  Nothing should happen except a new
-  // search will start and its result should be the alias again.  The urlbar
-  // should still contain the alias.  An empty search results page should not
-  // load.  The test will hang if that happens.
-  for (let i = 0; i < 2; i++) {
-    EventUtils.synthesizeKey("KEY_Enter");
-    await UrlbarTestUtils.promiseSearchComplete(window);
-    await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-    await assertAlias(true);
-    Assert.equal(gURLBar.value, `${ALIAS} `);
-  }
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
 });
 
 // Pressing enter on an @ alias offer (an @ alias with an empty search string)
 // in the view should enter search mode.
 add_task(async function enterAndFillAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
+  // Do a search for "@" to show all the @ aliases.
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: "@",
   });
 
-  // Do a search for "@" to show all the @ aliases.
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
-
   // Find our test engine in the results.  It's probably last, but for
   // robustness don't assume it is.
   let details;
   let index = 0;
   for (; ; index++) {
     details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
     if (details.searchParams && details.searchParams.keyword == ALIAS) {
       index++;
@@ -416,251 +427,75 @@ add_task(async function enterAndFillAlia
   EventUtils.synthesizeKey("KEY_Enter");
   await searchPromise;
 
   await UrlbarTestUtils.assertSearchMode(window, {
     engineName: details.searchParams.engine,
     entry: "keywordoffer",
   });
 
-  await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
-
+  await UrlbarTestUtils.exitSearchMode(window);
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-  await SpecialPowers.popPrefEnv();
-});
-
-// Pressing enter on an @ alias autofill should fill it in the urlbar input
-// with a trailing space and move the caret at the end.
-// This subtest can be removed when update2 is on by default.
-add_task(async function enterAutofillsAlias_legacy() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", false]],
-  });
-  let expectedString = `${ALIAS} `;
-  for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
-    await UrlbarTestUtils.promiseAutocompleteResultPopup({
-      window,
-      value,
-      selectionStart: value.length,
-      selectionEnd: value.length,
-    });
-    await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-
-    // Press Enter.
-    EventUtils.synthesizeKey("KEY_Enter");
-    await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-
-    // The urlbar input value should be the alias followed by a space so that it's
-    // ready for the user to start typing.
-    Assert.equal(gURLBar.value, expectedString);
-    Assert.equal(gURLBar.selectionStart, expectedString.length);
-    Assert.equal(gURLBar.selectionEnd, expectedString.length);
-    await assertAlias(true);
-  }
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
 });
 
 // Pressing Enter on an @ alias autofill should enter search mode.
 add_task(async function enterAutofillsAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
-  });
-  for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
-    await UrlbarTestUtils.promiseAutocompleteResultPopup({
-      window,
-      value,
-      selectionStart: value.length,
-      selectionEnd: value.length,
-    });
-    let testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
-      window,
-      0
-    );
-
-    let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-    EventUtils.synthesizeKey("KEY_Enter");
-    await searchPromise;
-
-    await UrlbarTestUtils.assertSearchMode(window, {
-      engineName: testEngineItem.result.payload.engine,
-      entry: "keywordoffer",
-    });
-
-    await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
-  }
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
-});
-
-// Pressing Right on an @ alias autofill should enter search mode.
-add_task(async function enterAutofillsAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
-  });
   for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window,
       value,
       selectionStart: value.length,
       selectionEnd: value.length,
     });
-    let testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
-      window,
-      0
-    );
 
-    EventUtils.synthesizeKey("KEY_ArrowRight");
+    // Press Enter.
+    let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+    EventUtils.synthesizeKey("KEY_Enter");
+    await searchPromise;
 
     await UrlbarTestUtils.assertSearchMode(window, {
-      engineName: testEngineItem.result.payload.engine,
-      entry: "typed",
+      engineName: testEngine.name,
+      entry: "keywordoffer",
     });
 
-    gURLBar.setSearchMode({});
+    await UrlbarTestUtils.exitSearchMode(window);
   }
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-  await SpecialPowers.popPrefEnv();
 });
 
-async function doSimpleTest(revertBetweenSteps) {
-  // When autofill is enabled, searching for "@tes" will autofill to "@test",
-  // which gets in the way of this test task, so temporarily disable it.
-  Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("browser.urlbar.autoFill");
-  });
-
-  // "@tes" -- not an alias, no highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS.substr(0, ALIAS.length - 1),
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(false);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
-
-  // "@test" -- alias, highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS,
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
+// Pressing Right on an @ alias autofill should enter search mode.
+add_task(async function rightEntersSearchMode() {
+  for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
+    await UrlbarTestUtils.promiseAutocompleteResultPopup({
+      window,
+      value,
+      selectionStart: value.length,
+      selectionEnd: value.length,
+    });
 
-  // "@test foo" -- alias, highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS + " foo",
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
+    let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
+    EventUtils.synthesizeKey("KEY_ArrowRight");
+    await searchPromise;
 
-  // "@test" -- alias, highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS,
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
+    await UrlbarTestUtils.assertSearchMode(window, {
+      engineName: testEngine.name,
+      entry: "typed",
+    });
+    Assert.equal(gURLBar.value, "", "value should be empty");
+    await UrlbarTestUtils.exitSearchMode(window);
   }
 
-  // "@tes" -- not an alias, no highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS.substr(0, ALIAS.length - 1),
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(false);
-
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-
-  Services.prefs.clearUserPref("browser.urlbar.autoFill");
-}
-
-async function assertAlias(aliasPresent, expectedAlias = ALIAS) {
-  await assertFirstResultIsAlias(aliasPresent, expectedAlias);
-  assertHighlighted(aliasPresent, expectedAlias);
-}
-
-async function assertFirstResultIsAlias(isAlias, expectedAlias) {
-  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
-  Assert.equal(
-    result.type,
-    UrlbarUtils.RESULT_TYPE.SEARCH,
-    "Should have the correct type"
-  );
-
-  Assert.equal(
-    "keyword" in result.searchParams && !!result.searchParams.keyword,
-    isAlias,
-    "Should have a keyword if expected"
-  );
-  if (isAlias) {
-    Assert.equal(
-      result.searchParams.keyword,
-      expectedAlias,
-      "Should have the correct keyword"
-    );
-  }
-}
-
-function assertHighlighted(highlighted, expectedAlias) {
-  let selection = gURLBar.editor.selectionController.getSelection(
-    Ci.nsISelectionController.SELECTION_FIND
-  );
-  Assert.ok(selection);
-  if (!highlighted) {
-    Assert.equal(selection.rangeCount, 0);
-    return;
-  }
-  Assert.equal(selection.rangeCount, 1);
-  let index = gURLBar.value.indexOf(expectedAlias);
-  Assert.ok(
-    index >= 0,
-    `gURLBar.value="${gURLBar.value}" expectedAlias="${expectedAlias}"`
-  );
-  let range = selection.getRangeAt(0);
-  Assert.ok(range);
-  Assert.equal(range.startOffset, index);
-  Assert.equal(range.endOffset, index + expectedAlias.length);
-}
+});
 
 /**
  * This test checks that if an engine is marked as hidden then
  * it should not appear in the popup when using the "@" token alias in the search bar.
  */
 add_task(async function hiddenEngine() {
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
@@ -710,72 +545,16 @@ add_task(async function hiddenEngine() {
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
 
   defaultEngine.hidden = false;
 });
 
 /**
- * This test checks that if an engine is marked as hidden then it should not
- * appear in the popup when using the "@" token alias in the search bar.
- */
-add_task(async function hiddenEngine() {
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: "@",
-  });
-
-  const defaultEngine = await Services.search.getDefault();
-
-  let foundDefaultEngineInPopup = false;
-
-  // Checks that the default engine appears in the urlbar's popup.
-  for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
-    if (defaultEngine.name == details.searchParams.engine) {
-      foundDefaultEngineInPopup = true;
-      break;
-    }
-  }
-  Assert.ok(foundDefaultEngineInPopup, "Default engine appears in the popup.");
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  // Checks that a hidden default engine (i.e. an engine removed from
-  // a user's search settings) does not appear in the urlbar's popup.
-  defaultEngine.hidden = true;
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: "@",
-    fireInputEvent: true,
-  });
-  foundDefaultEngineInPopup = false;
-  for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
-    if (defaultEngine.name == details.searchParams.engine) {
-      foundDefaultEngineInPopup = true;
-      break;
-    }
-  }
-  Assert.ok(
-    !foundDefaultEngineInPopup,
-    "Hidden default engine does not appear in the popup"
-  );
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  defaultEngine.hidden = false;
-});
-
-/**
  * This test checks that if an engines alias is not prefixed with
  * @ it still appears in the popup when using the "@" token
  * alias in the search bar.
  */
 add_task(async function nonPrefixedKeyword() {
   let name = "Custom";
   let alias = "customkeyword";
   let engine = await Services.search.addEngineWithDetails(name, {
@@ -813,8 +592,52 @@ add_task(async function nonPrefixedKeywo
   Assert.equal(
     keywordOfferResult.searchParams.engine,
     name,
     "The first result should be a keyword search result with the correct engine."
   );
 
   await Services.search.removeEngine(engine);
 });
+
+async function assertFirstResultIsAlias(isAlias, expectedAlias) {
+  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  Assert.equal(
+    result.type,
+    UrlbarUtils.RESULT_TYPE.SEARCH,
+    "Should have the correct type"
+  );
+
+  if (isAlias) {
+    Assert.equal(
+      result.searchParams.keyword,
+      expectedAlias,
+      "Payload keyword should be the alias"
+    );
+  } else {
+    Assert.notEqual(
+      result.searchParams.keyword,
+      expectedAlias,
+      "Payload keyword should be absent or not the alias"
+    );
+  }
+}
+
+function assertHighlighted(highlighted, expectedAlias) {
+  let selection = gURLBar.editor.selectionController.getSelection(
+    Ci.nsISelectionController.SELECTION_FIND
+  );
+  Assert.ok(selection);
+  if (!highlighted) {
+    Assert.equal(selection.rangeCount, 0);
+    return;
+  }
+  Assert.equal(selection.rangeCount, 1);
+  let index = gURLBar.value.indexOf(expectedAlias);
+  Assert.ok(
+    index >= 0,
+    `gURLBar.value="${gURLBar.value}" expectedAlias="${expectedAlias}"`
+  );
+  let range = selection.getRangeAt(0);
+  Assert.ok(range);
+  Assert.equal(range.startOffset, index);
+  Assert.equal(range.endOffset, index + expectedAlias.length);
+}
copy from browser/components/urlbar/tests/browser/browser_tokenAlias.js
copy to browser/components/urlbar/tests/browser/browser_tokenAlias_legacy.js
--- a/browser/components/urlbar/tests/browser/browser_tokenAlias.js
+++ b/browser/components/urlbar/tests/browser/browser_tokenAlias_legacy.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+// This file can be deleted when update2 is enabled by default.
+//
 // This test checks "@" search engine aliases ("token aliases") in the urlbar.
 
 "use strict";
 
 const ALIAS = "@test";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
 add_task(async function init() {
@@ -21,35 +23,118 @@ add_task(async function init() {
     alias: ALIAS,
     template: "http://example.com/?search={searchTerms}",
   });
   registerCleanupFunction(async function() {
     await Services.search.removeEngine(engine);
     Services.search.setDefault(oldDefaultEngine);
   });
 
-  // Search results aren't shown in quantumbar unless search suggestions are
-  // enabled.
   await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.suggest.searches", true]],
+    set: [
+      // Disable update2.
+      ["browser.urlbar.update2", false],
+      // Search results aren't shown in quantumbar unless search suggestions are
+      // enabled.
+      ["browser.urlbar.suggest.searches", true],
+    ],
   });
 });
 
 // Simple test that tries different variations of an alias, without reverting
 // the urlbar value in between.
 add_task(async function testNoRevert() {
   await doSimpleTest(false);
 });
 
 // Simple test that tries different variations of an alias, reverting the urlbar
 // value in between.
 add_task(async function testRevert() {
   await doSimpleTest(true);
 });
 
+async function doSimpleTest(revertBetweenSteps) {
+  // When autofill is enabled, searching for "@tes" will autofill to "@test",
+  // which gets in the way of this test task, so temporarily disable it.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", false]],
+  });
+
+  // "@tes" -- not an alias, no highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS.substr(0, ALIAS.length - 1),
+    fireInputEvent: true,
+  });
+  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+  await assertAlias(false);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test" -- alias, highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS,
+    fireInputEvent: true,
+  });
+  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+  await assertAlias(true);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test foo" -- alias, highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS + " foo",
+    fireInputEvent: true,
+  });
+  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+  await assertAlias(true);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@test" -- alias, highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS,
+    fireInputEvent: true,
+  });
+  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+  await assertAlias(true);
+
+  if (revertBetweenSteps) {
+    gURLBar.handleRevert();
+    gURLBar.blur();
+  }
+
+  // "@tes" -- not an alias, no highlight
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    value: ALIAS.substr(0, ALIAS.length - 1),
+    fireInputEvent: true,
+  });
+  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
+  await assertAlias(false);
+
+  await UrlbarTestUtils.promisePopupClose(window, () =>
+    EventUtils.synthesizeKey("KEY_Escape")
+  );
+
+  await SpecialPowers.popPrefEnv();
+}
+
 // An alias should be recognized and highlighted even when there are spaces
 // before it.
 add_task(async function spacesBeforeAlias() {
   gURLBar.search("     " + ALIAS);
   await UrlbarTestUtils.promiseSearchComplete(window);
   await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
   await assertAlias(true);
 
@@ -235,21 +320,17 @@ add_task(async function nonTokenAlias() 
     EventUtils.synthesizeKey("KEY_Escape")
   );
 
   engine.alias = ALIAS;
 });
 
 // Clicking on an @ alias offer (an @ alias with an empty search string) in the
 // view should fill it in the urlbar input.
-// This subtest can be removed when update2 is on by default.
 add_task(async function clickAndFillAlias_legacy() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", false]],
-  });
   // Do a search for "@" to show all the @ aliases.
   gURLBar.search("@");
   await UrlbarTestUtils.promiseSearchComplete(window);
 
   // Find our test engine in the results.  It's probably last, but for
   // robustness don't assume it is.
   let testEngineItem;
   for (let i = 0; !testEngineItem; i++) {
@@ -284,68 +365,21 @@ add_task(async function clickAndFillAlia
     await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
     await assertAlias(true);
     Assert.equal(gURLBar.value, `${ALIAS} `);
   }
 
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-  await SpecialPowers.popPrefEnv();
-});
-
-// Clicking on an @ alias offer (an @ alias with an empty search string) in the
-// view should enter search mode.
-add_task(async function clickAndFillAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
-  });
-
-  // Do a search for "@" to show all the @ aliases.
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
-
-  // Find our test engine in the results.  It's probably last, but for
-  // robustness don't assume it is.
-  let testEngineItem;
-  for (let i = 0; !testEngineItem; i++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
-    if (details.searchParams && details.searchParams.keyword == ALIAS) {
-      testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
-        window,
-        i
-      );
-    }
-  }
-
-  // Click it.
-  let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-  EventUtils.synthesizeMouseAtCenter(testEngineItem, {});
-  await searchPromise;
-
-  await UrlbarTestUtils.assertSearchMode(window, {
-    engineName: testEngineItem.result.payload.engine,
-    entry: "keywordoffer",
-  });
-
-  await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
 });
 
 // Pressing enter on an @ alias offer (an @ alias with an empty search string)
 // in the view should fill it in the urlbar input.
-// This subtest can be removed when update2 is on by default.
 add_task(async function enterAndFillAlias_legacy() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", false]],
-  });
   // Do a search for "@" to show all the @ aliases.
   gURLBar.search("@");
   await UrlbarTestUtils.promiseSearchComplete(window);
 
   // Find our test engine in the results.  It's probably last, but for
   // robustness don't assume it is.
   let index = 0;
   for (; ; index++) {
@@ -379,68 +413,21 @@ add_task(async function enterAndFillAlia
     await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
     await assertAlias(true);
     Assert.equal(gURLBar.value, `${ALIAS} `);
   }
 
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-  await SpecialPowers.popPrefEnv();
-});
-
-// Pressing enter on an @ alias offer (an @ alias with an empty search string)
-// in the view should enter search mode.
-add_task(async function enterAndFillAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
-  });
-
-  // Do a search for "@" to show all the @ aliases.
-  gURLBar.search("@");
-  await UrlbarTestUtils.promiseSearchComplete(window);
-
-  // Find our test engine in the results.  It's probably last, but for
-  // robustness don't assume it is.
-  let details;
-  let index = 0;
-  for (; ; index++) {
-    details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
-    if (details.searchParams && details.searchParams.keyword == ALIAS) {
-      index++;
-      break;
-    }
-  }
-
-  // Key down to it and press enter.
-  EventUtils.synthesizeKey("KEY_ArrowDown", { repeat: index });
-  let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-  EventUtils.synthesizeKey("KEY_Enter");
-  await searchPromise;
-
-  await UrlbarTestUtils.assertSearchMode(window, {
-    engineName: details.searchParams.engine,
-    entry: "keywordoffer",
-  });
-
-  await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
 });
 
 // Pressing enter on an @ alias autofill should fill it in the urlbar input
 // with a trailing space and move the caret at the end.
-// This subtest can be removed when update2 is on by default.
 add_task(async function enterAutofillsAlias_legacy() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", false]],
-  });
   let expectedString = `${ALIAS} `;
   for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window,
       value,
       selectionStart: value.length,
       selectionEnd: value.length,
     });
@@ -455,213 +442,18 @@ add_task(async function enterAutofillsAl
     Assert.equal(gURLBar.value, expectedString);
     Assert.equal(gURLBar.selectionStart, expectedString.length);
     Assert.equal(gURLBar.selectionEnd, expectedString.length);
     await assertAlias(true);
   }
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
-  await SpecialPowers.popPrefEnv();
 });
 
-// Pressing Enter on an @ alias autofill should enter search mode.
-add_task(async function enterAutofillsAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
-  });
-  for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
-    await UrlbarTestUtils.promiseAutocompleteResultPopup({
-      window,
-      value,
-      selectionStart: value.length,
-      selectionEnd: value.length,
-    });
-    let testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
-      window,
-      0
-    );
-
-    let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-    EventUtils.synthesizeKey("KEY_Enter");
-    await searchPromise;
-
-    await UrlbarTestUtils.assertSearchMode(window, {
-      engineName: testEngineItem.result.payload.engine,
-      entry: "keywordoffer",
-    });
-
-    await UrlbarTestUtils.exitSearchMode(window, { backspace: true });
-  }
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
-});
-
-// Pressing Right on an @ alias autofill should enter search mode.
-add_task(async function enterAutofillsAlias() {
-  await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
-  });
-  for (let value of [ALIAS.substring(0, ALIAS.length - 1), ALIAS]) {
-    await UrlbarTestUtils.promiseAutocompleteResultPopup({
-      window,
-      value,
-      selectionStart: value.length,
-      selectionEnd: value.length,
-    });
-    let testEngineItem = await UrlbarTestUtils.waitForAutocompleteResultAt(
-      window,
-      0
-    );
-
-    EventUtils.synthesizeKey("KEY_ArrowRight");
-
-    await UrlbarTestUtils.assertSearchMode(window, {
-      engineName: testEngineItem.result.payload.engine,
-      entry: "typed",
-    });
-
-    gURLBar.setSearchMode({});
-  }
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-  await SpecialPowers.popPrefEnv();
-});
-
-async function doSimpleTest(revertBetweenSteps) {
-  // When autofill is enabled, searching for "@tes" will autofill to "@test",
-  // which gets in the way of this test task, so temporarily disable it.
-  Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("browser.urlbar.autoFill");
-  });
-
-  // "@tes" -- not an alias, no highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS.substr(0, ALIAS.length - 1),
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(false);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
-
-  // "@test" -- alias, highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS,
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
-
-  // "@test foo" -- alias, highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS + " foo",
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
-
-  // "@test" -- alias, highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS,
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(true);
-
-  if (revertBetweenSteps) {
-    gURLBar.handleRevert();
-    gURLBar.blur();
-  }
-
-  // "@tes" -- not an alias, no highlight
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: ALIAS.substr(0, ALIAS.length - 1),
-    fireInputEvent: true,
-  });
-  await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
-  await assertAlias(false);
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  Services.prefs.clearUserPref("browser.urlbar.autoFill");
-}
-
-async function assertAlias(aliasPresent, expectedAlias = ALIAS) {
-  await assertFirstResultIsAlias(aliasPresent, expectedAlias);
-  assertHighlighted(aliasPresent, expectedAlias);
-}
-
-async function assertFirstResultIsAlias(isAlias, expectedAlias) {
-  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
-  Assert.equal(
-    result.type,
-    UrlbarUtils.RESULT_TYPE.SEARCH,
-    "Should have the correct type"
-  );
-
-  Assert.equal(
-    "keyword" in result.searchParams && !!result.searchParams.keyword,
-    isAlias,
-    "Should have a keyword if expected"
-  );
-  if (isAlias) {
-    Assert.equal(
-      result.searchParams.keyword,
-      expectedAlias,
-      "Should have the correct keyword"
-    );
-  }
-}
-
-function assertHighlighted(highlighted, expectedAlias) {
-  let selection = gURLBar.editor.selectionController.getSelection(
-    Ci.nsISelectionController.SELECTION_FIND
-  );
-  Assert.ok(selection);
-  if (!highlighted) {
-    Assert.equal(selection.rangeCount, 0);
-    return;
-  }
-  Assert.equal(selection.rangeCount, 1);
-  let index = gURLBar.value.indexOf(expectedAlias);
-  Assert.ok(
-    index >= 0,
-    `gURLBar.value="${gURLBar.value}" expectedAlias="${expectedAlias}"`
-  );
-  let range = selection.getRangeAt(0);
-  Assert.ok(range);
-  Assert.equal(range.startOffset, index);
-  Assert.equal(range.endOffset, index + expectedAlias.length);
-}
-
 /**
  * This test checks that if an engine is marked as hidden then
  * it should not appear in the popup when using the "@" token alias in the search bar.
  */
 add_task(async function hiddenEngine() {
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     value: "@",
@@ -710,72 +502,16 @@ add_task(async function hiddenEngine() {
   await UrlbarTestUtils.promisePopupClose(window, () =>
     EventUtils.synthesizeKey("KEY_Escape")
   );
 
   defaultEngine.hidden = false;
 });
 
 /**
- * This test checks that if an engine is marked as hidden then it should not
- * appear in the popup when using the "@" token alias in the search bar.
- */
-add_task(async function hiddenEngine() {
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: "@",
-  });
-
-  const defaultEngine = await Services.search.getDefault();
-
-  let foundDefaultEngineInPopup = false;
-
-  // Checks that the default engine appears in the urlbar's popup.
-  for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
-    if (defaultEngine.name == details.searchParams.engine) {
-      foundDefaultEngineInPopup = true;
-      break;
-    }
-  }
-  Assert.ok(foundDefaultEngineInPopup, "Default engine appears in the popup.");
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  // Checks that a hidden default engine (i.e. an engine removed from
-  // a user's search settings) does not appear in the urlbar's popup.
-  defaultEngine.hidden = true;
-  await UrlbarTestUtils.promiseAutocompleteResultPopup({
-    window,
-    value: "@",
-    fireInputEvent: true,
-  });
-  foundDefaultEngineInPopup = false;
-  for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
-    let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
-    if (defaultEngine.name == details.searchParams.engine) {
-      foundDefaultEngineInPopup = true;
-      break;
-    }
-  }
-  Assert.ok(
-    !foundDefaultEngineInPopup,
-    "Hidden default engine does not appear in the popup"
-  );
-
-  await UrlbarTestUtils.promisePopupClose(window, () =>
-    EventUtils.synthesizeKey("KEY_Escape")
-  );
-
-  defaultEngine.hidden = false;
-});
-
-/**
  * This test checks that if an engines alias is not prefixed with
  * @ it still appears in the popup when using the "@" token
  * alias in the search bar.
  */
 add_task(async function nonPrefixedKeyword() {
   let name = "Custom";
   let alias = "customkeyword";
   let engine = await Services.search.addEngineWithDetails(name, {
@@ -813,8 +549,57 @@ add_task(async function nonPrefixedKeywo
   Assert.equal(
     keywordOfferResult.searchParams.engine,
     name,
     "The first result should be a keyword search result with the correct engine."
   );
 
   await Services.search.removeEngine(engine);
 });
+
+async function assertAlias(aliasPresent, expectedAlias = ALIAS) {
+  await assertFirstResultIsAlias(aliasPresent, expectedAlias);
+  assertHighlighted(aliasPresent, expectedAlias);
+}
+
+async function assertFirstResultIsAlias(isAlias, expectedAlias) {
+  let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  Assert.equal(
+    result.type,
+    UrlbarUtils.RESULT_TYPE.SEARCH,
+    "Should have the correct type"
+  );
+
+  if (isAlias) {
+    Assert.equal(
+      result.searchParams.keyword,
+      expectedAlias,
+      "Payload keyword should be the alias"
+    );
+  } else {
+    Assert.notEqual(
+      result.searchParams.keyword,
+      expectedAlias,
+      "Payload keyword should be absent or not the alias"
+    );
+  }
+}
+
+function assertHighlighted(highlighted, expectedAlias) {
+  let selection = gURLBar.editor.selectionController.getSelection(
+    Ci.nsISelectionController.SELECTION_FIND
+  );
+  Assert.ok(selection);
+  if (!highlighted) {
+    Assert.equal(selection.rangeCount, 0);
+    return;
+  }
+  Assert.equal(selection.rangeCount, 1);
+  let index = gURLBar.value.indexOf(expectedAlias);
+  Assert.ok(
+    index >= 0,
+    `gURLBar.value="${gURLBar.value}" expectedAlias="${expectedAlias}"`
+  );
+  let range = selection.getRangeAt(0);
+  Assert.ok(range);
+  Assert.equal(range.startOffset, index);
+  Assert.equal(range.endOffset, index + expectedAlias.length);
+}
--- a/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbar_event_telemetry.js
@@ -646,33 +646,18 @@ const tests = [
       template: "http://example.com/?search={searchTerms}",
     });
 
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window: win,
       value: `${alias} `,
     });
 
-    let keywordOfferResult = await UrlbarTestUtils.getDetailsOfResultAt(
-      window,
-      0
-    );
-    Assert.equal(
-      keywordOfferResult.searchParams.keyword,
-      alias,
-      "The first result should be a keyword search result with the correct alias."
-    );
-
-    // Fire an input event simulating typing a space after the alias.
-    let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
-    UrlbarTestUtils.fireInputEvent(window);
-    await searchPromise;
-
     await UrlbarTestUtils.assertSearchMode(window, {
-      engineName: keywordOfferResult.searchParams.engine,
+      engineName: "AliasTest",
       entry: "typed",
     });
 
     let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window: win,
       value: "moz",
       fireInputEvent: true,
--- a/browser/components/urlbar/tests/browser/head.js
+++ b/browser/components/urlbar/tests/browser/head.js
@@ -74,23 +74,24 @@ async function updateTopSites(condition,
   // Wait for the feed to be updated.
   await TestUtils.waitForCondition(() => {
     let sites = AboutNewTab.getTopSites();
     return condition(sites);
   }, "Waiting for top sites to be updated");
 }
 
 /**
- * Simple convenience method to append a space to a token alias if update2 is
- * off. When the update2 pref is removed, this method can be removed and its
- * callers can simply use the passed string
- * (e.g. change foo(getAutofillSearchString("@test")) to foo("@test")).
+ * Simple convenience method to append a space to token aliases but not to other
+ * values.
+ *
+ * TODO (Bug 1661882): Remove this function and simply use the passed-in string
+ * directly.
+ *
  * @param {string} val
  * @returns {string}
- *   `val` with a space appended if update2 is disabled. Returns `val` if it
- *    does not start with "@".
+ *   `val` with a space appended if it's a token alias, or just `val` otherwise.
  */
 function getAutofillSearchString(val) {
   if (!val.startsWith("@")) {
     return val;
   }
-  return val + (UrlbarPrefs.get("update2") ? "" : " ");
+  return val + " ";
 }
--- a/browser/components/urlbar/tests/unit/head.js
+++ b/browser/components/urlbar/tests/unit/head.js
@@ -389,21 +389,28 @@ function frecencyForUrl(aURI) {
  * @param {array} [options.tags]
  *   An array of string tags. Defaults to an empty array.
  * @param {boolean} [options.heuristic]
  *   True if this is a heuristic result. Defaults to false.
  * @returns {UrlbarResult}
  */
 function makeBookmarkResult(
   queryContext,
-  { title, uri, iconUri, tags = [], heuristic = false }
+  {
+    title,
+    uri,
+    iconUri,
+    tags = [],
+    heuristic = false,
+    source = UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+  }
 ) {
   let result = new UrlbarResult(
     UrlbarUtils.RESULT_TYPE.URL,
-    UrlbarUtils.RESULT_SOURCE.BOOKMARKS,
+    source,
     ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
       url: [uri, UrlbarUtils.HIGHLIGHT.TYPED],
       // Check against undefined so consumers can pass in the empty string.
       icon: [typeof iconUri != "undefined" ? iconUri : `page-icon:${uri}`],
       title: [title, UrlbarUtils.HIGHLIGHT.TYPED],
       tags: [tags, UrlbarUtils.HIGHLIGHT.TYPED],
     })
   );
@@ -424,17 +431,16 @@ function makeBookmarkResult(
  */
 function makeFormHistoryResult(queryContext, { suggestion, engineName }) {
   return new UrlbarResult(
     UrlbarUtils.RESULT_TYPE.SEARCH,
     UrlbarUtils.RESULT_SOURCE.HISTORY,
     ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
       engine: engineName,
       suggestion: [suggestion, UrlbarUtils.HIGHLIGHT.SUGGESTED],
-      isSearchHistory: true,
       lowerCaseSuggestion: suggestion.toLocaleLowerCase(),
     })
   );
 }
 
 /**
  * Creates a UrlbarResult for an omnibox extension result. For more information,
  * see the documentation for omnibox.SuggestResult:
@@ -527,17 +533,17 @@ function makePrioritySearchResult(
   queryContext,
   { engineName, engineIconUri, heuristic }
 ) {
   let result = new UrlbarResult(
     UrlbarUtils.RESULT_TYPE.SEARCH,
     UrlbarUtils.RESULT_SOURCE.SEARCH,
     ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
       engine: [engineName, UrlbarUtils.HIGHLIGHT.TYPED],
-      icon: engineIconUri ? engineIconUri : "",
+      icon: engineIconUri,
     })
   );
 
   if (heuristic) {
     result.heuristic = heuristic;
   }
   return result;
 }
@@ -575,61 +581,59 @@ function makeSearchResult(
     suggestion,
     tailPrefix,
     tail,
     tailOffsetIndex,
     engineName,
     alias,
     query,
     engineIconUri,
-    heuristic = false,
     keywordOffer,
     providerName,
+    inPrivateWindow,
+    isPrivateEngine,
+    heuristic = false,
+    type = UrlbarUtils.RESULT_TYPE.SEARCH,
+    source = UrlbarUtils.RESULT_SOURCE.SEARCH,
   }
 ) {
-  if (!keywordOffer) {
-    keywordOffer = UrlbarUtils.KEYWORD_OFFER.NONE;
-    if (
-      alias &&
-      !query.trim() &&
-      (UrlbarPrefs.get("update2") || alias.startsWith("@"))
-    ) {
-      keywordOffer = heuristic
-        ? UrlbarUtils.KEYWORD_OFFER.HIDE
-        : UrlbarUtils.KEYWORD_OFFER.SHOW;
+  // Tail suggestion common cases, handled here to reduce verbosity in tests.
+  if (tail) {
+    if (!tailPrefix) {
+      tailPrefix = "… ";
+    }
+    if (!tailOffsetIndex) {
+      tailOffsetIndex = suggestion.indexOf(tail);
     }
   }
 
-  // Tail suggestion common cases, handled here to reduce verbosity in tests.
-  if (tail && !tailPrefix) {
-    tailPrefix = "… ";
-  }
-
-  if (!tailOffsetIndex) {
-    tailOffsetIndex = tail ? suggestion.indexOf(tail) : -1;
-  }
-
   let result = new UrlbarResult(
-    UrlbarUtils.RESULT_TYPE.SEARCH,
-    UrlbarUtils.RESULT_SOURCE.SEARCH,
+    type,
+    source,
     ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
       engine: [engineName, UrlbarUtils.HIGHLIGHT.TYPED],
       suggestion: [suggestion, UrlbarUtils.HIGHLIGHT.SUGGESTED],
       tailPrefix,
       tail: [tail, UrlbarUtils.HIGHLIGHT.SUGGESTED],
       tailOffsetIndex,
-      keyword: [alias, UrlbarUtils.HIGHLIGHT.TYPED],
+      keyword: [
+        alias,
+        keywordOffer == UrlbarUtils.KEYWORD_OFFER.SHOW
+          ? UrlbarUtils.HIGHLIGHT.TYPED
+          : UrlbarUtils.HIGHLIGHT.NONE,
+      ],
       // Check against undefined so consumers can pass in the empty string.
       query: [
         typeof query != "undefined" ? query : queryContext.trimmedSearchString,
         UrlbarUtils.HIGHLIGHT.TYPED,
       ],
-      isSearchHistory: false,
-      icon: [engineIconUri ? engineIconUri : ""],
+      icon: engineIconUri,
       keywordOffer,
+      inPrivateWindow,
+      isPrivateEngine,
     })
   );
 
   if (typeof suggestion == "string") {
     result.payload.lowerCaseSuggestion = result.payload.suggestion.toLocaleLowerCase();
   }
 
   if (providerName) {
@@ -656,32 +660,47 @@ function makeSearchResult(
  *   True if this is a heuristic result. Defaults to false.
  *  * @param {string} providerName
  *   The name of the provider offering this result. The test suite will not
  *   check which provider offered a result unless this option is specified.
  * @returns {UrlbarResult}
  */
 function makeVisitResult(
   queryContext,
-  { title, uri, iconUri, tags = null, heuristic = false, providerName }
+  {
+    title,
+    uri,
+    iconUri,
+    providerName,
+    tags = null,
+    heuristic = false,
+    source = UrlbarUtils.RESULT_SOURCE.HISTORY,
+  }
 ) {
   let payload = {
     url: [uri, UrlbarUtils.HIGHLIGHT.TYPED],
-    // Check against undefined so consumers can pass in the empty string.
-    icon: [typeof iconUri != "undefined" ? iconUri : `page-icon:${uri}`],
     title: [title, UrlbarUtils.HIGHLIGHT.TYPED],
   };
 
+  if (iconUri) {
+    payload.icon = iconUri;
+  } else if (
+    iconUri === undefined &&
+    source != UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL
+  ) {
+    payload.icon = `page-icon:${uri}`;
+  }
+
   if (!heuristic || tags) {
     payload.tags = [tags || [], UrlbarUtils.HIGHLIGHT.TYPED];
   }
 
   let result = new UrlbarResult(
     UrlbarUtils.RESULT_TYPE.URL,
-    UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+    source,
     ...UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, payload)
   );
 
   if (providerName) {
     result.providerName = providerName;
   }
 
   result.heuristic = heuristic;
@@ -721,22 +740,24 @@ async function check_results({
   // updates.
   // This is not a problem in real life, but autocomplete tests should
   // return reliable resultsets, thus we have to wait.
   await PlacesTestUtils.promiseAsyncUpdates();
 
   let controller = UrlbarTestUtils.newMockController({
     input: {
       isPrivate: context.isPrivate,
+      onFirstResult() {
+        return false;
+      },
       window: {
         location: {
           href: AppConstants.BROWSER_CHROME_URL,
         },
       },
-      autofillFirstResult() {},
     },
   });
 
   if (incompleteSearch) {
     let incompleteContext = createContext(incompleteSearch, {
       isPrivate: context.isPrivate,
     });
     controller.startQuery(incompleteContext);
@@ -758,45 +779,72 @@ async function check_results({
       Assert.equal(
         context.results[0].payload.url,
         completed,
         "The completed autofill value is correct."
       );
     }
   }
   if (context.results.length != matches.length) {
-    info("Returned results: " + JSON.stringify(context.results));
-    Assert.equal(
-      context.results.length,
-      matches.length,
-      "Found the expected number of results."
-    );
+    info("Actual results: " + JSON.stringify(context.results));
   }
-
-  Assert.deepEqual(
-    matches.map(m => m.payload),
-    context.results.map(m => m.payload),
-    "Payloads are the same."
+  Assert.equal(
+    context.results.length,
+    matches.length,
+    "Found the expected number of results."
   );
 
-  Assert.deepEqual(
-    matches.map(m => m.heuristic),
-    context.results.map(m => m.heuristic),
-    "Heuristic results are correctly flagged."
-  );
+  function getPayload(result) {
+    let payload = {};
+    for (let [key, value] of Object.entries(result.payload)) {
+      if (value !== undefined) {
+        payload[key] = value;
+      }
+    }
+    return payload;
+  }
 
-  matches.forEach((match, i) => {
-    if (match.providerName) {
+  for (let i = 0; i < matches.length; i++) {
+    let actual = context.results[i];
+    let expected = matches[i];
+    info(
+      `Comparing results at index ${i}:` +
+        " actual=" +
+        JSON.stringify(actual) +
+        " expected=" +
+        JSON.stringify(expected)
+    );
+    Assert.equal(
+      actual.type,
+      expected.type,
+      `result.type at result index ${i}`
+    );
+    Assert.equal(
+      actual.source,
+      expected.source,
+      `result.source at result index ${i}`
+    );
+    Assert.equal(
+      actual.heuristic,
+      expected.heuristic,
+      `result.heuristic at result index ${i}`
+    );
+    if (expected.providerName) {
       Assert.equal(
-        match.providerName,
-        context.results[i].providerName,
-        `The result at index ${i} is from the correct provider.`
+        actual.providerName,
+        expected.providerName,
+        `result.providerName at result index ${i}`
       );
     }
-  });
+    Assert.deepEqual(
+      getPayload(actual),
+      getPayload(expected),
+      `result.payload at result index ${i}`
+    );
+  }
 }
 
 /**
  * Returns the frecency of an origin.
  *
  * @param   {string} prefix
  *          The origin's prefix, e.g., "http://".
  * @param   {string} aHost
--- a/browser/components/urlbar/tests/unit/test_autofill_origins.js
+++ b/browser/components/urlbar/tests/unit/test_autofill_origins.js
@@ -171,16 +171,17 @@ add_task(async function portNoMatch1() {
       uri: "http://example.com:8888/",
     },
   ]);
   let context = createContext(`${origin}:89`, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${origin}:89/`,
         title: `http://${origin}:89/`,
         iconUri: "",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
@@ -194,41 +195,44 @@ add_task(async function portNoMatch2() {
       uri: "http://example.com:8888/",
     },
   ]);
   let context = createContext(`${origin}:9`, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${origin}:9/`,
         title: `http://${origin}:9/`,
         iconUri: "",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
   await cleanup();
 });
 
 // "example/" should *not* match http://example.com/.
-add_task(async function trailingSlash() {
+add_task(async function trailingSlash_2() {
   await PlacesTestUtils.addVisits([
     {
       uri: "http://example.com/",
     },
   ]);
   let context = createContext("example/", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://example/",
         title: "http://example/",
+        iconUri: "page-icon:http://example/",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
   await cleanup();
 });
 
@@ -557,16 +561,17 @@ add_task(async function suggestHistoryFa
       uri: baseURL + "other1",
     },
   ]);
   let context = createContext(search, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${search}/`,
         title: `${search}/`,
         iconUri: "",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
@@ -576,16 +581,17 @@ add_task(async function suggestHistoryFa
       uri: bookmarkedURL,
     },
   ]);
   context = createContext(search, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${search}/`,
         title: `${search}/`,
         iconUri: "",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
@@ -595,16 +601,17 @@ add_task(async function suggestHistoryFa
       uri: baseURL + "other2",
     },
   ]);
   context = createContext(search, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${search}/`,
         title: `${search}/`,
         iconUri: "",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
--- a/browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js
+++ b/browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js
@@ -30,35 +30,37 @@ let path;
 let search;
 let searchCase;
 let title;
 let url;
 const host = "example.com";
 let origins;
 
 function add_autofill_task(callback) {
-  add_task(async () => {
-    info("Running subtest with origins disabled.");
+  let func = async () => {
+    info(`Running subtest with origins disabled: ${callback.name}`);
     origins = false;
     path = "/foo";
     search = "example.com/f";
     searchCase = "EXAMPLE.COM/f";
     title = "example.com/foo";
     url = host + path;
     await callback();
 
-    info("Running subtest with origins enabled.");
+    info(`Running subtest with origins enabled: ${callback.name}`);
     origins = true;
     path = "/";
     search = "ex";
     searchCase = "EX";
     title = "example.com";
     url = host + path;
     await callback();
-  });
+  };
+  Object.defineProperty(func, "name", { value: callback.name });
+  add_task(func);
 }
 
 // "ex" should match http://example.com/.
 add_autofill_task(async function basic() {
   await PlacesTestUtils.addVisits([
     {
       uri: "http://" + url,
     },
@@ -167,16 +169,17 @@ add_autofill_task(async function wwwShou
         }),
       ],
     });
   } else {
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://www." + search,
           title: "http://www." + search,
           iconUri: `page-icon:http://www.${host}/`,
           heuristic: true,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -284,16 +287,17 @@ add_autofill_task(async function prefixW
     },
   ]);
   let context = createContext("http://www." + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://www.${search}/` : `http://www.${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://www.${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
@@ -308,16 +312,17 @@ add_autofill_task(async function httpPre
     },
   ]);
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "https://" + url,
@@ -394,16 +399,17 @@ add_autofill_task(async function httpsWW
         }),
       ],
     });
   } else {
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://www." + search,
           title: "http://www." + search,
           iconUri: `page-icon:http://www.${host}/`,
           heuristic: true,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -467,16 +473,17 @@ add_autofill_task(async function httpsPr
   let context = createContext("https://www." + search, { isPrivate: false });
   let prefixedUrl = origins
     ? `https://www.${search}/`
     : `https://www.${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:https://www.${host}/`,
         providerame: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
@@ -491,16 +498,17 @@ add_autofill_task(async function httpsPr
     },
   ]);
   let context = createContext("https://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `https://${search}/` : `https://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:https://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "http://" + url,
@@ -785,16 +793,17 @@ add_autofill_task(async function frecenc
         }),
       ],
     });
   } else {
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://" + search,
           title: "http://" + search,
           iconUri: `page-icon:http://${host}/`,
           heuristic: true,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -985,16 +994,17 @@ add_autofill_task(async function suggest
         }),
       ],
     });
   } else {
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://" + search,
           title: "http://" + search,
           iconUri: `page-icon:http://${host}/`,
           heuristic: true,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1042,16 +1052,17 @@ add_autofill_task(async function suggest
         }),
       ],
     });
   } else {
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://" + search,
           title: "http://" + search,
           iconUri: `page-icon:http://${host}/`,
           heuristic: true,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1142,16 +1153,17 @@ add_autofill_task(async function suggest
         engineName: ENGINE_NAME,
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       })
     );
   } else {
     matches.unshift(
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://" + search,
         title: "http://" + search,
         iconUri: `page-icon:http://${host}/`,
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       })
     );
   }
@@ -1236,16 +1248,17 @@ add_autofill_task(async function suggest
     uri: "ftp://" + url,
   });
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeBookmarkResult(context, {
         uri: "ftp://" + url,
@@ -1274,16 +1287,17 @@ add_autofill_task(async function suggest
     uri: "http://non-matching-" + url,
   });
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeBookmarkResult(context, {
         uri: "http://non-matching-" + url,
@@ -1312,16 +1326,17 @@ add_autofill_task(async function suggest
     uri: "ftp://non-matching-" + url,
   });
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeBookmarkResult(context, {
         uri: "ftp://non-matching-" + url,
@@ -1393,16 +1408,17 @@ add_autofill_task(async function suggest
         engineName: ENGINE_NAME,
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       })
     );
   } else {
     matches.unshift(
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       })
     );
   }
@@ -1460,16 +1476,17 @@ add_autofill_task(async function suggest
   Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
   await PlacesTestUtils.addVisits("ftp://" + url);
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "ftp://" + url,
@@ -1497,16 +1514,17 @@ add_autofill_task(async function suggest
   Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
   await PlacesTestUtils.addVisits("http://non-matching-" + url);
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "http://non-matching-" + url,
@@ -1534,16 +1552,17 @@ add_autofill_task(async function suggest
   Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
   await PlacesTestUtils.addVisits("ftp://non-matching-" + url);
   let context = createContext("http://" + search, { isPrivate: false });
   let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: prefixedUrl,
         title: prefixedUrl,
         heuristic: true,
         iconUri: origins ? "" : `page-icon:http://${host}/`,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "ftp://non-matching-" + url,
@@ -1597,16 +1616,17 @@ add_autofill_task(async function suggest
         }),
       ],
     });
   } else {
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://" + search,
           title: "http://" + search,
           iconUri: `page-icon:http://${host}/`,
           heuristic: true,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1646,16 +1666,17 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1682,16 +1703,17 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     let context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1718,16 +1740,17 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     let context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1754,16 +1777,17 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     let context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
       ],
     });
@@ -1861,23 +1885,25 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     let context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeBookmarkResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.HISTORY,
           uri: "ftp://" + url,
           title: "A bookmark",
         }),
       ],
     });
     await cleanup();
   }
 );
@@ -1902,23 +1928,25 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     let context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeBookmarkResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.HISTORY,
           uri: "http://non-matching-" + url,
           title: "A bookmark",
         }),
       ],
     });
     await cleanup();
   }
 );
@@ -1943,23 +1971,25 @@ add_autofill_task(
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     let context = createContext("http://" + search, { isPrivate: false });
     let prefixedUrl = origins ? `http://${search}/` : `http://${search}`;
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: prefixedUrl,
           title: prefixedUrl,
           heuristic: true,
           iconUri: origins ? "" : `page-icon:http://${host}/`,
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeBookmarkResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.HISTORY,
           uri: "ftp://non-matching-" + url,
           title: "A bookmark",
         }),
       ],
     });
     await cleanup();
   }
 );
@@ -2066,16 +2096,17 @@ add_autofill_task(
     for (let i = 0; i < 3; i++) {
       await PlacesTestUtils.addVisits("http://some-other-" + url);
     }
     let context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "http://some-other-" + url,
@@ -2094,16 +2125,17 @@ add_autofill_task(
       uri: "http://" + url,
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "http://some-other-" + url,
@@ -2144,16 +2176,17 @@ add_autofill_task(
     for (let i = 0; i < 3; i++) {
       await PlacesTestUtils.addVisits("ftp://some-other-" + url);
     }
     let context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "ftp://some-other-" + url,
@@ -2172,16 +2205,17 @@ add_autofill_task(
       uri: "ftp://" + url,
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "ftp://some-other-" + url,
@@ -2222,16 +2256,17 @@ add_autofill_task(
     for (let i = 0; i < 3; i++) {
       await PlacesTestUtils.addVisits("http://some-other-" + url);
     }
     let context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "http://some-other-" + url,
@@ -2250,16 +2285,17 @@ add_autofill_task(
       uri: "http://non-matching-" + url,
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "http://some-other-" + url,
@@ -2300,16 +2336,17 @@ add_autofill_task(
     for (let i = 0; i < 3; i++) {
       await PlacesTestUtils.addVisits("ftp://some-other-" + url);
     }
     let context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "ftp://some-other-" + url,
@@ -2328,16 +2365,17 @@ add_autofill_task(
       uri: "ftp://non-matching-" + url,
     });
     Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false);
     context = createContext("http://" + search, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `http://${search}/`,
           title: `http://${search}/`,
           heuristic: true,
           iconUri: "",
           providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
         }),
         makeVisitResult(context, {
           uri: "ftp://some-other-" + url,
--- a/browser/components/urlbar/tests/unit/test_autofill_search_engine_aliases.js
+++ b/browser/components/urlbar/tests/unit/test_autofill_search_engine_aliases.js
@@ -31,18 +31,17 @@ add_task(async function basic() {
     uri: "http://example.com/",
     title: TEST_ENGINE_ALIAS,
   });
 
   let search = TEST_ENGINE_ALIAS.substr(
     0,
     Math.round(TEST_ENGINE_ALIAS.length / 2)
   );
-  let autofilledValue =
-    TEST_ENGINE_ALIAS + (UrlbarPrefs.get("update2") ? "" : " ");
+  let autofilledValue = TEST_ENGINE_ALIAS + " ";
   let context = createContext(search, { isPrivate: false });
   await check_results({
     context,
     autofilled: autofilledValue,
     matches: [
       makeSearchResult(context, {
         engineName: TEST_ENGINE_NAME,
         alias: TEST_ENGINE_ALIAS,
@@ -67,17 +66,17 @@ add_task(async function preserveCase() {
   });
 
   let search = TEST_ENGINE_ALIAS.toUpperCase().substr(
     0,
     Math.round(TEST_ENGINE_ALIAS.length / 2)
   );
   let alias = search + TEST_ENGINE_ALIAS.substr(search.length);
 
-  let autofilledValue = alias + (UrlbarPrefs.get("update2") ? "" : " ");
+  let autofilledValue = alias + " ";
   let context = createContext(search, { isPrivate: false });
   await check_results({
     context,
     autofilled: autofilledValue,
     matches: [
       makeSearchResult(context, {
         engineName: TEST_ENGINE_NAME,
         alias,
--- a/browser/components/urlbar/tests/unit/test_autofill_search_engines.js
+++ b/browser/components/urlbar/tests/unit/test_autofill_search_engines.js
@@ -189,44 +189,46 @@ add_task(async function searchEngines() 
     // these queries.
     let otherScheme = schemes[(i + 1) % schemes.length];
     context = createContext(otherScheme + "://ex", { isPrivate: false });
     await check_results({
       context,
       search: otherScheme + "://ex",
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: otherScheme + "://ex/",
           title: otherScheme + "://ex/",
-          iconUri: "",
           heuristic: true,
         }),
       ],
     });
     context = createContext(otherScheme + "://www.ex", { isPrivate: false });
     await check_results({
       context,
       search: otherScheme + "://www.ex",
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: otherScheme + "://www.ex/",
           title: otherScheme + "://www.ex/",
-          iconUri: "",
           heuristic: true,
         }),
       ],
     });
 
     context = createContext("example/", { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: "http://example/",
           title: "http://example/",
+          iconUri: "page-icon:http://example/",
           heuristic: true,
         }),
       ],
     });
 
     await Services.search.removeEngine(engine);
   }
 });
--- a/browser/components/urlbar/tests/unit/test_autofill_urls.js
+++ b/browser/components/urlbar/tests/unit/test_autofill_urls.js
@@ -62,16 +62,17 @@ add_task(async function portNoMatch() {
       uri: "http://example.com:8888/foo",
     },
   ]);
   let context = createContext("example.com:8999/f", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://example.com:8999/f",
         title: "http://example.com:8999/f",
         iconUri: "page-icon:http://example.com:8999/",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
     ],
   });
--- a/browser/components/urlbar/tests/unit/test_avoid_middle_complete.js
+++ b/browser/components/urlbar/tests/unit/test_avoid_middle_complete.js
@@ -174,16 +174,17 @@ add_task(async function test_searchEngin
   info(
     "Should not autoFill search engine if search string has a different scheme."
   );
   let context = createContext("http://pie", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://pie/",
         title: "http://pie/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
--- a/browser/components/urlbar/tests/unit/test_avoid_stripping_to_empty_tokens.js
+++ b/browser/components/urlbar/tests/unit/test_avoid_stripping_to_empty_tokens.js
@@ -65,16 +65,17 @@ add_task(async function test_protocol_tr
 
     input = prot + "://www. ";
     info("Searching for: " + input);
     context = createContext(input, { isPrivate: false });
     await check_results({
       context,
       matches: [
         makeVisitResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
           uri: `${input.trim()}/`,
           title: `${input.trim()}/`,
           iconUri: "",
           heuristic: true,
           providerName: "HeuristicFallback",
         }),
         makeVisitResult(context, {
           uri: visit.uri.spec,
--- a/browser/components/urlbar/tests/unit/test_casing.js
+++ b/browser/components/urlbar/tests/unit/test_casing.js
@@ -39,16 +39,17 @@ add_task(async function test_casing_2() 
   await PlacesTestUtils.addVisits({
     uri: Services.io.newURI("http://mozilla.org/test/"),
   });
   let context = createContext("mozilla.org/T", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://mozilla.org/T",
         title: "http://mozilla.org/T",
         iconUri: "page-icon:http://mozilla.org/",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "http://mozilla.org/test/",
@@ -86,16 +87,17 @@ add_task(async function test_casing_4() 
   await PlacesTestUtils.addVisits({
     uri: Services.io.newURI("http://mozilla.org/Test/"),
   });
   let context = createContext("mOzilla.org/t", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://mozilla.org/t",
         title: "http://mozilla.org/t",
         iconUri: "page-icon:http://mozilla.org/",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "http://mozilla.org/Test/",
@@ -185,16 +187,17 @@ add_task(async function test_untrimmed_p
   await PlacesTestUtils.addVisits({
     uri: Services.io.newURI("http://mozilla.org/Test/"),
   });
   let context = createContext("http://mOzilla.org/t", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://mozilla.org/t",
         title: "http://mozilla.org/t",
         iconUri: "page-icon:http://mozilla.org/",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "http://mozilla.org/Test/",
@@ -232,16 +235,17 @@ add_task(async function test_untrimmed_p
   await PlacesTestUtils.addVisits({
     uri: Services.io.newURI("http://www.mozilla.org/Test/"),
   });
   let context = createContext("http://www.mOzilla.org/t", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://www.mozilla.org/t",
         title: "http://www.mozilla.org/t",
         iconUri: "page-icon:http://www.mozilla.org/",
         heuristic: true,
         providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
       }),
       makeVisitResult(context, {
         uri: "http://www.mozilla.org/Test/",
--- a/browser/components/urlbar/tests/unit/test_providerHeuristicFallback.js
+++ b/browser/components/urlbar/tests/unit/test_providerHeuristicFallback.js
@@ -32,133 +32,133 @@ add_task(async function setup() {
 add_task(async function() {
   info("visit url, no protocol");
   let query = "mozilla.org";
   let context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
       }),
     ],
   });
 
   info("visit url, no protocol but with 2 dots");
   query = "www.mozilla.org";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
       }),
     ],
   });
 
   info("visit url, no protocol, e-mail like");
   query = "a@b.com";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
       }),
     ],
   });
 
   info("visit url, with protocol but with 2 dots");
   query = "https://www.mozilla.org";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${query}/`,
         title: `${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   // info("visit url, with protocol but with 3 dots");
   query = "https://www.mozilla.org.tw";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${query}/`,
         title: `${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit url, with protocol");
   query = "https://mozilla.org";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${query}/`,
         title: `${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit url, about: protocol (no host)");
   query = "about:nonexistent";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: query,
         title: query,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit url, with non-standard whitespace");
   query = "https://mozilla.org";
   context = createContext(`${query}\u2028`, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${query}/`,
         title: `${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   // This is distinct because of how we predict being able to url autofill via
   // host lookups.
   info("visit url, host matching visited host but not visited url");
@@ -170,16 +170,17 @@ add_task(async function() {
     },
   ]);
   query = "mozilla.org/rum";
   context = createContext(`${query}\u2028`, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}`,
         title: `http://${query}`,
         iconUri: "page-icon:http://mozilla.org/",
         heuristic: true,
       }),
     ],
   });
   await PlacesUtils.history.clear();
@@ -218,34 +219,35 @@ add_task(async function() {
 
   info("known host");
   query = "firefox";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
       }),
     ],
   });
 
   info("url with known host");
   query = "firefox/get";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}`,
         title: `http://${query}`,
         iconUri: "page-icon:http://firefox/",
         heuristic: true,
       }),
     ],
   });
 
@@ -255,50 +257,51 @@ add_task(async function() {
     Services.prefs.clearUserPref("browser.fixup.domainwhitelist.mozilla");
   });
   query = "mozilla/rum";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}`,
         title: `http://${query}`,
         iconUri: "page-icon:http://mozilla/",
         heuristic: true,
       }),
     ],
   });
 
   // ipv4 and ipv6 literal addresses should offer to visit.
   info("visit url, ipv4 literal");
   query = "127.0.0.1";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit url, ipv6 literal");
   query = "[2001:db8::1]";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   // Setting keyword.enabled to false should always try to visit.
   let keywordEnabled = Services.prefs.getBoolPref("keyword.enabled");
   Services.prefs.setBoolPref("keyword.enabled", false);
@@ -307,34 +310,34 @@ add_task(async function() {
   });
   info("visit url, keyword.enabled = false");
   query = "bacon";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit two word query, keyword.enabled = false");
   query = "bacon lovers";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: query,
         title: query,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
   Services.prefs.setBoolPref("keyword.enabled", true);
   info("visit two word query, keyword.enabled = true");
   query = "bacon lovers";
   context = createContext(query, { isPrivate: false });
@@ -352,49 +355,49 @@ add_task(async function() {
 
   info("visit url, scheme+host");
   query = "http://example";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${query}/`,
         title: `${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit url, scheme+host");
   query = "ftp://example";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `${query}/`,
         title: `${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("visit url, host+port");
   query = "example:8080";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${query}/`,
         title: `http://${query}/`,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("numerical operations that look like urls should search");
   query = "123/12";
   context = createContext(query, { isPrivate: false });
@@ -422,34 +425,34 @@ add_task(async function() {
   });
 
   query = "resource:///modules";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: query,
         title: query,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("access resource://app/modules");
   query = "resource://app/modules";
   context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: query,
         title: query,
-        iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   info("protocol with an extra slash");
   query = "http:///";
   context = createContext(query, { isPrivate: false });
@@ -484,18 +487,19 @@ add_task(async function() {
       makeSearchResult(context, {
         engineName: "AliasEngine",
         heuristic: true,
       }),
     ],
   });
   await Services.search.setDefault(originalTestEngine);
 
+  Services.prefs.setBoolPref("browser.urlbar.update2", false);
   info(
-    "Leading restriction tokens are not removed from the search result, apart from the search token."
+    "With update2 disabled, leading restriction tokens are not removed from the search result, apart from the search token."
   );
   // Note that we use the alias from AliasEngine in the query. Since we're using
   // a restriction token, we expect that the default engine be used.
   for (let token of Object.values(UrlbarTokenizer.RESTRICT)) {
     for (query of [`${token} alias query`, `query ${token}`]) {
       let expectedQuery =
         token == UrlbarTokenizer.RESTRICT.SEARCH &&
         query.startsWith(UrlbarTokenizer.RESTRICT.SEARCH)
@@ -510,10 +514,64 @@ add_task(async function() {
             engineName: ENGINE_NAME,
             query: expectedQuery,
             heuristic: true,
           }),
         ],
       });
     }
   }
+  Services.prefs.clearUserPref("browser.urlbar.update2");
+
+  Services.prefs.setBoolPref("browser.urlbar.update2", true);
+  info(
+    "Leading search-mode restriction tokens are removed from the search result."
+  );
+  for (let restrict of UrlbarTokenizer.SEARCH_MODE_RESTRICT) {
+    let token = UrlbarTokenizer.RESTRICT[restrict];
+    query = `${token} query`;
+    let expectedQuery = query.substring(2);
+    context = createContext(query, { isPrivate: false });
+    info(`Searching for "${query}", expecting "${expectedQuery}"`);
+    await check_results({
+      context,
+      matches: [
+        makeSearchResult(context, {
+          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+          heuristic: true,
+          query: expectedQuery,
+          alias: token,
+          keywordOffer: UrlbarUtils.KEYWORD_OFFER.NONE,
+        }),
+      ],
+    });
+  }
+
+  info(
+    "Leading non-search-mode restriction tokens are not removed from the search result."
+  );
+  for (let restrict of Object.keys(UrlbarTokenizer.RESTRICT)) {
+    if (
+      UrlbarTokenizer.SEARCH_MODE_RESTRICT.has(restrict) ||
+      restrict == "SEARCH"
+    ) {
+      continue;
+    }
+    let token = UrlbarTokenizer.RESTRICT[restrict];
+    query = `${token} query`;
+    let expectedQuery = query;
+    context = createContext(query, { isPrivate: false });
+    info(`Searching for "${query}", expecting "${expectedQuery}"`);
+    await check_results({
+      context,
+      matches: [
+        makeSearchResult(context, {
+          heuristic: true,
+          query: expectedQuery,
+          engineName: ENGINE_NAME,
+        }),
+      ],
+    });
+  }
+  Services.prefs.clearUserPref("browser.urlbar.update2");
+
   await Services.search.removeEngine(engine2);
 });
--- a/browser/components/urlbar/tests/unit/test_search_suggestions.js
+++ b/browser/components/urlbar/tests/unit/test_search_suggestions.js
@@ -431,31 +431,33 @@ add_task(async function restrictToken() 
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
         query: "",
         heuristic: true,
       }),
       ...makeExpectedFormHistoryResults(context),
     ],
   });
+
   // Also if followed by multiple spaces.
   context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH}  `, {
     isPrivate: false,
   });
   await check_results({
     context,
     matches: [
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
         query: "",
         heuristic: true,
       }),
       ...makeExpectedFormHistoryResults(context),
     ],
   });
+
   // If followed by any char we should fetch suggestions.
   // Note this uses "h" to match form history.
   context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH}h`, {
     isPrivate: false,
   });
   await check_results({
     context,
     matches: [
@@ -465,16 +467,17 @@ add_task(async function restrictToken() 
         heuristic: true,
       }),
       ...makeExpectedSuggestionResults(context, {
         suggestionPrefix: "h",
         query: "h",
       }),
     ],
   });
+
   // Also if followed by a space and single char.
   context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH} h`, {
     isPrivate: false,
   });
   await check_results({
     context,
     matches: [
       makeSearchResult(context, {
@@ -483,26 +486,76 @@ add_task(async function restrictToken() 
         heuristic: true,
       }),
       ...makeExpectedSuggestionResults(context, {
         suggestionPrefix: "h",
         query: "h",
       }),
     ],
   });
-  // Any other restriction char allows to search for it.
+
+  // With update2 disabled, any other restriction char allows to search for it.
+  Services.prefs.setBoolPref("browser.urlbar.update2", false);
   context = createContext(UrlbarTokenizer.RESTRICT.OPENPAGE, {
     isPrivate: false,
   });
   await check_results({
     context,
     matches: [
       makeSearchResult(context, { engineName: ENGINE_NAME, heuristic: true }),
     ],
   });
+  Services.prefs.clearUserPref("browser.urlbar.update2");
+
+  // Leading search-mode restriction tokens are removed.
+  Services.prefs.setBoolPref("browser.urlbar.update2", true);
+  context = createContext(
+    `${UrlbarTokenizer.RESTRICT.BOOKMARK} ${SEARCH_STRING}`,
+    { isPrivate: false }
+  );
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+        heuristic: true,
+        query: SEARCH_STRING,
+        alias: UrlbarTokenizer.RESTRICT.BOOKMARK,
+        keywordOffer: UrlbarUtils.KEYWORD_OFFER.NONE,
+      }),
+      makeBookmarkResult(context, {
+        uri: `http://example.com/${SEARCH_STRING}-bookmark`,
+        title: `${SEARCH_STRING} bookmark`,
+      }),
+    ],
+  });
+
+  // Non-search-mode restriction tokens remain in the query and heuristic search
+  // result.
+  let restrict;
+  for (let r of Object.keys(UrlbarTokenizer.RESTRICT)) {
+    if (!UrlbarTokenizer.SEARCH_MODE_RESTRICT.has(r)) {
+      restrict = r;
+      break;
+    }
+  }
+  Assert.ok(
+    restrict,
+    "Non-search-mode restrict token exists -- if not, you can probably remove me!"
+  );
+  context = createContext(UrlbarTokenizer.RESTRICT[restrict], {
+    isPrivate: false,
+  });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, { engineName: ENGINE_NAME, heuristic: true }),
+    ],
+  });
+  Services.prefs.clearUserPref("browser.urlbar.update2");
 
   await cleanUpSuggestions();
 });
 
 add_task(async function mixup_frecency() {
   Services.prefs.setBoolPref(SUGGEST_PREF, true);
   // At most, we should have 14 results in this subtest. We set this to 20 to
   // make we're not cutting off any results and we are actually getting 12.
@@ -755,16 +808,17 @@ add_task(async function prohibit_suggest
       false
     );
   });
   context = createContext(SEARCH_STRING, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${SEARCH_STRING}/`,
         title: `http://${SEARCH_STRING}/`,
         iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
         heuristic: false,
@@ -795,16 +849,17 @@ add_task(async function prohibit_suggest
     Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words");
   });
 
   context = createContext(SEARCH_STRING, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: `http://${SEARCH_STRING}/`,
         title: `http://${SEARCH_STRING}/`,
         iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
         heuristic: false,
@@ -813,16 +868,17 @@ add_task(async function prohibit_suggest
     ],
   });
 
   context = createContext("somethingelse", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://somethingelse/",
         title: "http://somethingelse/",
         iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, {
         engineName: ENGINE_NAME,
         heuristic: false,
@@ -843,54 +899,59 @@ add_task(async function prohibit_suggest
 
   Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words");
 
   context = createContext("http://1.2.3.4/", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://1.2.3.4/",
         title: "http://1.2.3.4/",
+        iconUri: "page-icon:http://1.2.3.4/",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("[2001::1]:30", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://[2001::1]:30/",
         title: "http://[2001::1]:30/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("user:pass@test", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://user:pass@test/",
         title: "http://user:pass@test/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("data:text/plain,Content", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "data:text/plain,Content",
         title: "data:text/plain,Content",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
@@ -911,16 +972,17 @@ add_task(async function uri_like_queries
 
   // We should not fetch any suggestions for an actual URL.
   let query = "mozilla.org";
   let context = createContext(query, { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         title: `http://${query}/`,
         uri: `http://${query}/`,
         iconUri: "",
         heuristic: true,
       }),
       makeSearchResult(context, { query, engineName: ENGINE_NAME }),
     ],
   });
@@ -1130,16 +1192,17 @@ add_task(async function avoid_remote_url
     ],
   });
 
   context = createContext("ftp://test", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "ftp://test/",
         title: "ftp://test/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
@@ -1169,16 +1232,17 @@ add_task(async function avoid_remote_url
     ],
   });
 
   context = createContext("ftp://test", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "ftp://test/",
         title: "ftp://test/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
@@ -1214,81 +1278,87 @@ add_task(async function avoid_remote_url
     ],
   });
 
   context = createContext("http://www", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://www/",
         title: "http://www/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("https://www", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "https://www/",
         title: "https://www/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("http://test", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://test/",
         title: "http://test/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("https://test", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "https://test/",
         title: "https://test/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("http://www.test", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://www.test/",
         title: "http://www.test/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
   context = createContext("http://www.test.com", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "http://www.test.com/",
         title: "http://www.test.com/",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
@@ -1316,16 +1386,17 @@ add_task(async function avoid_remote_url
     ],
   });
 
   context = createContext("file:///Users", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "file:///Users",
         title: "file:///Users",
         iconUri: "",
         heuristic: true,
       }),
     ],
   });
 
@@ -1511,16 +1582,17 @@ add_task(async function formHistory() {
   // not a search result.  Now the "foo" and "foobar" form history should be
   // included.
   await PlacesTestUtils.addVisits("http://foo.example.com/");
   context = createContext("foo", { isPrivate: false });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.HISTORY,
         uri: "http://foo.example.com/",
         title: "foo.example.com",
         heuristic: true,
       }),
       makeFormHistoryResult(context, {
         suggestion: "foo",
         engineName: ENGINE_NAME,
       }),
--- a/browser/components/urlbar/tests/unit/test_search_suggestions_aliases.js
+++ b/browser/components/urlbar/tests/unit/test_search_suggestions_aliases.js
@@ -1,181 +1,343 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that an engine with suggestions works with our alias autocomplete
  * behavior.
  */
 
+const DEFAULT_ENGINE_NAME = "TestDefaultEngine";
 const SUGGESTIONS_ENGINE_NAME = "engine-suggestions.xml";
 const SUGGEST_PREF = "browser.urlbar.suggest.searches";
 const SUGGEST_ENABLED_PREF = "browser.search.suggest.enabled";
+const HISTORY_TITLE = "fire";
 
 let engine;
 
 add_task(async function setup() {
   engine = await addTestSuggestionsEngine();
-});
 
-add_task(async function engineWithSuggestions() {
-  // History matches should not appear with @ aliases, so this visit/match
-  // should not appear when searching with the @ alias below.
-  let historyTitle = "fire";
+  // Set a mock engine as the default so we don't hit the network below when we
+  // do searches that return the default engine heuristic result.
+  Services.search.defaultEngine = await Services.search.addEngineWithDetails(
+    DEFAULT_ENGINE_NAME,
+    { template: "http://example.com/?s=%S" }
+  );
+
+  // History matches should not appear with @aliases, so this visit should not
+  // appear when searching with @aliases below.
   await PlacesTestUtils.addVisits({
     uri: engine.searchForm,
-    title: historyTitle,
+    title: HISTORY_TITLE,
   });
+});
 
-  // Search in both a non-private and private context.
-  for (let private of [false, true]) {
-    // Use a normal alias and then one with an "@".  For the @ alias, the only
-    // matches should be the search suggestions -- no history matches.
-    for (let alias of ["moz", "@moz"]) {
-      engine.alias = alias;
-      Assert.equal(engine.alias, alias);
+// A non-token alias without a trailing space shouldn't be recognized as a
+// keyword.  It should be treated as part of the search string.
+add_task(async function nonTokenAlias_noTrailingSpace() {
+  Services.prefs.setBoolPref("browser.urlbar.update2", true);
+  let alias = "moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  let context = createContext(alias, { isPrivate: false });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        engineName: DEFAULT_ENGINE_NAME,
+        query: alias,
+        heuristic: true,
+      }),
+      makeSearchResult(context, {
+        engineName: DEFAULT_ENGINE_NAME,
+        query: alias,
+        heuristic: false,
+        inPrivateWindow: true,
+        isPrivateEngine: false,
+      }),
+    ],
+  });
+  Services.prefs.clearUserPref("browser.urlbar.update2");
+});
 
-      // Search for "alias"
-      let context = createContext(`${alias}`, { isPrivate: private });
-      let expectedMatches = [
+// With update2 disabled, a non-token alias without a trailing space should be
+// recognized as a keyword, and the history result should be included.
+add_task(async function nonTokenAlias_noTrailingSpace_legacy() {
+  Services.prefs.setBoolPref("browser.urlbar.update2", false);
+  let alias = "moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  for (let isPrivate of [false, true]) {
+    let context = createContext(alias, { isPrivate });
+    await check_results({
+      context,
+      matches: [
         makeSearchResult(context, {
           engineName: SUGGESTIONS_ENGINE_NAME,
           alias,
           query: "",
           heuristic: true,
         }),
-      ];
-      if (alias[0] != "@") {
-        expectedMatches.push(
-          makeVisitResult(context, {
-            uri: "http://localhost:9000/search?terms=",
-            title: historyTitle,
-          })
-        );
-      }
+        makeVisitResult(context, {
+          uri: "http://localhost:9000/search?terms=",
+          title: HISTORY_TITLE,
+        }),
+      ],
+    });
+  }
+  Services.prefs.clearUserPref("browser.urlbar.update2");
+});
 
-      await check_results({
-        context,
-        matches: expectedMatches,
-      });
-
-      // Search for "alias " (trailing space)
-      context = createContext(`${alias} `, { isPrivate: private });
-      expectedMatches = [
+// A non-token alias with a trailing space should be recognized as a keyword,
+// and the history result should be included.
+add_task(async function nonTokenAlias_trailingSpace() {
+  let alias = "moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  for (let isPrivate of [false, true]) {
+    let context = createContext(alias + " ", { isPrivate });
+    await check_results({
+      context,
+      matches: [
         makeSearchResult(context, {
           engineName: SUGGESTIONS_ENGINE_NAME,
           alias,
           query: "",
           heuristic: true,
         }),
-      ];
-      if (alias[0] != "@") {
-        expectedMatches.push(
-          makeVisitResult(context, {
-            uri: "http://localhost:9000/search?terms=",
-            title: historyTitle,
-          })
-        );
-      }
-      await check_results({
-        context,
-        matches: expectedMatches,
-      });
+        makeVisitResult(context, {
+          uri: "http://localhost:9000/search?terms=",
+          title: HISTORY_TITLE,
+        }),
+      ],
+    });
+  }
+});
 
-      // Search for "alias historyTitle" -- Include the history title so that
-      // the history result is eligible to be shown.  Whether or not it's
-      // actually shown depends on the alias: If it's an @ alias, it shouldn't
-      // be shown.
-      context = createContext(`${alias} ${historyTitle}`, {
-        isPrivate: private,
-      });
-      expectedMatches = [
+// Search for "alias HISTORY_TITLE" with a non-token alias in a non-private
+// context.  The remote suggestions and history result should be shown.
+add_task(async function nonTokenAlias_history_nonPrivate() {
+  let alias = "moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  let context = createContext(`${alias} ${HISTORY_TITLE}`, {
+    isPrivate: false,
+  });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        heuristic: true,
+      }),
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        suggestion: `${HISTORY_TITLE} foo`,
+      }),
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        suggestion: `${HISTORY_TITLE} bar`,
+      }),
+      makeVisitResult(context, {
+        uri: "http://localhost:9000/search?terms=",
+        title: HISTORY_TITLE,
+      }),
+    ],
+  });
+});
+
+// Search for "alias HISTORY_TITLE" with a non-token alias in a private context.
+// The history result should be shown, but not the remote suggestions.
+add_task(async function nonTokenAlias_history_private() {
+  let alias = "moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  let context = createContext(`${alias} ${HISTORY_TITLE}`, {
+    isPrivate: true,
+  });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        heuristic: true,
+      }),
+      makeVisitResult(context, {
+        uri: "http://localhost:9000/search?terms=",
+        title: HISTORY_TITLE,
+      }),
+    ],
+  });
+});
+
+// A token alias without a trailing space should be autofilled with a trailing
+// space and recognized as a keyword with a keyword offer.
+add_task(async function tokenAlias_noTrailingSpace() {
+  let alias = "@moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  for (let isPrivate of [false, true]) {
+    let context = createContext(alias, { isPrivate });
+    await check_results({
+      context,
+      autofilled: alias + " ",
+      matches: [
         makeSearchResult(context, {
           engineName: SUGGESTIONS_ENGINE_NAME,
           alias,
-          query: historyTitle,
+          keywordOffer: UrlbarUtils.KEYWORD_OFFER.HIDE,
+          query: "",
+          heuristic: true,
+        }),
+      ],
+    });
+  }
+});
+
+// A token alias with a trailing space should be recognized as a keyword without
+// a keyword offer.
+add_task(async function tokenAlias_trailingSpace() {
+  Services.prefs.setBoolPref("browser.urlbar.update2", true);
+  let alias = "@moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  for (let isPrivate of [false, true]) {
+    let context = createContext(alias + " ", { isPrivate });
+    await check_results({
+      context,
+      matches: [
+        makeSearchResult(context, {
+          engineName: SUGGESTIONS_ENGINE_NAME,
+          alias,
+          query: "",
           heuristic: true,
         }),
-      ];
-      // Suggestions should be shown in a non-private context but not in a
-      // private context.
-      if (!private) {
-        expectedMatches.push(
-          makeSearchResult(context, {
-            engineName: SUGGESTIONS_ENGINE_NAME,
-            alias,
-            query: historyTitle,
-            suggestion: `${historyTitle} foo`,
-          }),
-          makeSearchResult(context, {
-            engineName: SUGGESTIONS_ENGINE_NAME,
-            alias,
-            query: historyTitle,
-            suggestion: `${historyTitle} bar`,
-          })
-        );
-      }
-      if (alias[0] != "@") {
-        expectedMatches.push(
-          makeVisitResult(context, {
-            uri: "http://localhost:9000/search?terms=",
-            title: historyTitle,
-          })
-        );
-      }
-      await check_results({
-        context,
-        matches: expectedMatches,
-      });
-    }
+      ],
+    });
   }
-
-  engine.alias = "";
-  await PlacesUtils.bookmarks.eraseEverything();
-  await PlacesUtils.history.clear();
+  Services.prefs.clearUserPref("browser.urlbar.update2");
 });
 
-add_task(async function disabled_urlbarSuggestions() {
+// Search for "alias HISTORY_TITLE" with a token alias in a non-private context.
+// The remote suggestions should be shown, but not the history result.
+add_task(async function tokenAlias_history_nonPrivate() {
+  let alias = "@moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  let context = createContext(`${alias} ${HISTORY_TITLE}`, {
+    isPrivate: false,
+  });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        heuristic: true,
+      }),
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        suggestion: `${HISTORY_TITLE} foo`,
+      }),
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        suggestion: `${HISTORY_TITLE} bar`,
+      }),
+    ],
+  });
+});
+
+// Search for "alias HISTORY_TITLE" with a token alias in a private context.
+// Neither the history result nor the remote suggestions should be shown.
+add_task(async function tokenAlias_history_private() {
+  let alias = "@moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  let context = createContext(`${alias} ${HISTORY_TITLE}`, {
+    isPrivate: true,
+  });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: HISTORY_TITLE,
+        heuristic: true,
+      }),
+    ],
+  });
+});
+
+// Even when they're disabled, suggestions should still be returned when using a
+// token alias in a non-private context.
+add_task(async function suggestionsDisabled_nonPrivate() {
   Services.prefs.setBoolPref(SUGGEST_PREF, false);
   Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
   let alias = "@moz";
   engine.alias = alias;
   Assert.equal(engine.alias, alias);
-
-  for (let private of [false, true]) {
-    let context = createContext(`${alias} term`, { isPrivate: private });
-    let expectedMatches = [
+  let context = createContext(`${alias} term`, { isPrivate: false });
+  await check_results({
+    context,
+    matches: [
       makeSearchResult(context, {
         engineName: SUGGESTIONS_ENGINE_NAME,
         alias,
         query: "term",
         heuristic: true,
       }),
-    ];
-
-    if (!private) {
-      expectedMatches.push(
-        makeSearchResult(context, {
-          engineName: SUGGESTIONS_ENGINE_NAME,
-          alias,
-          query: "term",
-          suggestion: "term foo",
-        })
-      );
-      expectedMatches.push(
-        makeSearchResult(context, {
-          engineName: SUGGESTIONS_ENGINE_NAME,
-          alias,
-          query: "term",
-          suggestion: "term bar",
-        })
-      );
-    }
-    await check_results({
-      context,
-      matches: expectedMatches,
-    });
-  }
-
-  engine.alias = "";
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: "term",
+        suggestion: "term foo",
+      }),
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: "term",
+        suggestion: "term bar",
+      }),
+    ],
+  });
   Services.prefs.clearUserPref(SUGGEST_PREF);
   Services.prefs.clearUserPref(SUGGEST_ENABLED_PREF);
 });
+
+// Suggestions should not be returned when using a token alias in a private
+// context.
+add_task(async function suggestionsDisabled_private() {
+  Services.prefs.setBoolPref(SUGGEST_PREF, false);
+  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
+  let alias = "@moz";
+  engine.alias = alias;
+  Assert.equal(engine.alias, alias);
+  let context = createContext(`${alias} term`, { isPrivate: true });
+  await check_results({
+    context,
+    matches: [
+      makeSearchResult(context, {
+        engineName: SUGGESTIONS_ENGINE_NAME,
+        alias,
+        query: "term",
+        heuristic: true,
+      }),
+    ],
+  });
+  Services.prefs.clearUserPref(SUGGEST_PREF);
+  Services.prefs.clearUserPref(SUGGEST_ENABLED_PREF);
+});
--- a/browser/components/urlbar/tests/unit/test_trimming.js
+++ b/browser/components/urlbar/tests/unit/test_trimming.js
@@ -200,16 +200,17 @@ add_task(async function test_escaped_cha
   });
   let context = createContext("https://www.mozilla.org/啊-test", {
     isPrivate: false,
   });
   await check_results({
     context,
     matches: [
       makeVisitResult(context, {
+        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
         uri: "https://www.mozilla.org/%E5%95%8A-test",
         title: "https://www.mozilla.org/啊-test",
         iconUri: "page-icon:https://www.mozilla.org/",
         heuristic: true,
       }),
       // UnifiedComplete escapes this character.
       makeVisitResult(context, {
         uri: "https://www.mozilla.org/%E5%95%8A-test",
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -232,17 +232,112 @@ add_task(async function test_simpleQuery
     resultMethodHist,
     UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
     1
   );
 
   BrowserTestUtils.removeTab(tab);
 });
 
-add_task(async function test_searchAlias() {
+add_task(async function test_searchMode_enter() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.urlbar.update2", true],
+      ["browser.urlbar.update2.oneOffsRefresh", true],
+    ],
+  });
+  Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
+
+  let resultIndexHist = TelemetryTestUtils.getAndClearHistogram(
+    "FX_URLBAR_SELECTED_RESULT_INDEX"
+  );
+  let resultTypeHist = TelemetryTestUtils.getAndClearHistogram(
+    "FX_URLBAR_SELECTED_RESULT_TYPE_2"
+  );
+  let resultIndexByTypeHist = TelemetryTestUtils.getAndClearKeyedHistogram(
+    "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE_2"
+  );
+  let resultMethodHist = TelemetryTestUtils.getAndClearHistogram(
+    "FX_URLBAR_SELECTED_RESULT_METHOD"
+  );
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(
+    gBrowser,
+    "about:blank"
+  );
+
+  info("Enter search mode using an alias and a query.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  await searchInAwesomebar("mozalias query");
+  EventUtils.synthesizeKey("KEY_Enter");
+  await p;
+
+  // Check if the scalars contain the expected values.
+  const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
+  TelemetryTestUtils.assertKeyedScalar(
+    scalars,
+    SCALAR_SEARCHMODE,
+    "search_enter",
+    1
+  );
+  Assert.equal(
+    Object.keys(scalars[SCALAR_SEARCHMODE]).length,
+    1,
+    "This search must only increment one entry in the scalar."
+  );
+
+  // Also check events.
+  TelemetryTestUtils.assertEvents(
+    [
+      [
+        "navigation",
+        "search",
+        "urlbar_searchmode",
+        "enter",
+        { engine: "other-MozSearch" },
+      ],
+    ],
+    { category: "navigation", method: "search" }
+  );
+
+  // Check the histograms as well.
+  TelemetryTestUtils.assertHistogram(resultIndexHist, 0, 1);
+
+  TelemetryTestUtils.assertHistogram(
+    resultTypeHist,
+    UrlbarUtils.SELECTED_RESULT_TYPES.searchengine,
+    1
+  );
+
+  TelemetryTestUtils.assertKeyedHistogramValue(
+    resultIndexByTypeHist,
+    "searchengine",
+    0,
+    1
+  );
+
+  TelemetryTestUtils.assertHistogram(
+    resultMethodHist,
+    UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
+    1
+  );
+
+  BrowserTestUtils.removeTab(tab);
+  await SpecialPowers.popPrefEnv();
+});
+
+// This subtest can be removed with the update2 pref.
+add_task(async function test_searchAlias_legacy() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["browser.urlbar.update2", false],
+      ["browser.urlbar.update2.oneOffsRefresh", false],
+    ],
+  });
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
 
   let resultIndexHist = TelemetryTestUtils.getAndClearHistogram(
     "FX_URLBAR_SELECTED_RESULT_INDEX"
   );
   let resultTypeHist = TelemetryTestUtils.getAndClearHistogram(
     "FX_URLBAR_SELECTED_RESULT_TYPE_2"
@@ -311,16 +406,17 @@ add_task(async function test_searchAlias
 
   TelemetryTestUtils.assertHistogram(
     resultMethodHist,
     UrlbarTestUtils.SELECTED_RESULT_METHODS.enter,
     1
   );
 
   BrowserTestUtils.removeTab(tab);
+  await SpecialPowers.popPrefEnv();
 });
 
 // Performs a search using the first result, a one-off button, and the Return
 // (Enter) key.
 // This subtest can be removed with the update2 pref.
 add_task(async function test_oneOff_enter_legacy() {
   await SpecialPowers.pushPrefEnv({
     set: [
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar_searchmode.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar_searchmode.js
@@ -45,17 +45,21 @@ function assertSearchModeScalar(entry, k
         `No other urlbar.searchmode scalars should be recorded. Checking ${e}`
       );
     }
   }
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
-    set: [["browser.urlbar.update2", true]],
+    set: [
+      ["browser.urlbar.update2", true],
+      ["browser.urlbar.update2.localOneOffs", true],
+      ["browser.urlbar.update2.oneOffsRefresh", true],
+    ],
   });
 
   // Create a new search engine.
   await Services.search.addEngineWithDetails(ENGINE_NAME, {
     alias: ENGINE_ALIAS,
     method: "GET",
     template: "http://example.com/?q={searchTerms}",
   });
@@ -285,28 +289,31 @@ add_task(async function test_keywordoffe
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
 
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser,
     "about:blank"
   );
 
-  // Enter search mode by selecting a keywordoffer result.
+  // Do a search for "@" + our test alias.  It should autofill with a trailing
+  // space, and the heuristic result should be an autofill result with a keyword
+  // offer.
+  let alias = "@" + ENGINE_ALIAS;
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
-    value: ENGINE_ALIAS,
+    value: alias,
   });
   let keywordOfferResult = await UrlbarTestUtils.getDetailsOfResultAt(
     window,
     0
   );
   Assert.equal(
     keywordOfferResult.searchParams.keyword,
-    ENGINE_ALIAS,
+    alias,
     "The first result should be a keyword search result with the correct alias."
   );
 
   // Select the keyword offer.
   let searchPromise = UrlbarTestUtils.promiseSearchComplete(window);
   EventUtils.synthesizeKey("KEY_Enter");
   await searchPromise;
 
--- a/toolkit/components/places/UnifiedComplete.jsm
+++ b/toolkit/components/places/UnifiedComplete.jsm
@@ -391,32 +391,16 @@ function makeKeyForMatch(match) {
       });
       break;
   }
 
   return [key, prefix, action];
 }
 
 /**
- * Returns the portion of a string starting at the index where another string
- * ends.
- *
- * @param   {string} sourceStr
- *          The string to search within.
- * @param   {string} targetStr
- *          The string to search for.
- * @returns {string} The substring within sourceStr where targetStr ends, or the
- *          empty string if targetStr does not occur in sourceStr.
- */
-function substringAfter(sourceStr, targetStr) {
-  let index = sourceStr.indexOf(targetStr);
-  return index < 0 ? "" : sourceStr.substr(index + targetStr.length);
-}
-
-/**
  * Makes a moz-action url for the given action and set of parameters.
  *
  * @param   type
  *          The action type.
  * @param   params
  *          A JS object of action params.
  * @returns A moz-action url as a string.
  */
@@ -488,16 +472,17 @@ function Search(
     this._inPrivateWindow = queryContext.isPrivate;
     this._disablePrivateActions =
       this._inPrivateWindow && !PrivateBrowsingUtils.permanentPrivateBrowsing;
     this._prohibitAutoFill = !queryContext.allowAutofill;
     this._maxResults = queryContext.maxResults;
     this._userContextId = queryContext.userContextId;
     this._currentPage = queryContext.currentPage;
     this._searchModeEngine = queryContext.searchMode?.engineName;
+    this._searchMode = queryContext.searchMode;
   } else {
     let params = new Set(searchParam.split(" "));
     this._enableActions = params.has("enable-actions");
     this._disablePrivateActions = params.has("disable-private-actions");
     this._inPrivateWindow = params.has("private-window");
     this._prohibitAutoFill = params.has("prohibit-autofill");
     // Extract the max-results param.
     let maxResults = searchParam.match(REGEXP_MAX_RESULTS);
@@ -1004,16 +989,21 @@ Search.prototype = {
     let value = stripAnyPrefix(url)[1];
     value = value.substr(value.indexOf(this._searchString));
 
     this._addAutofillMatch(value, url, Infinity, ["preloaded-top-site"]);
     return true;
   },
 
   async _matchFirstHeuristicResult(conn) {
+    if (this._searchMode) {
+      // Use UrlbarProviderHeuristicFallback.
+      return false;
+    }
+
     // We always try to make the first result a special "heuristic" result.  The
     // heuristics below determine what type of result it will be, if any.
 
     if (this.pending && this._enableActions && this._heuristicToken) {
       // It may be a search engine with an alias - which works like a keyword.
       let matched = await this._matchSearchEngineAlias(this._heuristicToken);
       if (matched) {
         return true;
@@ -1042,17 +1032,17 @@ Search.prototype = {
   },
 
   async _matchPlacesKeyword(keyword) {
     let entry = await PlacesUtils.keywords.fetch(keyword);
     if (!entry) {
       return false;
     }
 
-    let searchString = substringAfter(
+    let searchString = UrlbarUtils.substringAfter(
       this._originalSearchString,
       keyword
     ).trim();
 
     let url = null;
     let postData = null;
     try {
       [url, postData] = await BrowserUtils.parseUrlAndPostData(
@@ -1101,21 +1091,28 @@ Search.prototype = {
   },
 
   async _matchSearchEngineAlias(alias) {
     let engine = await UrlbarSearchUtils.engineForAlias(alias);
     if (!engine) {
       return false;
     }
 
+    let query = UrlbarUtils.substringAfter(this._originalSearchString, alias);
+
+    // Match an alias only when it has a space after it.  If there's no trailing
+    // space, then continue to treat it as part of the search string.
+    if (UrlbarPrefs.get("update2") && !query.startsWith(" ")) {
+      return false;
+    }
+
     this._searchEngineAliasMatch = {
       engine,
       alias,
-      query: substringAfter(this._originalSearchString, alias).trim(),
-      isTokenAlias: alias.startsWith("@"),
+      query: query.trimStart(),
     };
     this._addSearchEngineMatch(this._searchEngineAliasMatch);
     if (!this._keywordSubstitute) {
       this._keywordSubstitute = {
         host: engine.getResultDomain(),
         keyword: alias,
       };
     }
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
@@ -6,16 +6,19 @@ const SUGGESTIONS_ENGINE_NAME = "engine-
 /**
  * Tests search engine aliases. See
  * browser/components/urlbar/tests/browser/browser_tokenAlias.js for tests of
  * the token alias list (i.e. showing all aliased engines on a "@" query).
  */
 // Basic test that uses two engines, a GET engine and a POST engine, neither
 // providing search suggestions.
 add_task(async function basicGetAndPost() {
+  // This test requires update2.  See also test_search_engine_alias_legacy.js.
+  Services.prefs.setBoolPref("browser.urlbar.update2", true);
+
   // Note that head_autocomplete.js has already added a MozSearch engine.
   // Here we add another engine with a search alias.
   await Services.search.addEngineWithDetails("AliasedGETMozSearch", {
     alias: "get",
     method: "GET",
     template: "http://s.example.com/search",
   });
   await Services.search.addEngineWithDetails("AliasedPOSTMozSearch", {
@@ -29,25 +32,17 @@ add_task(async function basicGetAndPost(
     value: "http://s.example.com/search?q=firefox",
     comment: "test visit for http://s.example.com/search?q=firefox",
   };
 
   for (let alias of ["get", "post"]) {
     await check_autocomplete({
       search: alias,
       searchParam: "enable-actions",
-      matches: [
-        makeSearchMatch(`${alias} `, {
-          engineName: `Aliased${alias.toUpperCase()}MozSearch`,
-          searchQuery: "",
-          alias,
-          heuristic: true,
-        }),
-        historyMatch,
-      ],
+      matches: [],
     });
 
     await check_autocomplete({
       search: `${alias} `,
       searchParam: "enable-actions",
       matches: [
         makeSearchMatch(`${alias} `, {
           engineName: `Aliased${alias.toUpperCase()}MozSearch`,
copy from toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
copy to toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias_legacy.js
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias_legacy.js
@@ -1,21 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const SUGGESTIONS_ENGINE_NAME = "engine-suggestions.xml";
 
 /**
+ * This file can be deleted when update2 is enabled by default.
+ *
  * Tests search engine aliases. See
  * browser/components/urlbar/tests/browser/browser_tokenAlias.js for tests of
  * the token alias list (i.e. showing all aliased engines on a "@" query).
  */
 // Basic test that uses two engines, a GET engine and a POST engine, neither
 // providing search suggestions.
 add_task(async function basicGetAndPost() {
+  Services.prefs.setBoolPref("browser.urlbar.update2", false);
+
   // Note that head_autocomplete.js has already added a MozSearch engine.
   // Here we add another engine with a search alias.
   await Services.search.addEngineWithDetails("AliasedGETMozSearch", {
     alias: "get",
     method: "GET",
     template: "http://s.example.com/search",
   });
   await Services.search.addEngineWithDetails("AliasedPOSTMozSearch", {
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -21,13 +21,14 @@ support-files =
 [test_ignore_protocol.js]
 [test_keyword_search.js]
 [test_keyword_search_actions.js]
 [test_multi_word_search.js]
 [test_preloaded_sites.js]
 [test_remote_tab_matches.js]
 skip-if = !sync
 [test_search_engine_alias.js]
+[test_search_engine_alias_legacy.js]
 [test_search_engine_restyle.js]
 [test_special_search.js]
 [test_swap_protocol.js]
 [test_tab_matches.js]
 [test_word_boundary_search.js]