Bug 1640045 - Replace PlacesSearchAutocompleteProvider with UrlbarSearchUtils, remove its parseSubmissionURL function, and init the search service on the first query. r=mak
authorDrew Willcoxon <adw@mozilla.com>
Tue, 16 Jun 2020 01:08:38 +0000
changeset 535787 58e905055fe282a6e450c54e1ac6a1dba6c47f22
parent 535786 62d50af432bbd33a1de6f4a06c4f42f05d564c60
child 535788 b49ea5f44be02a75d4afeeb7d2db6876402afa0a
push id37509
push usercsabou@mozilla.com
push dateTue, 16 Jun 2020 03:30:48 +0000
treeherdermozilla-central@b49ea5f44be0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1640045
milestone79.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 1640045 - Replace PlacesSearchAutocompleteProvider with UrlbarSearchUtils, remove its parseSubmissionURL function, and init the search service on the first query. r=mak * Replace PlacesSearchAutocompleteProvider with UrlbarSearchUtils. * Move the module from toolkit to browser. The only consumers of PlacesSearchAutocompleteProvider are urlbar and UnifiedComplete. * I'd like to add functions to UrlbarUtils instead, but PlacesSearchAutocompleteProvider adds itself as an observer for search engine changes, and it keeps some state so that alias lookups are O(1). It has an init function to set that up. That's not quite as easy to do if I just added some functions to UrlbarUtils, and I think that O(1) lookups of aliases are worth keeping (vs. O(number of installed engines)), so I kept a separate module, now called UrlbarSearchUtils. * Init the search service (via UrlbarSearchUtils) from UrlbarProvidersManager.startQuery so that every module involved in querying doesn't need to do it. * Remove PlacesSearchAutocompleteProvider.currentEngine. Previous consumers can simply use Services.search directly now that the service is initialized early (see previous point). * Remove PlacesSearchAutocompleteProvider.parseSubmissionURL. Here again consumers can use Services.search directly. Differential Revision: https://phabricator.services.mozilla.com/D79244
browser/components/extensions/test/xpcshell/test_ext_urlbar.js
browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
browser/components/urlbar/UrlbarProviderExtension.jsm
browser/components/urlbar/UrlbarProviderSearchSuggestions.jsm
browser/components/urlbar/UrlbarProviderTokenAliasEngines.jsm
browser/components/urlbar/UrlbarProviderTopSites.jsm
browser/components/urlbar/UrlbarProvidersManager.jsm
browser/components/urlbar/UrlbarSearchUtils.jsm
browser/components/urlbar/moz.build
browser/components/urlbar/tests/UrlbarTestUtils.jsm
browser/components/urlbar/tests/browser/browser_top_sites.js
browser/components/urlbar/tests/browser/head.js
browser/components/urlbar/tests/unit/head.js
browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.jsm
browser/components/urlbar/tests/unit/xpcshell.ini
toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
toolkit/components/places/UnifiedComplete.jsm
toolkit/components/places/moz.build
toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.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
@@ -1351,24 +1351,18 @@ add_task(async function test_privateBrow
     searchString: "*",
   });
   let controller = UrlbarTestUtils.newMockController();
 
   let startPromise = controller.startQuery(context);
   controller.cancelQuery();
   await startPromise;
 
-  // Check isActive and priority.
-  Assert.ok(provider.isActive(context));
-  Assert.equal(provider.getPriority(context), 0);
-
-  // The events should have been fired.
-  await Promise.all(
-    ["onBehaviorRequested", "onQueryCanceled"].map(msg => ext.awaitMessage(msg))
-  );
+  // onQueryCanceled should have been fired.
+  await ext.awaitMessage("onQueryCanceled");
 
   await ext.unload();
 });
 
 // Performs a search in a non-private context for an extension that does not
 // allow private browsing.  The extension's listeners should be called.
 add_task(async function test_nonPrivateBrowsing() {
   let ext = ExtensionTestUtils.loadExtension({
--- a/browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
+++ b/browser/components/urlbar/UrlbarMuxerUnifiedComplete.jsm
@@ -10,18 +10,17 @@
 
 var EXPORTED_SYMBOLS = ["UrlbarMuxerUnifiedComplete"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
   Log: "resource://gre/modules/Log.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
+  Services: "resource://gre/modules/Services.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarMuxer: "resource:///modules/UrlbarUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "logger", () =>
   Log.repository.getLogger("Urlbar.Muxer.UnifiedComplete")
 );
@@ -189,25 +188,17 @@ class MuxerUnifiedComplete extends Urlba
       }
 
       // Exclude SERPs from browser history that dupe either the heuristic or
       // included form history.
       if (
         result.source == UrlbarUtils.RESULT_SOURCE.HISTORY &&
         result.type == UrlbarUtils.RESULT_TYPE.URL
       ) {
-        let submission;
-        try {
-          // parseSubmissionURL throws if PlacesSearchAutocompleteProvider
-          // hasn't finished initializing, so try-catch this call.  There's no
-          // harm if it throws, we just won't dedupe SERPs this time.
-          submission = PlacesSearchAutocompleteProvider.parseSubmissionURL(
-            result.payload.url
-          );
-        } catch (error) {}
+        let submission = Services.search.parseSubmissionURL(result.payload.url);
         if (submission) {
           let resultQuery = submission.terms.toLocaleLowerCase();
           if (
             heuristicResultQuery === resultQuery ||
             formHistorySuggestions.has(resultQuery)
           ) {
             // If the result's URL is the same as a brand new SERP URL created
             // from the query string modulo certain URL params, then treat the
--- a/browser/components/urlbar/UrlbarProviderExtension.jsm
+++ b/browser/components/urlbar/UrlbarProviderExtension.jsm
@@ -10,23 +10,22 @@
  */
 
 var EXPORTED_SYMBOLS = ["UrlbarProviderExtension"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   Services: "resource://gre/modules/Services.jsm",
   SkippableTimer: "resource:///modules/UrlbarUtils.jsm",
   UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
   UrlbarResult: "resource:///modules/UrlbarResult.jsm",
+  UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 // When we send events to extensions, we wait this amount of time in ms for them
 // to respond before timing out.  Tests can override this by setting
 // UrlbarProviderExtension.notificationTimeout.
 const DEFAULT_NOTIFICATION_TIMEOUT = 200;
 
@@ -298,29 +297,27 @@ class UrlbarProviderExtension extends Ur
     // one domain can have many engines.
     if (extResult.type == "search") {
       let engine;
       if (extResult.payload.engine) {
         // Validate the engine name by looking it up.
         engine = Services.search.getEngineByName(extResult.payload.engine);
       } else if (extResult.payload.keyword) {
         // Look up the engine by its alias.
-        engine = await PlacesSearchAutocompleteProvider.engineForAlias(
+        engine = await UrlbarSearchUtils.engineForAlias(
           extResult.payload.keyword
         );
       } else if (extResult.payload.url) {
         // Look up the engine by its domain.
         let host;
         try {
           host = new URL(extResult.payload.url).hostname;
         } catch (err) {}
         if (host) {
-          engine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-            host
-          );
+          engine = await UrlbarSearchUtils.engineForDomainPrefix(host);
         }
       }
       if (!engine) {
         // No engine found.
         throw new Error("Invalid or missing engine specified by extension");
       }
       extResult.payload.engine = engine.name;
     }
--- a/browser/components/urlbar/UrlbarProviderSearchSuggestions.jsm
+++ b/browser/components/urlbar/UrlbarProviderSearchSuggestions.jsm
@@ -10,25 +10,24 @@
 
 var EXPORTED_SYMBOLS = ["UrlbarProviderSearchSuggestions"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
   Log: "resource://gre/modules/Log.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   SearchSuggestionController:
     "resource://gre/modules/SearchSuggestionController.jsm",
   Services: "resource://gre/modules/Services.jsm",
   SkippableTimer: "resource:///modules/UrlbarUtils.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
   UrlbarResult: "resource:///modules/UrlbarResult.jsm",
+  UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "logger", () =>
   Log.repository.getLogger("Urlbar.Provider.SearchSuggestions")
 );
 
@@ -259,25 +258,26 @@ class ProviderSearchSuggestions extends 
     if (leadingRestrictionToken === UrlbarTokenizer.RESTRICT.SEARCH) {
       query = UrlbarUtils.substringAfter(query, leadingRestrictionToken).trim();
     }
 
     // Find our search engine. It may have already been set with an alias.
     let engine;
     if (aliasEngine) {
       engine = aliasEngine.engine;
+    } else if (queryContext.engineName) {
+      engine = Services.search.getEngineByName(queryContext.engineName);
+    } else if (queryContext.isPrivate) {
+      engine = Services.search.defaultPrivateEngine;
     } else {
-      engine = queryContext.engineName
-        ? Services.search.getEngineByName(queryContext.engineName)
-        : await PlacesSearchAutocompleteProvider.currentEngine(
-            queryContext.isPrivate
-          );
-      if (!engine) {
-        return;
-      }
+      engine = Services.search.defaultEngine;
+    }
+
+    if (!engine) {
+      return;
     }
 
     let alias = (aliasEngine && aliasEngine.alias) || "";
     let results = await this._fetchSearchSuggestions(
       queryContext,
       engine,
       query,
       alias
@@ -498,47 +498,26 @@ class ProviderSearchSuggestions extends 
     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.
     if (!possibleAlias || possibleAlias == "@") {
       return null;
     }
 
     // Check if the user entered an engine alias directly.
-    let engineMatch = await PlacesSearchAutocompleteProvider.engineForAlias(
-      possibleAlias
-    );
+    let engineMatch = await UrlbarSearchUtils.engineForAlias(possibleAlias);
     if (engineMatch) {
       return {
         engine: engineMatch,
         alias: possibleAlias,
         query: UrlbarUtils.substringAfter(
           queryContext.searchString,
           possibleAlias
         ).trim(),
         isTokenAlias: possibleAlias.startsWith("@"),
       };
     }
 
-    // Check if the user is matching a token alias.
-    let engines = await PlacesSearchAutocompleteProvider.tokenAliasEngines();
-    if (!engines || !engines.length) {
-      return null;
-    }
-
-    for (let { engine, tokenAliases } of engines) {
-      if (tokenAliases.includes(possibleAlias)) {
-        return {
-          engine,
-          alias: possibleAlias,
-          query: UrlbarUtils.substringAfter(
-            queryContext.searchString,
-            possibleAlias
-          ).trim(),
-          isTokenAlias: true,
-        };
-      }
-    }
     return null;
   }
 }
 
 var UrlbarProviderSearchSuggestions = new ProviderSearchSuggestions();
--- a/browser/components/urlbar/UrlbarProviderTokenAliasEngines.jsm
+++ b/browser/components/urlbar/UrlbarProviderTokenAliasEngines.jsm
@@ -10,20 +10,19 @@
 
 var EXPORTED_SYMBOLS = ["UrlbarProviderTokenAliasEngines"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
   Log: "resource://gre/modules/Log.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
   UrlbarResult: "resource:///modules/UrlbarResult.jsm",
+  UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "logger", () =>
   Log.repository.getLogger("Urlbar.Provider.TokenAliasEngines")
 );
 
 /**
@@ -63,17 +62,17 @@ class ProviderTokenAliasEngines extends 
    * If this method returns false, the providers manager won't start a query
    * with this provider, to save on resources.
    * @param {UrlbarQueryContext} queryContext The query context object
    * @returns {boolean} Whether this provider should be invoked for the search.
    */
   async isActive(queryContext) {
     this._engines = [];
     if (queryContext.searchString.trim() == "@") {
-      this._engines = await PlacesSearchAutocompleteProvider.tokenAliasEngines();
+      this._engines = await UrlbarSearchUtils.tokenAliasEngines();
     }
 
     return this._engines.length;
   }
 
   /**
    * Starts querying.
    * @param {object} queryContext The query context object
--- a/browser/components/urlbar/UrlbarProviderTopSites.jsm
+++ b/browser/components/urlbar/UrlbarProviderTopSites.jsm
@@ -9,23 +9,22 @@ var EXPORTED_SYMBOLS = ["UrlbarProviderT
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AboutNewTab: "resource:///modules/AboutNewTab.jsm",
   Log: "resource://gre/modules/Log.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   Services: "resource://gre/modules/Services.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
   UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.jsm",
   UrlbarResult: "resource:///modules/UrlbarResult.jsm",
+  UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
   TOP_SITES_MAX_SITES_PER_ROW: "resource://activity-stream/common/Reducers.jsm",
   TOP_SITES_DEFAULT_ROWS: "resource://activity-stream/common/Reducers.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "logger", () =>
   Log.repository.getLogger("Urlbar.Provider.TopSites")
 );
@@ -185,30 +184,26 @@ class ProviderTopSites extends UrlbarPro
           if (!this.queries.get(queryContext)) {
             break;
           }
 
           addCallback(this, result);
           break;
         }
         case "search": {
-          let engine = await PlacesSearchAutocompleteProvider.engineForAlias(
-            site.title
-          );
+          let engine = await UrlbarSearchUtils.engineForAlias(site.title);
 
           if (!engine && site.url) {
             // Look up the engine by its domain.
             let host;
             try {
               host = new URL(site.url).hostname;
             } catch (err) {}
             if (host) {
-              engine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-                host
-              );
+              engine = await UrlbarSearchUtils.engineForDomainPrefix(host);
             }
           }
 
           if (!engine) {
             // No engine found. We skip this Top Site.
             break;
           }
 
--- a/browser/components/urlbar/UrlbarProvidersManager.jsm
+++ b/browser/components/urlbar/UrlbarProvidersManager.jsm
@@ -16,16 +16,17 @@ const { XPCOMUtils } = ChromeUtils.impor
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
   Log: "resource://gre/modules/Log.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",
 });
 
 XPCOMUtils.defineLazyGetter(this, "logger", () =>
   Log.repository.getLogger("Urlbar.ProvidersManager")
 );
 
@@ -191,16 +192,24 @@ class ProvidersManager {
     // Providers can use queryContext.sources to decide whether they want to be
     // invoked or not.
     updateSourcesIfEmpty(queryContext);
     logger.debug(`Context sources ${queryContext.sources}`);
 
     let query = new Query(queryContext, controller, muxer, providers);
     this.queries.set(queryContext, query);
 
+    // The muxer and many providers depend on the search service and our search
+    // utils.  Make sure they're initialized now (via UrlbarSearchUtils) so that
+    // all query-related urlbar modules don't need to do it.
+    await UrlbarSearchUtils.init();
+    if (query.canceled) {
+      return;
+    }
+
     // Update the behavior of extension providers.
     for (let provider of this.providers) {
       if (
         provider.type == UrlbarUtils.PROVIDER_TYPE.EXTENSION &&
         provider.name != "Omnibox"
       ) {
         await provider.tryMethod("updateBehavior", queryContext);
       }
rename from toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
rename to browser/components/urlbar/UrlbarSearchUtils.jsm
--- a/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
+++ b/browser/components/urlbar/UrlbarSearchUtils.jsm
@@ -1,226 +1,136 @@
 /* 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/. */
 
 /*
- * Provides functions to handle search engine URLs in the browser history.
+ * Search service utilities for urlbar.  The only reason these functions aren't
+ * a part of UrlbarUtils is that we want O(1) case-insensitive lookup for search
+ * aliases, and to do that we need to observe the search service, persistent
+ * state, and an init method.  A separate object is easier.
  */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["PlacesSearchAutocompleteProvider"];
+var EXPORTED_SYMBOLS = ["UrlbarSearchUtils"];
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
 
-const SearchAutocompleteProviderInternal = {
-  /**
-   * {Map<string: nsISearchEngine>} Maps from each domain to the engine with
-   * that domain.  If more than one engine has the same domain, the last one
-   * passed to _addEngine will be the one in this map.
-   */
-  enginesByDomain: new Map(),
+/**
+ * Search service utilities for urlbar.
+ */
+class SearchUtils {
+  constructor() {
+    this._refreshEnginesByAliasPromise = Promise.resolve();
+    this.QueryInterface = ChromeUtils.generateQI([
+      Ci.nsIObserver,
+      Ci.nsISupportsWeakReference,
+    ]);
+  }
 
   /**
-   * {Map<string: nsISearchEngine>} Maps from each lowercased alias to the
-   * engine with that alias.  If more than one engine has the same alias, the
-   * last one passed to _addEngine will be the one in this map.
+   * Initializes the instance and also Services.search.
    */
-  enginesByAlias: new Map(),
+  async init() {
+    if (!this._initPromise) {
+      this._initPromise = this._initInternal();
+    }
+    await this._initPromise;
+  }
+
+  /**
+   * Gets the engine whose domain matches a given prefix.
+   *
+   * @param {string} prefix
+   *   String containing the first part of the matching domain name.
+   * @returns {nsISearchEngine}
+   *   The matching engine or null if there isn't one.
+   */
+  async engineForDomainPrefix(prefix) {
+    await this.init();
+    for (let engine of await Services.search.getVisibleEngines()) {
+      let domain = engine.getResultDomain();
+      if (domain.startsWith(prefix) || domain.startsWith("www." + prefix)) {
+        return engine;
+      }
+    }
+    return null;
+  }
 
   /**
-   * {array<{ {nsISearchEngine} engine, {array<string>} tokenAliases }>} Array
-   * of engines that have "@" aliases.
+   * Gets the engine with a given alias.
+   *
+   * @param {string} alias
+   *   A search engine alias.  The alias string comparison is case insensitive.
+   * @returns {nsISearchEngine}
+   *   The matching engine or null if there isn't one.
    */
-  tokenAliasEngines: [],
+  async engineForAlias(alias) {
+    await Promise.all([this.init(), this._refreshEnginesByAliasPromise]);
+    return this._enginesByAlias.get(alias.toLocaleLowerCase()) || null;
+  }
 
-  async initialize() {
-    try {
-      await Services.search.init();
-    } catch (errorCode) {
-      throw new Error("Unable to initialize search service.");
+  /**
+   * The list of engines with token ("@") aliases.
+   *
+   * @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 = this._engineAliases(engine).filter(a =>
+        a.startsWith("@")
+      );
+      if (tokenAliases.length) {
+        tokenAliasEngines.push({ engine, tokenAliases });
+      }
     }
+    return tokenAliasEngines;
+  }
 
-    // The initial loading of the search engines must succeed.
-    this._refreshedPromise = this._refresh();
-    await this._refreshedPromise;
-
+  async _initInternal() {
+    await Services.search.init();
+    await this._refreshEnginesByAlias();
     Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
+  }
 
-    this.initialized = true;
-  },
+  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) {
+        let aliases = this._engineAliases(engine);
+        for (let alias of aliases) {
+          this._enginesByAlias.set(alias.toLocaleLowerCase(), engine);
+        }
+      }
+    }
+  }
 
-  initialized: false,
+  _engineAliases(engine) {
+    let aliases = [];
+    if (engine.alias) {
+      aliases.push(engine.alias);
+    }
+    aliases.push(...engine.wrappedJSObject._internalAliases);
+    return aliases;
+  }
 
   observe(subject, topic, data) {
     switch (data) {
       case "engine-added":
       case "engine-changed":
       case "engine-removed":
       case "engine-default":
-        this._refreshedPromise = this._refresh();
-    }
-  },
-
-  async _refresh() {
-    this.enginesByDomain.clear();
-    this.enginesByAlias.clear();
-    this.tokenAliasEngines = [];
-
-    // The search engines will always be processed in the order returned by the
-    // search service, which can be defined by the user.
-    (await Services.search.getEngines()).forEach(e => this._addEngine(e));
-  },
-
-  _addEngine(engine) {
-    if (engine.hidden) {
-      return;
-    }
-
-    let domain = engine.getResultDomain();
-    if (domain) {
-      this.enginesByDomain.set(domain, engine);
-    }
-
-    let aliases = [];
-    if (engine.alias) {
-      aliases.push(engine.alias);
-    }
-    aliases.push(...engine.wrappedJSObject._internalAliases);
-    for (let alias of aliases) {
-      this.enginesByAlias.set(alias.toLocaleLowerCase(), engine);
-    }
-
-    let tokenAliases = aliases.filter(a => a.startsWith("@"));
-    if (tokenAliases.length) {
-      this.tokenAliasEngines.push({ engine, tokenAliases });
-    }
-  },
-
-  QueryInterface: ChromeUtils.generateQI([
-    Ci.nsIObserver,
-    Ci.nsISupportsWeakReference,
-  ]),
-};
-
-var gInitializationPromise = null;
-
-var PlacesSearchAutocompleteProvider = Object.freeze({
-  /**
-   * Starts initializing the component and returns a promise that is resolved or
-   * rejected when initialization and updates are finished.
-   */
-  ensureReady() {
-    if (!gInitializationPromise) {
-      gInitializationPromise = SearchAutocompleteProviderInternal.initialize();
-    }
-
-    return Promise.all([
-      gInitializationPromise,
-      SearchAutocompleteProviderInternal._refreshedPromise,
-    ]);
-  },
-
-  /**
-   * Gets the engine whose domain matches a given prefix.
-   *
-   * @param   {string} prefix
-   *          String containing the first part of the matching domain name.
-   * @returns {nsISearchEngine} The matching engine or null if there isn't one.
-   */
-  async engineForDomainPrefix(prefix) {
-    await this.ensureReady();
-
-    // Match at the beginning for now.  In the future, an "options" argument may
-    // allow the matching behavior to be tuned.
-    let tuples = SearchAutocompleteProviderInternal.enginesByDomain.entries();
-    for (let [domain, engine] of tuples) {
-      if (domain.startsWith(prefix) || domain.startsWith("www." + prefix)) {
-        return engine;
-      }
+        this._refreshEnginesByAliasPromise = this._refreshEnginesByAlias();
+        break;
     }
-    return null;
-  },
-
-  /**
-   * Gets the engine with a given alias.
-   *
-   * @param   {string} alias
-   *          A search engine alias.
-   * @returns {nsISearchEngine} The matching engine or null if there isn't one.
-   */
-  async engineForAlias(alias) {
-    await this.ensureReady();
-
-    return (
-      SearchAutocompleteProviderInternal.enginesByAlias.get(
-        alias.toLocaleLowerCase()
-      ) || null
-    );
-  },
-
-  /**
-   * Gets the list of engines with token ("@") aliases.
-   *
-   * @returns {array<{ {nsISearchEngine} engine, {array<string>} tokenAliases }>}
-   *          Array of objects { engine, tokenAliases } for token alias engines.
-   */
-  async tokenAliasEngines() {
-    await this.ensureReady();
-
-    return SearchAutocompleteProviderInternal.tokenAliasEngines.slice();
-  },
+  }
+}
 
-  /**
-   * Use this to get the current engine rather than Services.search.defaultEngine
-   * directly.  This method makes sure that the service is first initialized.
-   *
-   * @param {boolean} inPrivateWindow
-   *   Set to true if this search is being run in a private window.
-   * @returns {nsISearchEngine} The current search engine.
-   */
-  async currentEngine(inPrivateWindow) {
-    await this.ensureReady();
-
-    return inPrivateWindow
-      ? Services.search.defaultPrivateEngine
-      : Services.search.defaultEngine;
-  },
-
-  /**
-   * Synchronously determines if the provided URL represents results from a
-   * search engine, and provides details about the match.
-   *
-   * @param url
-   *        String containing the URL to parse.
-   *
-   * @return An object with the following properties, or null if the URL does
-   *         not represent a search result:
-   *         {
-   *           engine: The search engine, as an nsISearchEngine.
-   *           terms: The originally sought terms extracted from the URI.
-   *           termsParameterName: The engine's search-string parameter.
-   *         }
-   *
-   * @remarks The asynchronous ensureInitialized function must be called before
-   *          this synchronous method can be used.
-   *
-   * @note This API function needs to be synchronous because it is called inside
-   *       a row processing callback of Sqlite.jsm, in UnifiedComplete.js.
-   */
-  parseSubmissionURL(url) {
-    if (!SearchAutocompleteProviderInternal.initialized) {
-      throw new Error("The component has not been initialized.");
-    }
-
-    let parseUrlResult = Services.search.parseSubmissionURL(url);
-    return (
-      parseUrlResult.engine && {
-        engine: parseUrlResult.engine,
-        terms: parseUrlResult.terms,
-        termsParameterName: parseUrlResult.termsParameterName,
-      }
-    );
-  },
-});
+var UrlbarSearchUtils = new SearchUtils();
--- a/browser/components/urlbar/moz.build
+++ b/browser/components/urlbar/moz.build
@@ -18,16 +18,17 @@ EXTRA_JS_MODULES += [
     'UrlbarProviderPrivateSearch.jsm',
     'UrlbarProviderSearchSuggestions.jsm',
     'UrlbarProviderSearchTips.jsm',
     'UrlbarProvidersManager.jsm',
     'UrlbarProviderTokenAliasEngines.jsm',
     'UrlbarProviderTopSites.jsm',
     'UrlbarProviderUnifiedComplete.jsm',
     'UrlbarResult.jsm',
+    'UrlbarSearchUtils.jsm',
     'UrlbarTokenizer.jsm',
     'UrlbarUtils.jsm',
     'UrlbarValueFormatter.jsm',
     'UrlbarView.jsm',
 ]
 
 TESTING_JS_MODULES += [
     'tests/UrlbarTestUtils.jsm',
--- a/browser/components/urlbar/tests/UrlbarTestUtils.jsm
+++ b/browser/components/urlbar/tests/UrlbarTestUtils.jsm
@@ -11,18 +11,16 @@ const { XPCOMUtils } = ChromeUtils.impor
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonTestUtils: "resource://testing-common/AddonTestUtils.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   BrowserTestUtils: "resource://testing-common/BrowserTestUtils.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   FormHistory: "resource://gre/modules/FormHistory.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
   TestUtils: "resource://testing-common/TestUtils.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProvider: "resource:///modules/UrlbarUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
@@ -383,27 +381,25 @@ var UrlbarTestUtils = {
    */
   async initXPCShellDependencies() {
     // The FormHistoryStartup component must be initialized since urlbar uses
     // form history.
     Cc["@mozilla.org/satchel/form-history-startup;1"]
       .getService(Ci.nsIObserver)
       .observe(null, "profile-after-change", null);
 
-    // These two calls are necessary because UrlbarMuxerUnifiedComplete.sort
-    // calls PlacesSearchAutocompleteProvider.parseSubmissionURL, so we need
-    // engines and PlacesSearchAutocompleteProvider.
+    // This is necessary because UrlbarMuxerUnifiedComplete.sort calls
+    // Services.search.parseSubmissionURL, so we need engines.
     try {
       await AddonTestUtils.promiseStartupManager();
     } catch (error) {
       if (!error.message.includes("already started")) {
         throw error;
       }
     }
-    await PlacesSearchAutocompleteProvider.ensureReady();
   },
 };
 
 UrlbarTestUtils.formHistory = {
   /**
    * Performs an operation on the urlbar's form history.
    *
    * @param {object} updateObject
--- a/browser/components/urlbar/tests/browser/browser_top_sites.js
+++ b/browser/components/urlbar/tests/browser/browser_top_sites.js
@@ -1,18 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AboutNewTab: "resource:///modules/AboutNewTab.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
 });
 
 const EN_US_TOPSITES =
   "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/";
 
 async function addTestVisits() {
   // Add some visits to a URL.
   for (let i = 0; i < 5; i++) {
--- a/browser/components/urlbar/tests/browser/head.js
+++ b/browser/components/urlbar/tests/browser/head.js
@@ -14,18 +14,19 @@ var { XPCOMUtils } = ChromeUtils.import(
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
   AboutNewTab: "resource:///modules/AboutNewTab.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   ResetProfile: "resource://gre/modules/ResetProfile.jsm",
   TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
+  UrlbarQueryContext: "resource:///modules/UrlbarUtils.jsm",
   UrlbarResult: "resource:///modules/UrlbarResult.jsm",
-  UrlbarQueryContext: "resource:///modules/UrlbarUtils.jsm",
+  UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 /* import-globals-from head-common.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/head-common.js",
   this
 );
--- a/browser/components/urlbar/tests/unit/head.js
+++ b/browser/components/urlbar/tests/unit/head.js
@@ -10,18 +10,16 @@ var {
   UrlbarProvider,
   UrlbarQueryContext,
   UrlbarUtils,
 } = ChromeUtils.import("resource:///modules/UrlbarUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonTestUtils: "resource://testing-common/AddonTestUtils.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   HttpServer: "resource://testing-common/httpd.js",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
   TestUtils: "resource://testing-common/TestUtils.jsm",
   UrlbarController: "resource:///modules/UrlbarController.jsm",
   UrlbarInput: "resource:///modules/UrlbarInput.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
rename from toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
rename to browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.jsm
--- a/toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.jsm
@@ -1,197 +1,150 @@
 /* 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 { PlacesSearchAutocompleteProvider } = ChromeUtils.import(
-  "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm"
+const { UrlbarSearchUtils } = ChromeUtils.import(
+  "resource:///modules/UrlbarSearchUtils.jsm"
 );
 
 add_task(async function() {
-  await Services.search.init();
+  await UrlbarSearchUtils.init();
   // Tell the search service we are running in the US.  This also has the
   // desired side-effect of preventing our geoip lookup.
   Services.prefs.setCharPref("browser.search.region", "US");
   Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
 
   Services.search.restoreDefaultEngines();
   Services.search.resetToOriginalDefaultEngine();
 });
 
 add_task(async function search_engine_match() {
   let engine = await Services.search.getDefault();
   let domain = engine.getResultDomain();
   let token = domain.substr(0, 1);
-  let matchedEngine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-    token
-  );
+  let matchedEngine = await UrlbarSearchUtils.engineForDomainPrefix(token);
   Assert.equal(matchedEngine, engine);
 });
 
 add_task(async function no_match() {
-  Assert.equal(
-    null,
-    await PlacesSearchAutocompleteProvider.engineForDomainPrefix("test")
-  );
+  Assert.equal(null, await UrlbarSearchUtils.engineForDomainPrefix("test"));
 });
 
 add_task(async function hide_search_engine_nomatch() {
   let engine = await Services.search.getDefault();
   let domain = engine.getResultDomain();
   let token = domain.substr(0, 1);
   let promiseTopic = promiseSearchTopic("engine-changed");
   await Promise.all([Services.search.removeEngine(engine), promiseTopic]);
   Assert.ok(engine.hidden);
-  let matchedEngine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-    token
-  );
+  let matchedEngine = await UrlbarSearchUtils.engineForDomainPrefix(token);
   Assert.ok(!matchedEngine || matchedEngine.getResultDomain() != domain);
   engine.hidden = false;
   await TestUtils.waitForCondition(() =>
-    PlacesSearchAutocompleteProvider.engineForDomainPrefix(token)
+    UrlbarSearchUtils.engineForDomainPrefix(token)
   );
-  let matchedEngine2 = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-    token
-  );
+  let matchedEngine2 = await UrlbarSearchUtils.engineForDomainPrefix(token);
   Assert.ok(matchedEngine2);
 });
 
 add_task(async function add_search_engine_match() {
   let promiseTopic = promiseSearchTopic("engine-added");
-  Assert.equal(
-    null,
-    await PlacesSearchAutocompleteProvider.engineForDomainPrefix("bacon")
-  );
+  Assert.equal(null, await UrlbarSearchUtils.engineForDomainPrefix("bacon"));
   await Promise.all([
     Services.search.addEngineWithDetails("bacon", {
       alias: "pork",
       description: "Search Bacon",
       method: "GET",
       template: "http://www.bacon.moz/?search={searchTerms}",
     }),
     promiseTopic,
   ]);
   await promiseTopic;
-  let matchedEngine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-    "bacon"
-  );
+  let matchedEngine = await UrlbarSearchUtils.engineForDomainPrefix("bacon");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.searchForm, "http://www.bacon.moz");
   Assert.equal(matchedEngine.name, "bacon");
   Assert.equal(matchedEngine.iconURI, null);
 });
 
 add_task(async function test_aliased_search_engine_match() {
-  Assert.equal(
-    null,
-    await PlacesSearchAutocompleteProvider.engineForAlias("sober")
-  );
+  Assert.equal(null, await UrlbarSearchUtils.engineForAlias("sober"));
   // Lower case
-  let matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias(
-    "pork"
-  );
+  let matchedEngine = await UrlbarSearchUtils.engineForAlias("pork");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "bacon");
   Assert.equal(matchedEngine.alias, "pork");
   Assert.equal(matchedEngine.iconURI, null);
   // Upper case
-  matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias("PORK");
+  matchedEngine = await UrlbarSearchUtils.engineForAlias("PORK");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "bacon");
   Assert.equal(matchedEngine.alias, "pork");
   Assert.equal(matchedEngine.iconURI, null);
   // Cap case
-  matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias("Pork");
+  matchedEngine = await UrlbarSearchUtils.engineForAlias("Pork");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "bacon");
   Assert.equal(matchedEngine.alias, "pork");
   Assert.equal(matchedEngine.iconURI, null);
 });
 
 add_task(async function test_aliased_search_engine_match_upper_case_alias() {
   let promiseTopic = promiseSearchTopic("engine-added");
-  Assert.equal(
-    null,
-    await PlacesSearchAutocompleteProvider.engineForDomainPrefix("patch")
-  );
+  Assert.equal(null, await UrlbarSearchUtils.engineForDomainPrefix("patch"));
   await Promise.all([
     Services.search.addEngineWithDetails("patch", {
       alias: "PR",
       description: "Search Patch",
       method: "GET",
       template: "http://www.patch.moz/?search={searchTerms}",
     }),
     promiseTopic,
   ]);
   // lower case
-  let matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias(
-    "pr"
-  );
+  let matchedEngine = await UrlbarSearchUtils.engineForAlias("pr");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "patch");
   Assert.equal(matchedEngine.alias, "PR");
   Assert.equal(matchedEngine.iconURI, null);
   // Upper case
-  matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias("PR");
+  matchedEngine = await UrlbarSearchUtils.engineForAlias("PR");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "patch");
   Assert.equal(matchedEngine.alias, "PR");
   Assert.equal(matchedEngine.iconURI, null);
   // Cap case
-  matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias("Pr");
+  matchedEngine = await UrlbarSearchUtils.engineForAlias("Pr");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "patch");
   Assert.equal(matchedEngine.alias, "PR");
   Assert.equal(matchedEngine.iconURI, null);
 });
 
 add_task(async function remove_search_engine_nomatch() {
   let engine = Services.search.getEngineByName("bacon");
   let promiseTopic = promiseSearchTopic("engine-removed");
   await Promise.all([Services.search.removeEngine(engine), promiseTopic]);
-  Assert.equal(
-    null,
-    await PlacesSearchAutocompleteProvider.engineForDomainPrefix("bacon")
-  );
-});
-
-add_task(async function test_parseSubmissionURL_basic() {
-  // Most of the logic of parseSubmissionURL is tested in the search service
-  // itself, thus we only do a sanity check of the wrapper here.
-  let engine = await Services.search.getDefault();
-  let submissionURL = engine.getSubmission("terms").uri.spec;
-
-  let result = PlacesSearchAutocompleteProvider.parseSubmissionURL(
-    submissionURL
-  );
-  Assert.equal(result.engine.name, engine.name);
-  Assert.equal(result.terms, "terms");
-
-  result = PlacesSearchAutocompleteProvider.parseSubmissionURL(
-    "http://example.org/"
-  );
-  Assert.equal(result, null);
+  Assert.equal(null, await UrlbarSearchUtils.engineForDomainPrefix("bacon"));
 });
 
 add_task(async function test_builtin_aliased_search_engine_match() {
-  let engine = await PlacesSearchAutocompleteProvider.engineForAlias("@google");
+  let engine = await UrlbarSearchUtils.engineForAlias("@google");
   Assert.ok(engine);
   Assert.equal(engine.name, "Google");
   let promiseTopic = promiseSearchTopic("engine-changed");
   await Promise.all([Services.search.removeEngine(engine), promiseTopic]);
-  let matchedEngine = await PlacesSearchAutocompleteProvider.engineForAlias(
-    "@google"
-  );
+  let matchedEngine = await UrlbarSearchUtils.engineForAlias("@google");
   Assert.ok(!matchedEngine);
   engine.hidden = false;
   await TestUtils.waitForCondition(() =>
-    PlacesSearchAutocompleteProvider.engineForAlias("@google")
+    UrlbarSearchUtils.engineForAlias("@google")
   );
-  engine = await PlacesSearchAutocompleteProvider.engineForAlias("@google");
+  engine = await UrlbarSearchUtils.engineForAlias("@google");
   Assert.ok(engine);
 });
 
 function promiseSearchTopic(expectedVerb) {
   return new Promise(resolve => {
     Services.obs.addObserver(function observe(subject, topic, verb) {
       info("browser-search-engine-modified: " + verb);
       if (verb == expectedVerb) {
--- a/browser/components/urlbar/tests/unit/xpcshell.ini
+++ b/browser/components/urlbar/tests/unit/xpcshell.ini
@@ -19,11 +19,12 @@ support-files =
 [test_search_suggestions_tail.js]
 [test_tokenizer.js]
 [test_UrlbarController_integration.js]
 [test_UrlbarController_telemetry.js]
 [test_UrlbarController_unit.js]
 [test_UrlbarPrefs.js]
 [test_UrlbarQueryContext.js]
 [test_UrlbarQueryContext_restrictSource.js]
+[test_UrlbarSearchUtils.jsm]
 [test_UrlbarUtils_addToUrlbarHistory.js]
 [test_UrlbarUtils_getShortcutOrURIAndPostData.js]
 [test_UrlbarUtils_getTokenMatches.js]
--- a/toolkit/components/places/UnifiedComplete.jsm
+++ b/toolkit/components/places/UnifiedComplete.jsm
@@ -339,26 +339,25 @@ XPCOMUtils.defineLazyGlobalGetters(this,
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AboutPagesUtils: "resource://gre/modules/AboutPagesUtils.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
   ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.jsm",
   ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
   PlacesRemoteTabsAutocompleteProvider:
     "resource://gre/modules/PlacesRemoteTabsAutocompleteProvider.jsm",
-  PlacesSearchAutocompleteProvider:
-    "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ProfileAge: "resource://gre/modules/ProfileAge.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   Sqlite: "resource://gre/modules/Sqlite.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
+  UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.jsm",
   UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "syncUsernamePref",
   "services.sync.username"
@@ -953,27 +952,16 @@ Search.prototype = {
     // Used by stop() to interrupt an eventual running statement.
     this.interrupt = () => {
       // Interrupt any ongoing statement to run the search sooner.
       if (!UrlbarProvidersManager.interruptLevel) {
         conn.interrupt();
       }
     };
 
-    if (UrlbarPrefs.get("restyleSearches")) {
-      // This explicit initialization is only necessary for
-      // _maybeRestyleSearchMatch, because it calls the synchronous
-      // parseSubmissionURL that can't wait for async initialization of
-      // PlacesSearchAutocompleteProvider.
-      await PlacesSearchAutocompleteProvider.ensureReady();
-      if (!this.pending) {
-        return;
-      }
-    }
-
     // For any given search, we run many queries/heuristics:
     // 1) by alias (as defined in SearchService)
     // 2) inline completion from search engine resultDomains
     // 3) inline completion for origins (this._originQuery) or urls (this._urlQuery)
     // 4) directly typed in url (ie, can be navigated to as-is)
     // 5) submission for the current search engine
     // 6) Places keywords
     // 7) adaptive learning (this._adaptiveQuery)
@@ -991,17 +979,17 @@ Search.prototype = {
     // default result and therefore be autofilled (this also happens if actions
     // are not enabled).
 
     // Check for Preloaded Sites Expiry before Autofill
     await this._checkPreloadedSitesExpiry();
 
     // If the query is simply "@" and we have tokenAliasEngines then return
     // early. UrlbarProviderTokenAliasEngines will add engine results.
-    let tokenAliasEngines = await PlacesSearchAutocompleteProvider.tokenAliasEngines();
+    let tokenAliasEngines = await UrlbarSearchUtils.tokenAliasEngines();
     if (this._trimmedOriginalSearchString == "@" && tokenAliasEngines.length) {
       this._autocompleteSearch.finishSearch(true);
       return;
     }
 
     // Add the first heuristic result, if any.  Set _addingHeuristicResult
     // to true so that when the result is added, "heuristic" can be included in
     // its style.
@@ -1229,17 +1217,17 @@ Search.prototype = {
   async _matchSearchEngineTokenAliasForAutofill() {
     // We need an "@engine" heuristic token.
     let token = this._heuristicToken;
     if (!token || token.length == 1 || !token.startsWith("@")) {
       return false;
     }
 
     // See if any engine has a token alias that starts with the heuristic token.
-    let engines = await PlacesSearchAutocompleteProvider.tokenAliasEngines();
+    let engines = await UrlbarSearchUtils.tokenAliasEngines();
     for (let { engine, tokenAliases } of engines) {
       for (let alias of tokenAliases) {
         if (alias.startsWith(token.toLocaleLowerCase())) {
           // We found one.  The match we add here is a little special compared
           // to others.  It needs to be an autofill match and its `value` must
           // be the string that will be autofilled so that the controller will
           // autofill it.  But it also must be a searchengine action so that the
           // front end will style it as a search engine result.  The front end
@@ -1494,35 +1482,33 @@ Search.prototype = {
   async _matchSearchEngineDomain() {
     if (!UrlbarPrefs.get("autoFill.searchEngines")) {
       return false;
     }
     if (!this._searchString) {
       return false;
     }
 
-    // PlacesSearchAutocompleteProvider only matches against engine domains.
+    // engineForDomainPrefix only matches against engine domains.
     // Remove an eventual trailing slash from the search string (without the
     // prefix) and check if the resulting string is worth matching.
     // Later, we'll verify that the found result matches the original
     // searchString and eventually discard it.
     let searchStr = this._searchString;
     if (searchStr.indexOf("/") == searchStr.length - 1) {
       searchStr = searchStr.slice(0, -1);
     }
     // If the search string looks more like a url than a domain, bail out.
     if (
       !UrlbarTokenizer.looksLikeOrigin(searchStr, { ignoreWhitelist: true })
     ) {
       return false;
     }
 
-    let engine = await PlacesSearchAutocompleteProvider.engineForDomainPrefix(
-      searchStr
-    );
+    let engine = await UrlbarSearchUtils.engineForDomainPrefix(searchStr);
     if (!engine) {
       return false;
     }
     let url = engine.searchForm;
     let domain = engine.getResultDomain();
     // Verify that the match we got is acceptable. Autofilling "example/" to
     // "example.com/" would not be good.
     if (
@@ -1554,17 +1540,17 @@ Search.prototype = {
       icon: engine.iconURI ? engine.iconURI.spec : null,
       style: "priority-search",
       frecency: Infinity,
     });
     return true;
   },
 
   async _matchSearchEngineAlias(alias) {
-    let engine = await PlacesSearchAutocompleteProvider.engineForAlias(alias);
+    let engine = await UrlbarSearchUtils.engineForAlias(alias);
     if (!engine) {
       return false;
     }
 
     this._searchEngineAliasMatch = {
       engine,
       alias,
       query: substringAfter(this._originalSearchString, alias).trim(),
@@ -1573,24 +1559,29 @@ Search.prototype = {
     this._addSearchEngineMatch(this._searchEngineAliasMatch);
     if (!this._keywordSubstitute) {
       this._keywordSubstitute = engine.getResultDomain();
     }
     return true;
   },
 
   async _matchCurrentSearchEngine() {
-    let engine = this._engineName
-      ? Services.search.getEngineByName(this._engineName)
-      : await PlacesSearchAutocompleteProvider.currentEngine(
-          this._inPrivateWindow
-        );
+    let engine;
+    if (this._engineName) {
+      engine = Services.search.getEngineByName(this._engineName);
+    } else if (this._inPrivateWindow) {
+      engine = Services.search.defaultPrivateEngine;
+    } else {
+      engine = Services.search.defaultEngine;
+    }
+
     if (!engine || !this.pending) {
       return false;
     }
+
     // Strip a leading search restriction char, because we prepend it to text
     // when the search shortcut is used and it's not user typed. Don't strip
     // other restriction chars, so that it's possible to search for things
     // including one of those (e.g. "c#").
     let query = this._trimmedOriginalSearchString;
     if (this._leadingRestrictionToken === UrlbarTokenizer.RESTRICT.SEARCH) {
       query = substringAfter(query, this._leadingRestrictionToken).trim();
     }
@@ -1836,19 +1827,17 @@ Search.prototype = {
       this._counts[UrlbarUtils.RESULT_GROUP.HEURISTIC];
     if (!this.pending || count >= this._maxResults) {
       cancel();
     }
   },
 
   _maybeRestyleSearchMatch(match) {
     // Return if the URL does not represent a search result.
-    let parseResult = PlacesSearchAutocompleteProvider.parseSubmissionURL(
-      match.value
-    );
+    let parseResult = Services.search.parseSubmissionURL(match.value);
     if (!parseResult) {
       return;
     }
 
     // Here we check that the user typed all or part of the search string in the
     // search history result.
     let terms = parseResult.terms.toLowerCase();
     if (
--- a/toolkit/components/places/moz.build
+++ b/toolkit/components/places/moz.build
@@ -63,17 +63,16 @@ if CONFIG['MOZ_PLACES']:
         'Bookmarks.jsm',
         'ExtensionSearchHandler.jsm',
         'History.jsm',
         'PlacesBackups.jsm',
         'PlacesCategoriesStarter.jsm',
         'PlacesDBUtils.jsm',
         'PlacesExpiration.jsm',
         'PlacesRemoteTabsAutocompleteProvider.jsm',
-        'PlacesSearchAutocompleteProvider.jsm',
         'PlacesSyncUtils.jsm',
         'PlacesTransactions.jsm',
         'PlacesUtils.jsm',
         'SyncedBookmarksMirror.jsm',
         'TaggingService.jsm',
         'UnifiedComplete.jsm',
     ]
 
--- a/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
+++ b/toolkit/components/places/tests/unifiedcomplete/xpcshell.ini
@@ -31,17 +31,16 @@ support-files =
 [test_encoded_urls.js]
 [test_escape_self.js]
 [test_history_autocomplete_tags.js]
 [test_ignore_protocol.js]
 [test_keyword_search.js]
 [test_keyword_search_actions.js]
 [test_keywords.js]
 [test_multi_word_search.js]
-[test_PlacesSearchAutocompleteProvider.js]
 [test_preloaded_sites.js]
 [test_query_url.js]
 [test_remote_tab_matches.js]
 skip-if = !sync
 [test_restrict_searchstring.js]
 [test_search_engine_alias.js]
 [test_search_engine_default.js]
 [test_search_engine_host.js]