Bug 1492475 - Part 1: Migrate most, if not all nsSearchService consumers to use async APIs. r=florian
authorMike de Boer <mdeboer@mozilla.com>
Fri, 05 Oct 2018 17:05:35 +0200
changeset 452863 891252fdd0b1a3e6b129025d94952ac30d922c7e
parent 452862 60aa2498320da118cb73c2375c201ecf4f47567b
child 452864 79b2eb2367aab104669bbc75c3b42290f7de1570
push id2
push usermdeboer@mozilla.com
push dateTue, 08 Jan 2019 17:01:21 +0000
reviewersflorian
bugs1492475
milestone66.0a1
Bug 1492475 - Part 1: Migrate most, if not all nsSearchService consumers to use async APIs. r=florian Differential Revision: https://phabricator.services.mozilla.com/D7894
browser/base/content/browser-pageActions.js
browser/base/content/browser.js
browser/components/enterprisepolicies/Policies.jsm
browser/components/extensions/parent/ext-chrome-settings-overrides.js
browser/components/extensions/parent/ext-search.js
browser/components/newtab/lib/ASRouterTargeting.jsm
browser/components/newtab/lib/SearchShortcuts.jsm
browser/components/newtab/lib/SnippetsFeed.jsm
browser/components/newtab/lib/TopSitesFeed.jsm
browser/components/preferences/in-content/search.js
browser/components/search/content/search-one-offs.js
browser/components/search/content/search.xml
browser/components/uitour/UITour.jsm
browser/modules/ContentSearch.jsm
docshell/base/nsDefaultURIFixup.cpp
docshell/base/nsDocShell.cpp
dom/ipc/ContentParent.cpp
netwerk/base/nsIBrowserSearchService.idl
toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
toolkit/components/processsingleton/MainProcessSingleton.js
toolkit/components/search/SearchSuggestionController.jsm
toolkit/components/search/nsSearchService.js
toolkit/components/telemetry/app/TelemetryEnvironment.jsm
toolkit/modules/Services.jsm
toolkit/modules/tests/xpcshell/test_Services.js
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1184,39 +1184,39 @@ BrowserPageActions.addSearchEngine = {
     // (Because this method isn't called when the panel button is clicked and it
     // shows a subview, and the many-engines case for the urlbar returned early
     // above.)
     let engine = this.engines[0];
     this._installEngine(engine.uri, engine.icon);
   },
 
   _installEngine(uri, image) {
-    Services.search.addEngine(uri, image, false, {
-      onSuccess: engine => {
+    Services.search.addEngine(uri, image, false).then(
+      engine => {
         showBrowserPageActionFeedback(this.action);
       },
-      onError(errorCode) {
+      errorCode => {
         if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) {
           // Download error is shown by the search service
           return;
         }
         const kSearchBundleURI = "chrome://global/locale/search/search.properties";
         let searchBundle = Services.strings.createBundle(kSearchBundleURI);
         let brandBundle = document.getElementById("bundle_brand");
         let brandName = brandBundle.getString("brandShortName");
         let title = searchBundle.GetStringFromName("error_invalid_engine_title");
         let text = searchBundle.formatStringFromName("error_duplicate_engine_msg",
                                                      [brandName, uri], 2);
         Services.prompt.QueryInterface(Ci.nsIPromptFactory);
         let prompt = Services.prompt.getPrompt(gBrowser.contentWindow, Ci.nsIPrompt);
         prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
         prompt.setPropertyAsBool("allowTabModal", true);
         prompt.alert(title, text);
-      },
-    });
+      }
+    );
   },
 };
 
 // share URL
 BrowserPageActions.shareURL = {
   onCommand(event, buttonNode) {
     let browser = gBrowser.selectedBrowser;
     let currentURI = gURLBar.makeURIReadable(browser.currentURI).displaySpec;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3749,23 +3749,21 @@ const BrowserSearch = {
 
   init() {
     Services.obs.addObserver(this, "browser-search-engine-modified");
   },
 
   delayedStartupInit() {
     // Asynchronously initialize the search service if necessary, to get the
     // current engine for working out the placeholder.
-    Services.search.init(rv => {
-      if (Components.isSuccessCode(rv)) {
-        // Delay the update for this until so that we don't change it while
-        // the user is looking at it / isn't expecting it.
-        this._updateURLBarPlaceholder(Services.search.defaultEngine, true);
-        this._searchInitComplete = true;
-      }
+    Services.search.getDefault().then(defaultEngine => {
+      // Delay the update for this until so that we don't change it while
+      // the user is looking at it / isn't expecting it.
+      this._updateURLBarPlaceholder(defaultEngine.name, true);
+      this._searchInitComplete = true;
     });
   },
 
   uninit() {
     Services.obs.removeObserver(this, "browser-search-engine-modified");
   },
 
   observe(engine, topic, data) {
@@ -3787,17 +3785,17 @@ const BrowserSearch = {
     case "engine-added":
       // An engine was added to the search service.  If a page is offering the
       // engine, then the engine needs to be removed from the corresponding
       // browser's offered engines.
       this._removeMaybeOfferedEngine(engineName);
       break;
     case "engine-current":
       if (this._searchInitComplete) {
-        this._updateURLBarPlaceholder(engine);
+        this._updateURLBarPlaceholder(engineName);
       }
       break;
     }
   },
 
   _addMaybeOfferedEngine(engineName) {
     let selectedBrowserOffersEngine = false;
     for (let browser of gBrowser.browsers) {
@@ -3863,31 +3861,33 @@ const BrowserSearch = {
   /**
    * Updates the URLBar placeholder for the specified engine, delaying the
    * update if required. This also saves the current engine name in preferences
    * for the next restart.
    *
    * Note: The engine name will only be displayed for built-in engines, as we
    * know they should have short names.
    *
-   * @param {nsISearchEngine} engine The search engine to use for the update.
+   * @param {String}  engineName     The search engine name to use for the update.
    * @param {Boolean} delayUpdate    Set to true, to delay update until the
    *                                 placeholder is not displayed.
    */
-  _updateURLBarPlaceholder(engine, delayUpdate = false) {
-    if (!engine) {
-      throw new Error("Expected an engine to be specified");
-    }
-
-    let engineName = "";
-    if (Services.search.getDefaultEngines().includes(engine)) {
-      engineName = engine.name;
+  async _updateURLBarPlaceholder(engineName, delayUpdate = false) {
+    if (!engineName) {
+      throw new Error("Expected an engineName to be specified");
+    }
+
+    let defaultEngines = await Services.search.getDefaultEngines();
+    if (defaultEngines.some(defaultEngine => defaultEngine.name == engineName)) {
       Services.prefs.setStringPref("browser.urlbar.placeholderName", engineName);
     } else {
       Services.prefs.clearUserPref("browser.urlbar.placeholderName");
+      // Set the engine name to an empty string for non-default engines, which'll
+      // make sure we display the default placeholder string.
+      engineName = "";
     }
 
     // Only delay if requested, and we're not displaying text in the URL bar
     // currently.
     if (delayUpdate && !gURLBar.value) {
       // Delays changing the URL Bar placeholder until the user is not going to be
       // seeing it, e.g. when there is a value entered in the bar, or if there is
       // a tab switch to a tab which has a url loaded.
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -795,76 +795,76 @@ var Policies = {
 
   "SearchEngines": {
     onBeforeUIStartup(manager, param) {
       if (param.PreventInstalls) {
         manager.disallowFeature("installSearchEngine", true);
       }
     },
     onAllWindowsRestored(manager, param) {
-      Services.search.init(() => {
+      Services.search.init(async () => {
         if (param.Remove) {
           // Only rerun if the list of engine names has changed.
-          runOncePerModification("removeSearchEngines",
-                                 JSON.stringify(param.Remove),
-                                 () => {
+          await runOncePerModification("removeSearchEngines",
+                                       JSON.stringify(param.Remove),
+                                       async function() {
             for (let engineName of param.Remove) {
               let engine = Services.search.getEngineByName(engineName);
               if (engine) {
                 try {
-                  Services.search.removeEngine(engine);
+                  await Services.search.removeEngine(engine);
                 } catch (ex) {
                   log.error("Unable to remove the search engine", ex);
                 }
               }
             }
           });
         }
         if (param.Add) {
           // Only rerun if the list of engine names has changed.
           let engineNameList = param.Add.map(engine => engine.Name);
-          runOncePerModification("addSearchEngines",
-                                 JSON.stringify(engineNameList),
-                                 () => {
+          await runOncePerModification("addSearchEngines",
+                                       JSON.stringify(engineNameList),
+                                       async function() {
             for (let newEngine of param.Add) {
               let newEngineParameters = {
                 template:    newEngine.URLTemplate,
                 iconURL:     newEngine.IconURL ? newEngine.IconURL.href : null,
                 alias:       newEngine.Alias,
                 description: newEngine.Description,
                 method:      newEngine.Method,
                 suggestURL:  newEngine.SuggestURLTemplate,
                 extensionID: "set-via-policy",
                 queryCharset: "UTF-8",
               };
               try {
-                Services.search.addEngineWithDetails(newEngine.Name,
-                                                     newEngineParameters);
+                await Services.search.addEngineWithDetails(newEngine.Name,
+                                                           newEngineParameters);
               } catch (ex) {
                 log.error("Unable to add search engine", ex);
               }
             }
           });
         }
         if (param.Default) {
-          runOncePerModification("setDefaultSearchEngine", param.Default, () => {
+          await runOncePerModification("setDefaultSearchEngine", param.Default, async () => {
             let defaultEngine;
             try {
               defaultEngine = Services.search.getEngineByName(param.Default);
               if (!defaultEngine) {
                 throw "No engine by that name could be found";
               }
             } catch (ex) {
               log.error(`Search engine lookup failed when attempting to set ` +
                         `the default engine. Requested engine was ` +
                         `"${param.Default}".`, ex);
             }
             if (defaultEngine) {
               try {
-                Services.search.defaultEngine = defaultEngine;
+                await Services.search.setDefault(defaultEngine);
               } catch (ex) {
                 log.error("Unable to set the default search engine", ex);
               }
             }
           });
         }
       });
     },
--- a/browser/components/extensions/parent/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/parent/ext-chrome-settings-overrides.js
@@ -112,17 +112,17 @@ this.chrome_settings_overrides = class e
     let item = await ExtensionSettingsStore.getSetting(
       DEFAULT_SEARCH_STORE_TYPE, ENGINE_ADDED_SETTING_NAME, id);
     if (item) {
       ExtensionSettingsStore.removeSetting(
         id, DEFAULT_SEARCH_STORE_TYPE, ENGINE_ADDED_SETTING_NAME);
       await searchInitialized;
       let engine = Services.search.getEngineByName(item.value);
       try {
-        Services.search.removeEngine(engine);
+        await Services.search.removeEngine(engine);
       } catch (e) {
         Cu.reportError(e);
       }
     }
   }
 
   static removeSearchSettings(id) {
     return Promise.all([
@@ -199,40 +199,42 @@ this.chrome_settings_overrides = class e
           }
         },
       });
 
       let searchProvider = manifest.chrome_settings_overrides.search_provider;
       let engineName = searchProvider.name.trim();
       if (searchProvider.is_default) {
         let engine = Services.search.getEngineByName(engineName);
-        if (engine && Services.search.getDefaultEngines().includes(engine)) {
+        let defaultEngines = await Services.search.getDefaultEngines();
+        if (engine && defaultEngines.some(defaultEngine => defaultEngine.name == engineName)) {
           // Needs to be called every time to handle reenabling, but
           // only sets default for install or enable.
           await this.setDefault(engineName);
           // For built in search engines, we don't do anything further
           return;
         }
       }
       await this.addSearchEngine();
       if (searchProvider.is_default) {
         if (extension.startupReason === "ADDON_INSTALL") {
           // Don't ask if it already the current engine
           let engine = Services.search.getEngineByName(engineName);
-          if (Services.search.defaultEngine != engine) {
+          let defaultEngine = await Services.search.getDefault();
+          if (defaultEngine.name != engine.name) {
             let allow = await new Promise(resolve => {
               let subject = {
                 wrappedJSObject: {
                   // This is a hack because we don't have the browser of
                   // the actual install. This means the popup might show
                   // in a different window. Will be addressed in a followup bug.
                   browser: windowTracker.topWindow.gBrowser.selectedBrowser,
                   name: this.extension.name,
                   icon: this.extension.iconURL,
-                  currentEngine: Services.search.defaultEngine.name,
+                  currentEngine: defaultEngine.name,
                   newEngine: engineName,
                   resolve,
                 },
               };
               Services.obs.notifyObservers(subject, "webextension-defaultsearch-prompt");
             });
             if (!allow) {
               return;
@@ -249,55 +251,58 @@ this.chrome_settings_overrides = class e
         chrome_settings_overrides.processDefaultSearchSetting("removeSetting", extension.id);
       }
     }
   }
 
   async setDefault(engineName) {
     let {extension} = this;
     if (extension.startupReason === "ADDON_INSTALL") {
+      let defaultEngine = await Services.search.getDefault();
       let item = await ExtensionSettingsStore.addSetting(
         extension.id, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => {
-          return Services.search.defaultEngine.name;
+          return defaultEngine.name;
         });
-      Services.search.defaultEngine = Services.search.getEngineByName(item.value);
+      await Services.search.setDefault(Services.search.getEngineByName(item.value));
     } else if (extension.startupReason === "ADDON_ENABLE") {
       chrome_settings_overrides.processDefaultSearchSetting("enable", extension.id);
     }
   }
 
   async addSearchEngine() {
     let {extension} = this;
     let isCurrent = false;
     let index = -1;
     if (extension.startupReason === "ADDON_UPGRADE") {
-      let engines = Services.search.getEnginesByExtensionID(extension.id);
+      let engines = await Services.search.getEnginesByExtensionID(extension.id);
       if (engines.length > 0) {
+        let firstEngine = engines[0];
+        let firstEngineName = firstEngine.name;
         // There can be only one engine right now
-        isCurrent = Services.search.defaultEngine == engines[0];
+        isCurrent = (await Services.search.getDefault()).name == firstEngineName;
         // Get position of engine and store it
-        index = Services.search.getEngines().indexOf(engines[0]);
-        Services.search.removeEngine(engines[0]);
+        index = (await Services.search.getEngines()).map(engine => engine.name).indexOf(firstEngineName);
+        await Services.search.removeEngine(firstEngine);
       }
     }
     try {
-      Services.search.addEnginesFromExtension(extension);
+      await Services.search.addEnginesFromExtension(extension);
       // Bug 1488516.  Preparing to support multiple engines per extension so
       // multiple locales can be loaded.
-      let engines = Services.search.getEnginesByExtensionID(extension.id);
+      let engines = await Services.search.getEnginesByExtensionID(extension.id);
       await ExtensionSettingsStore.addSetting(
         extension.id, DEFAULT_SEARCH_STORE_TYPE, ENGINE_ADDED_SETTING_NAME,
         engines[0].name);
       if (extension.startupReason === "ADDON_UPGRADE") {
         let engine = Services.search.getEngineByName(engines[0].name);
         if (isCurrent) {
-          Services.search.defaultEngine = engine;
+          await Services.search.setDefault(engine);
         }
         if (index != -1) {
-          Services.search.moveEngine(engine, index);
+          await Services.search.moveEngine(engine, index);
         }
       }
     } catch (e) {
       Cu.reportError(e);
       return false;
     }
     return true;
   }
--- a/browser/components/extensions/parent/ext-search.js
+++ b/browser/components/extensions/parent/ext-search.js
@@ -33,48 +33,49 @@ async function getDataURI(resourceURI) {
 }
 
 this.search = class extends ExtensionAPI {
   getAPI(context) {
     return {
       search: {
         async get() {
           await searchInitialized;
-          let visibleEngines = Services.search.getVisibleEngines();
+          let visibleEngines = await Services.search.getVisibleEngines();
+          let defaultEngine = await Services.search.getDefault();
           return Promise.all(visibleEngines.map(async engine => {
             let favIconUrl;
             if (engine.iconURI) {
               if (engine.iconURI.schemeIs("resource") ||
                   engine.iconURI.schemeIs("chrome")) {
                 // Convert internal URLs to data URLs
                 favIconUrl = await getDataURI(engine.iconURI.spec);
               } else {
                 favIconUrl = engine.iconURI.spec;
               }
             }
 
             return {
               name: engine.name,
-              isDefault: engine === Services.search.defaultEngine,
+              isDefault: engine.name === defaultEngine.name,
               alias: engine.alias || undefined,
               favIconUrl,
             };
           }));
         },
 
         async search(searchProperties) {
           await searchInitialized;
           let engine;
           if (searchProperties.engine) {
             engine = Services.search.getEngineByName(searchProperties.engine);
             if (!engine) {
               throw new ExtensionError(`${searchProperties.engine} was not found`);
             }
           } else {
-            engine = Services.search.defaultEngine;
+            engine = await Services.search.getDefault();
           }
           let submission = engine.getSubmission(searchProperties.query, null, "webextension");
           let options = {
             postData: submission.postData,
             triggeringPrincipal: context.principal,
           };
           let tabbrowser;
           if (searchProperties.tabId === null) {
--- a/browser/components/newtab/lib/ASRouterTargeting.jsm
+++ b/browser/components/newtab/lib/ASRouterTargeting.jsm
@@ -233,29 +233,24 @@ const TargetingGetters = {
           }
         }
         return {addons: info, isFullData: fullData};
       });
   },
   get searchEngines() {
     return new Promise(resolve => {
       // Note: calling init ensures this code is only executed after Search has been initialized
-      Services.search.init(rv => {
-        if (Components.isSuccessCode(rv)) {
-          let engines = Services.search.getVisibleEngines();
-          resolve({
-            current: Services.search.defaultEngine.identifier,
-            installed: engines
-              .map(engine => engine.identifier)
-              .filter(engine => engine),
-          });
-        } else {
-          resolve({installed: [], current: ""});
-        }
-      });
+      Services.search.getVisibleEngines().then(engines => {
+        resolve({
+          current: Services.search.defaultEngine.identifier,
+          installed: engines
+            .map(engine => engine.identifier)
+            .filter(engine => engine),
+        });
+      }).catch(() => resolve({installed: [], current: ""}));
     });
   },
   get isDefaultBrowser() {
     try {
       return ShellService.isDefaultBrowser();
     } catch (e) {}
     return null;
   },
--- a/browser/components/newtab/lib/SearchShortcuts.jsm
+++ b/browser/components/newtab/lib/SearchShortcuts.jsm
@@ -32,16 +32,16 @@ this.SEARCH_SHORTCUTS_HAVE_PINNED_PREF =
 
 function getSearchProvider(candidateShortURL) {
   return SEARCH_SHORTCUTS.filter(match => candidateShortURL === match.shortURL)[0] || null;
 }
 this.getSearchProvider = getSearchProvider;
 
 // Check topsite against predefined list of valid search engines
 // https://searchfox.org/mozilla-central/rev/ca869724246f4230b272ed1c8b9944596e80d920/toolkit/components/search/nsSearchService.js#939
-function checkHasSearchEngine(keyword) {
-  return Services.search.getDefaultEngines()
+async function checkHasSearchEngine(keyword) {
+  return (await Services.search.getDefaultEngines())
     .find(e => e.wrappedJSObject._internalAliases.includes(keyword));
 }
 this.checkHasSearchEngine = checkHasSearchEngine;
 
 const EXPORTED_SYMBOLS = ["checkHasSearchEngine", "getSearchProvider", "SEARCH_SHORTCUTS", "CUSTOM_SEARCH_SHORTCUTS", "SEARCH_SHORTCUTS_EXPERIMENT",
   "SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF", "SEARCH_SHORTCUTS_HAVE_PINNED_PREF"];
--- a/browser/components/newtab/lib/SnippetsFeed.jsm
+++ b/browser/components/newtab/lib/SnippetsFeed.jsm
@@ -66,30 +66,24 @@ this.SnippetsFeed = class SnippetsFeed {
       createdWeeksAgo:  Math.floor((Date.now() - createdDate) / ONE_WEEK),
       resetWeeksAgo: resetDate ? Math.floor((Date.now() - resetDate) / ONE_WEEK) : null,
     };
   }
 
   getSelectedSearchEngine() {
     return new Promise(resolve => {
       // Note: calling init ensures this code is only executed after Search has been initialized
-      Services.search.init(rv => {
-        // istanbul ignore else
-        if (Components.isSuccessCode(rv)) {
-          let engines = Services.search.getVisibleEngines();
-          resolve({
-            searchEngineIdentifier: Services.search.defaultEngine.identifier,
-            engines: engines
-              .filter(engine => engine.identifier)
-              .map(engine => `${TARGET_SEARCHENGINE_PREFIX}${engine.identifier}`),
-          });
-        } else {
-          resolve({engines: [], searchEngineIdentifier: ""});
-        }
-      });
+      Services.search.getVisibleEngines().then(engines => {
+        resolve({
+          searchEngineIdentifier: Services.search.defaultEngine.identifier,
+          engines: engines
+            .filter(engine => engine.identifier)
+            .map(engine => `${TARGET_SEARCHENGINE_PREFIX}${engine.identifier}`),
+        });
+      }).catch(() => resolve({engines: [], searchEngineIdentifier: ""}));
     });
   }
 
   async getAddonsInfo(target) {
     const {addons, fullData} = await AddonManager.getActiveAddons(["extension", "service"]);
     const info = {};
     for (const addon of addons) {
       info[addon.id] = {
--- a/browser/components/newtab/lib/TopSitesFeed.jsm
+++ b/browser/components/newtab/lib/TopSitesFeed.jsm
@@ -175,37 +175,37 @@ this.TopSitesFeed = class TopSitesFeed {
       // The plainPinnedSites array is populated with pinned sites at their
       // respective indices, and null everywhere else, but is not always the
       // right length
       const emptySlots = Math.max(numberOfSlots - plainPinnedSites.length, 0);
       const pinnedSites = [...plainPinnedSites].concat(
         Array(emptySlots).fill(null)
       );
 
-      await new Promise(resolve => Services.search.init(resolve));
-
-      const tryToInsertSearchShortcut = shortcut => {
+      const tryToInsertSearchShortcut = async shortcut => {
         const nextAvailable = pinnedSites.indexOf(null);
         // Only add a search shortcut if the site isn't already pinned, we
         // haven't previously inserted it, there's space to pin it, and the
         // search engine is available in Firefox
         if (
           !pinnedSites.find(s => s && s.hostname === shortcut.shortURL) &&
           !prevInsertedShortcuts.includes(shortcut.shortURL) &&
           nextAvailable > -1 &&
-          checkHasSearchEngine(shortcut.keyword)
+          await checkHasSearchEngine(shortcut.keyword)
         ) {
-          const site = this.topSiteToSearchTopSite({url: shortcut.url});
+          const site = await this.topSiteToSearchTopSite({url: shortcut.url});
           this._pinSiteAt(site, nextAvailable);
           pinnedSites[nextAvailable] = site;
           newInsertedShortcuts.push(shortcut.shortURL);
         }
       };
 
-      shouldPin.forEach(shortcut => tryToInsertSearchShortcut(shortcut));
+      for (let shortcut of shouldPin) {
+        await tryToInsertSearchShortcut(shortcut);
+      }
 
       if (newInsertedShortcuts.length) {
         this.store.dispatch(ac.SetPref(SEARCH_SHORTCUTS_HAVE_PINNED_PREF, prevInsertedShortcuts.concat(newInsertedShortcuts).join(",")));
         return true;
       }
     }
 
     return false;
@@ -213,51 +213,50 @@ this.TopSitesFeed = class TopSitesFeed {
 
   async getLinksWithDefaults() {
     const numItems = this.store.getState().Prefs.values[ROWS_PREF] * TOP_SITES_MAX_SITES_PER_ROW;
     const searchShortcutsExperiment = this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT];
     // We must wait for search services to initialize in order to access default
     // search engine properties without triggering a synchronous initialization
     await new Promise(resolve => Services.search.init(resolve));
 
-    // Get all frecent sites from history
-    const frecent = (await this.frecentCache.request({
+    // Get all frecent sites from history.
+    let frecent = [];
+    const cache = await this.frecentCache.request({
       // We need to overquery due to the top 5 alexa search + default search possibly being removed
       numItems: numItems + SEARCH_FILTERS.length + 1,
       topsiteFrecency: FRECENCY_THRESHOLD,
-    }))
-    .reduce((validLinks, link) => {
+    });
+    for (let link of cache) {
       const hostname = shortURL(link);
       if (!this.isExperimentOnAndLinkFilteredSearch(hostname)) {
-        validLinks.push({
-          ...(searchShortcutsExperiment ? this.topSiteToSearchTopSite(link) : link),
+        frecent.push({
+          ...(searchShortcutsExperiment ? await this.topSiteToSearchTopSite(link) : link),
           hostname,
         });
       }
-      return validLinks;
-    }, []);
+    }
 
-    // Remove any defaults that have been blocked
-    const notBlockedDefaultSites = DEFAULT_TOP_SITES
-      .reduce((topsites, link) => {
-        const searchProvider = getSearchProvider(shortURL(link));
-        if (NewTabUtils.blockedLinks.isBlocked({url: link.url})) {
-          return topsites;
-        } else if (this.isExperimentOnAndLinkFilteredSearch(link.hostname)) {
-          return topsites;
-          // If we've previously blocked a search shortcut, remove the default top site
-          // that matches the hostname
-        } else if (searchProvider && NewTabUtils.blockedLinks.isBlocked({url: searchProvider.url})) {
-          return topsites;
-        }
-        return [
-          ...topsites,
-          searchShortcutsExperiment ? this.topSiteToSearchTopSite(link) : link,
-        ];
-      }, []);
+    // Remove any defaults that have been blocked.
+    let notBlockedDefaultSites = [];
+    for (let link of DEFAULT_TOP_SITES) {
+      const searchProvider = getSearchProvider(shortURL(link));
+      if (NewTabUtils.blockedLinks.isBlocked({url: link.url})) {
+        continue;
+      } else if (this.isExperimentOnAndLinkFilteredSearch(link.hostname)) {
+        continue;
+        // If we've previously blocked a search shortcut, remove the default top site
+        // that matches the hostname
+      } else if (searchProvider && NewTabUtils.blockedLinks.isBlocked({url: searchProvider.url})) {
+        continue;
+      }
+      notBlockedDefaultSites.push(
+        searchShortcutsExperiment ? await this.topSiteToSearchTopSite(link) : link,
+      );
+    }
 
     // Get pinned links augmented with desired properties
     let plainPinned = await this.pinnedCache.request();
 
     // Insert search shortcuts if we need to.
     // _maybeInsertSearchShortcuts returns true if any search shortcuts are
     // inserted, meaning we need to expire and refresh the pinnedCache
     if (await this._maybeInsertSearchShortcuts(plainPinned)) {
@@ -374,33 +373,32 @@ this.TopSitesFeed = class TopSitesFeed {
       return;
     }
 
     if (!this._tippyTopProvider.initialized) {
       await this._tippyTopProvider.init();
     }
 
     // Populate the state with available search shortcuts
-    await new Promise(resolve => Services.search.init(resolve));
-    const searchShortcuts = Services.search.getDefaultEngines().reduce((result, engine) => {
+    const searchShortcuts = (await Services.search.getDefaultEngines()).reduce((result, engine) => {
       const shortcut = CUSTOM_SEARCH_SHORTCUTS.find(s => engine.wrappedJSObject._internalAliases.includes(s.keyword));
       if (shortcut) {
         result.push(this._tippyTopProvider.processSite({...shortcut}));
       }
       return result;
     }, []);
     this.store.dispatch(ac.BroadcastToContent({
       type: at.UPDATE_SEARCH_SHORTCUTS,
       data: {searchShortcuts},
     }));
   }
 
-  topSiteToSearchTopSite(site) {
+  async topSiteToSearchTopSite(site) {
     const searchProvider = getSearchProvider(shortURL(site));
-    if (!searchProvider || !checkHasSearchEngine(searchProvider.keyword)) {
+    if (!searchProvider || !await checkHasSearchEngine(searchProvider.keyword)) {
       return site;
     }
     return {
       ...site,
       searchTopSite: true,
       label: searchProvider.keyword,
     };
   }
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -140,24 +140,26 @@ var gSearchPane = {
       positionCheckbox.checked = false;
     }
 
     let permanentPBLabel =
       document.getElementById("urlBarSuggestionPermanentPBLabel");
     permanentPBLabel.hidden = urlbarSuggests.hidden || !permanentPB;
   },
 
-  buildDefaultEngineDropDown() {
+  async buildDefaultEngineDropDown() {
     // This is called each time something affects the list of engines.
     let list = document.getElementById("defaultEngine");
     // Set selection to the current default engine.
-    let currentEngine = Services.search.defaultEngine.name;
+    let currentEngine = (await Services.search.getDefault()).name;
 
     // If the current engine isn't in the list any more, select the first item.
     let engines = gEngineView._engineStore._engines;
+    if (!engines.length)
+      return;
     if (!engines.some(e => e.name == currentEngine))
       currentEngine = engines[0].name;
 
     // Now clean-up and rebuild the list.
     list.removeAllItems();
     gEngineView._engineStore._engines.forEach(e => {
       let item = list.appendItem(e.name);
       item.setAttribute("class", "menuitem-iconic searchengine-menuitem menuitem-with-favicon");
@@ -385,19 +387,18 @@ var gSearchPane = {
     for (let engine of gEngineView._engineStore.engines) {
       if (!engine.shown)
         hiddenList.push(engine.name);
     }
     Preferences.get("browser.search.hiddenOneOffs").value =
       hiddenList.join(",");
   },
 
-  setDefaultEngine() {
-    Services.search.defaultEngine =
-      document.getElementById("defaultEngine").selectedItem.engine;
+  async setDefaultEngine() {
+    await Services.search.setDefault(document.getElementById("defaultEngine").selectedItem.engine);
     ExtensionSettingsStore.setByUser(SEARCH_TYPE, SEARCH_KEY);
   },
 };
 
 function onDragEngineStart(event) {
   var selectedIndex = gEngineView.selectedIndex;
   var tree = document.getElementById("engineList");
   var row = { }, col = { }, child = { };
@@ -408,22 +409,31 @@ function onDragEngineStart(event) {
   }
 }
 
 
 function EngineStore() {
   let pref = Preferences.get("browser.search.hiddenOneOffs").value;
   this.hiddenList = pref ? pref.split(",") : [];
 
-  this._engines = Services.search.getVisibleEngines().map(this._cloneEngine, this);
-  this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine, this);
+  this._engines = [];
+  this._defaultEngines = [];
+  Promise.all([Services.search.getVisibleEngines(),
+    Services.search.getDefaultEngines()]).then(([visibleEngines, defaultEngines]) => {
+      for (let engine of visibleEngines) {
+        this.addEngine(engine);
+        gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
+      }
+      this._defaultEngines = defaultEngines.map(this._cloneEngine, this);
+      gSearchPane.buildDefaultEngineDropDown();
 
-  // check if we need to disable the restore defaults button
-  var someHidden = this._defaultEngines.some(e => e.hidden);
-  gSearchPane.showRestoreDefaults(someHidden);
+      // check if we need to disable the restore defaults button
+      var someHidden = this._defaultEngines.some(e => e.hidden);
+      gSearchPane.showRestoreDefaults(someHidden);
+    });
 }
 EngineStore.prototype = {
   _engines: null,
   _defaultEngines: null,
 
   get engines() {
     return this._engines;
   },
@@ -461,23 +471,23 @@ EngineStore.prototype = {
   moveEngine(aEngine, aNewIndex) {
     if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
       throw new Error("ES_moveEngine: invalid aNewIndex!");
     var index = this._getIndexForEngine(aEngine);
     if (index == -1)
       throw new Error("ES_moveEngine: invalid engine?");
 
     if (index == aNewIndex)
-      return; // nothing to do
+      return Promise.resolve(); // nothing to do
 
     // Move the engine in our internal store
     var removedEngine = this._engines.splice(index, 1)[0];
     this._engines.splice(aNewIndex, 0, removedEngine);
 
-    Services.search.moveEngine(aEngine.originalEngine, aNewIndex);
+    return Services.search.moveEngine(aEngine.originalEngine, aNewIndex);
   },
 
   removeEngine(aEngine) {
     if (this._engines.length == 1) {
       throw new Error("Cannot remove last engine!");
     }
 
     let engineName = aEngine.name;
@@ -489,36 +499,36 @@ EngineStore.prototype = {
     let removedEngine = this._engines.splice(index, 1)[0];
 
     if (this._defaultEngines.some(this._isSameEngine, removedEngine))
       gSearchPane.showRestoreDefaults(true);
     gSearchPane.buildDefaultEngineDropDown();
     return index;
   },
 
-  restoreDefaultEngines() {
+  async restoreDefaultEngines() {
     var added = 0;
 
     for (var i = 0; i < this._defaultEngines.length; ++i) {
       var e = this._defaultEngines[i];
 
       // If the engine is already in the list, just move it.
       if (this._engines.some(this._isSameEngine, e)) {
-        this.moveEngine(this._getEngineByName(e.name), i);
+        await this.moveEngine(this._getEngineByName(e.name), i);
       } else {
         // Otherwise, add it back to our internal store
 
         // The search service removes the alias when an engine is hidden,
         // so clear any alias we may have cached before unhiding the engine.
         e.alias = "";
 
         this._engines.splice(i, 0, e);
         let engine = e.originalEngine;
         engine.hidden = false;
-        Services.search.moveEngine(engine, i);
+        await Services.search.moveEngine(engine, i);
         added++;
       }
     }
     Services.search.resetToOriginalDefaultEngine();
     gSearchPane.showRestoreDefaults(false);
     gSearchPane.buildDefaultEngineDropDown();
     return added;
   },
@@ -559,17 +569,18 @@ EngineView.prototype = {
     return -1;
   },
   get selectedEngine() {
     return this._engineStore.engines[this.selectedIndex];
   },
 
   // Helpers
   rowCountChanged(index, count) {
-    this.tree.rowCountChanged(index, count);
+    if (this.tree)
+      this.tree.rowCountChanged(index, count);
   },
 
   invalidate() {
     this.tree.invalidate();
   },
 
   ensureRowIsVisible(index) {
     this.tree.ensureRowIsVisible(index);
--- a/browser/components/search/content/search-one-offs.js
+++ b/browser/components/search/content/search-one-offs.js
@@ -325,28 +325,28 @@ class SearchOneOffs {
   get bundle() {
     if (!this._bundle) {
       const kBundleURI = "chrome://browser/locale/search.properties";
       this._bundle = Services.strings.createBundle(kBundleURI);
     }
     return this._bundle;
   }
 
-  get engines() {
+  async getEngines() {
     if (this._engines) {
       return this._engines;
     }
     let currentEngineNameToIgnore;
     if (!this.getAttribute("includecurrentengine"))
-      currentEngineNameToIgnore = Services.search.defaultEngine.name;
+      currentEngineNameToIgnore = (await Services.search.getDefault()).name;
 
     let pref = Services.prefs.getStringPref("browser.search.hiddenOneOffs");
     let hiddenList = pref ? pref.split(",") : [];
 
-    this._engines = Services.search.getVisibleEngines().filter(e => {
+    this._engines = (await Services.search.getVisibleEngines()).filter(e => {
       let name = e.name;
       return (!currentEngineNameToIgnore ||
               name != currentEngineNameToIgnore) &&
              !hiddenList.includes(name);
     });
 
     return this._engines;
   }
@@ -386,17 +386,17 @@ class SearchOneOffs {
       }
     }
     this.buttons.setAttribute("aria-label", groupText);
   }
 
   /**
    * Builds all the UI.
    */
-  _rebuild() {
+  async _rebuild() {
     // Update the 'Search for <keywords> with:" header.
     this._updateAfterQueryChanged();
 
     // Handle opensearch items. This needs to be done before building the
     // list of one off providers, as that code will return early if all the
     // alternative engines are hidden.
     // Skip this in compact mode, ie. for the urlbar.
     if (!this.compact) {
@@ -423,20 +423,21 @@ class SearchOneOffs {
     }
 
     // Remove the trailing empty text node introduced by the binding's
     // content markup above.
     if (this.settingsButtonCompact.nextElementSibling) {
       this.settingsButtonCompact.nextElementSibling.remove();
     }
 
-    let engines = this.engines;
+    let engines = await this.getEngines();
+    let defaultEngine = await Services.search.getDefault();
     let oneOffCount = engines.length;
     let collapsed = !oneOffCount ||
-                    (oneOffCount == 1 && engines[0].name == Services.search.defaultEngine.name);
+                    (oneOffCount == 1 && engines[0].name == defaultEngine.name);
 
     // header is a xul:deck so collapsed doesn't work on it, see bug 589569.
     this.header.hidden = this.buttons.collapsed = collapsed;
 
     if (collapsed) {
       return;
     }
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -473,32 +473,33 @@
       <field name="oneOffButtons" readonly="true">
         new SearchOneOffs(
           document.getAnonymousElementByAttribute(this, "anonid",
                                                   "search-one-off-buttons"));
       </field>
 
       <method name="updateHeader">
         <body><![CDATA[
-          let currentEngine = Services.search.defaultEngine;
-          let uri = currentEngine.iconURI;
-          if (uri) {
-            this.setAttribute("src", uri.spec);
-          } else {
-            // If the default has just been changed to a provider without icon,
-            // avoid showing the icon of the previous default provider.
-            this.removeAttribute("src");
-          }
+          Services.search.getDefault(currentEngine => {
+            let uri = currentEngine.iconURI;
+            if (uri) {
+              this.setAttribute("src", uri.spec);
+            } else {
+              // If the default has just been changed to a provider without icon,
+              // avoid showing the icon of the previous default provider.
+              this.removeAttribute("src");
+            }
 
-          let headerText = this.bundle.formatStringFromName("searchHeader",
-                                                            [currentEngine.name], 1);
-          document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine-name")
-                  .setAttribute("value", headerText);
-          document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine")
-                  .engine = currentEngine;
+            let headerText = this.bundle.formatStringFromName("searchHeader",
+                                                              [currentEngine.name], 1);
+            document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine-name")
+                    .setAttribute("value", headerText);
+            document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine")
+                    .engine = currentEngine;
+          });
         ]]></body>
       </method>
 
       <!-- This is called when a one-off is clicked and when "search in new tab"
            is selected from a one-off context menu. -->
       <method name="handleOneOffSearch">
         <parameter name="event"/>
         <parameter name="engine"/>
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -1454,29 +1454,24 @@ var UITour = {
       case "appinfo":
         this.getAppInfo(aMessageManager, aWindow, aCallbackID);
         break;
       case "availableTargets":
         this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
         break;
       case "search":
       case "selectedSearchEngine":
-        Services.search.init(rv => {
-          let data;
-          if (Components.isSuccessCode(rv)) {
-            let engines = Services.search.getVisibleEngines();
-            data = {
-              searchEngineIdentifier: Services.search.defaultEngine.identifier,
-              engines: engines.filter((engine) => engine.identifier)
-                              .map((engine) => TARGET_SEARCHENGINE_PREFIX + engine.identifier),
-            };
-          } else {
-            data = {engines: [], searchEngineIdentifier: ""};
-          }
-          this.sendPageCallback(aMessageManager, aCallbackID, data);
+        Services.search.getVisibleEngines().then(engines => {
+          this.sendPageCallback(aMessageManager, aCallbackID, {
+            searchEngineIdentifier: Services.search.defaultEngine.identifier,
+            engines: engines.filter(engine => engine.identifier)
+                            .map(engine => TARGET_SEARCHENGINE_PREFIX + engine.identifier),
+          });
+        }).catch(() => {
+          this.sendPageCallback(aMessageManager, aCallbackID, {engines: [], searchEngineIdentifier: ""});
         });
         break;
       case "sync":
         this.sendPageCallback(aMessageManager, aCallbackID, {
           setup: Services.prefs.prefHasUserValue("services.sync.username"),
           desktopDevices: Services.prefs.getIntPref("services.sync.clients.devices.desktop", 0),
           mobileDevices: Services.prefs.getIntPref("services.sync.clients.devices.mobile", 0),
           totalDevices: Services.prefs.getIntPref("services.sync.numClients", 0),
@@ -1659,27 +1654,20 @@ var UITour = {
       mutation.target.removeAttribute("width");
       mutation.target.removeAttribute("height");
       return;
     }
   },
 
   selectSearchEngine(aID) {
     return new Promise((resolve, reject) => {
-      Services.search.init((rv) => {
-        if (!Components.isSuccessCode(rv)) {
-          reject("selectSearchEngine: search service init failed: " + rv);
-          return;
-        }
-
-        let engines = Services.search.getVisibleEngines();
+      Services.search.getVisibleEngines().then(engines => {
         for (let engine of engines) {
           if (engine.identifier == aID) {
-            Services.search.defaultEngine = engine;
-            resolve();
+            Services.search.setDefault(engine).finally(resolve);
             return;
           }
         }
         reject("selectSearchEngine could not find engine with given ID");
       });
     });
   },
 
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -333,17 +333,17 @@ var ContentSearch = {
       engines: [],
       currentEngine: await this._currentEngineObj(),
     };
     if (uriFlag) {
       state.currentEngine.iconBuffer = Services.search.defaultEngine.getIconURLBySize(16, 16);
     }
     let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
     let hiddenList = pref ? pref.split(",") : [];
-    for (let engine of Services.search.getVisibleEngines()) {
+    for (let engine of await Services.search.getVisibleEngines()) {
       let uri = engine.getIconURLBySize(16, 16);
       let iconBuffer = uri;
       if (!uriFlag) {
         iconBuffer = await this._arrayBufferFromDataURI(uri);
       }
       state.engines.push({
         name: engine.name,
         iconBuffer,
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -437,18 +437,18 @@ nsDefaultURIFixup::KeywordToURI(const ns
 
     nsCOMPtr<nsIURI> temp = DeserializeURI(uri);
     info->mPreferredURI = temp.forget();
     return NS_OK;
   }
 
 #ifdef MOZ_TOOLKIT_SEARCH
   // Try falling back to the search service's default search engine
-  nsCOMPtr<nsIBrowserSearchService> searchSvc =
-      do_GetService("@mozilla.org/browser/search-service;1");
+  nsCOMPtr<nsISearchService> searchSvc =
+    do_GetService("@mozilla.org/browser/search-service;1");
   if (searchSvc) {
     nsCOMPtr<nsISearchEngine> defaultEngine;
     searchSvc->GetDefaultEngine(getter_AddRefs(defaultEngine));
     if (defaultEngine) {
       nsCOMPtr<nsISearchSubmission> submission;
       nsAutoString responseType;
       // We allow default search plugins to specify alternate
       // parameters that are specific to keyword searches.
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13167,18 +13167,18 @@ void nsDocShell::MaybeNotifyKeywordSearc
     dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
     if (contentChild) {
       contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword);
     }
     return;
   }
 
 #ifdef MOZ_TOOLKIT_SEARCH
-  nsCOMPtr<nsIBrowserSearchService> searchSvc =
-      do_GetService("@mozilla.org/browser/search-service;1");
+  nsCOMPtr<nsISearchService> searchSvc =
+    do_GetService("@mozilla.org/browser/search-service;1");
   if (searchSvc) {
     nsCOMPtr<nsISearchEngine> searchEngine;
     searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
     if (searchEngine) {
       nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
       if (obsSvc) {
         // Note that "keyword-search" refers to a search via the url
         // bar, not a bookmarks keyword search.
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4143,18 +4143,18 @@ mozilla::ipc::IPCResult ContentParent::R
   info->GetPreferredURI(getter_AddRefs(uri));
   SerializeURI(uri, *aURI);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvNotifyKeywordSearchLoading(
     const nsString& aProvider, const nsString& aKeyword) {
 #ifdef MOZ_TOOLKIT_SEARCH
-  nsCOMPtr<nsIBrowserSearchService> searchSvc =
-      do_GetService("@mozilla.org/browser/search-service;1");
+  nsCOMPtr<nsISearchService> searchSvc =
+    do_GetService("@mozilla.org/browser/search-service;1");
   if (searchSvc) {
     nsCOMPtr<nsISearchEngine> searchEngine;
     searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine));
     if (searchEngine) {
       nsCOMPtr<nsIObserverService> obsSvc =
           mozilla::services::GetObserverService();
       if (obsSvc) {
         // Note that "keyword-search" refers to a search via the url
--- a/netwerk/base/nsIBrowserSearchService.idl
+++ b/netwerk/base/nsIBrowserSearchService.idl
@@ -231,16 +231,293 @@ interface nsIBrowserSearchInitObserver :
   /**
    * Called once initialization of the browser search service is complete.
    *
    * @param aStatus The status of that service.
    */
   void onInitComplete(in nsresult aStatus);
 };
 
+[scriptable, uuid(0301834b-2630-440e-8b98-db8dc55f34b9)]
+interface nsISearchService : nsISupports
+{
+  /**
+   * Start asynchronous initialization.
+   *
+   * The callback is triggered once initialization is complete, which may be
+   * immediately, if initialization has already been completed by some previous
+   * call to this method. The callback is always invoked asynchronously.
+   *
+   * @param aObserver An optional object observing the end of initialization.
+   */
+  void init([optional] in nsIBrowserSearchInitObserver aObserver);
+
+  /**
+   * Determine whether initialization has been completed.
+   *
+   * Clients of the service can use this attribute to quickly determine whether
+   * initialization is complete, and decide to trigger some immediate treatment,
+   * to launch asynchronous initialization or to bailout.
+   *
+   * Note that this attribute does not indicate that initialization has succeeded.
+   *
+   * @return |true| if the search service is now initialized, |false| if
+   * initialization has not been triggered yet.
+   */
+  readonly attribute bool isInitialized;
+
+  /**
+   * Resets the default engine to its original value.
+   */
+  Promise resetToOriginalDefaultEngine();
+
+  /**
+   * Checks if an EngineURL of type URLTYPE_SEARCH_HTML exists for
+   * any engine, with a matching method, template URL, and query params.
+   *
+   * @param method
+   *        The HTTP request method used when submitting a search query.
+   *        Must be a case insensitive value of either "get" or "post".
+   *
+   * @param url
+   *        The URL to which search queries should be sent.
+   *        Must not be null.
+   *
+   * @param formData
+   *        The un-sorted form data used as query params.
+   */
+  boolean hasEngineWithURL(in AString method, in AString url, in jsval formData);
+
+  /**
+   * Adds a new search engine from the file at the supplied URI, optionally
+   * asking the user for confirmation first.  If a confirmation dialog is
+   * shown, it will offer the option to begin using the newly added engine
+   * right away.
+   *
+   * @param engineURL
+   *        The URL to the search engine's description file.
+   *
+   * @param iconURL
+   *        A URL string to an icon file to be used as the search engine's
+   *        icon. This value may be overridden by an icon specified in the
+   *        engine description file.
+   *
+   * @param confirm
+   *        A boolean value indicating whether the user should be asked for
+   *        confirmation before this engine is added to the list.  If this
+   *        value is false, the engine will be added to the list upon successful
+   *        load, but it will not be selected as the current engine.
+   *
+   * @param extensionID [optional]
+   *        Optional: The correct extensionID if called by an add-on.
+   *
+   * @throws NS_ERROR_FAILURE if the description file cannot be successfully
+   *         loaded.
+   */
+  Promise addEngine(in AString engineURL, in AString iconURL, in boolean confirm,
+                    [optional] in AString extensionID);
+
+  /**
+   * Adds a new search engine, without asking the user for confirmation and
+   * without starting to use it right away.
+   *
+   * @param name
+   *        The search engine's name. Must be unique. Must not be null.
+   *
+   * @param iconURL
+   *        Optional: A URL string pointing to the icon to be used to represent
+   *        the engine.
+   *        This is a jsval so that an object can be passed to replace the
+   *        parameters below.
+   *
+   * @param alias
+   *        Optional: A unique shortcut that can be used to retrieve the
+   *        search engine.
+   *
+   * @param description
+   *        Optional: a description of the search engine.
+   *
+   * @param method
+   *        Optional: The HTTP request method used when submitting a search query.
+   *        Case insensitive value of either "get" or "post".
+   *        Defaults to "get".
+   *
+   * @param template
+   *        The template for the URL to which search queries should be sent. The
+   *        template will be subjected to OpenSearch parameter substitution.
+   *        See http://www.opensearch.org/Specifications/OpenSearch
+   *        Must not be null.
+   *
+   * @param extensionID [optional]
+   *        Optional: The correct extensionID if called by an add-on.
+   *
+   * Alternatively, all of these parameters except for name can be
+   * passed as an object in place of parameter two.
+   *
+   *   Services.search.addEngineWithDetails("Example engine", {
+   *     template: "http://example.com/?search={searchTerms}",
+   *     description: "Example search engine description",
+   *     suggestURL: http://example.com/?suggest={searchTerms},
+   * });
+   *
+   * Using this method, you can use new parameters:
+   *
+   * @param suggestURL [optional]
+   *        Optional: The URL to which search suggestion requests
+   *        should be sent.
+   *
+   * @param postData [optional]
+   *        Optional: For POST requests, a string of URL parameters
+   *        to send, separated by '&'.  The string will be subjected
+   *        to OpenSearch parameter substitution.
+   *
+   */
+  Promise addEngineWithDetails(in AString name,
+                               in jsval iconURL,
+                               [optional] in AString alias,
+                               [optional] in AString description,
+                               [optional] in AString method,
+                               [optional] in AString url,
+                               [optional] in AString extensionID);
+
+  Promise addEnginesFromExtension(in jsval extension);
+
+  /**
+   * Un-hides all engines in the set of engines returned by getDefaultEngines.
+   */
+  void restoreDefaultEngines();
+
+  /**
+   * Returns an engine with the specified alias.
+   *
+   * @param   alias
+   *          The search engine's alias.
+   * @returns The corresponding nsISearchEngine object, or null if it doesn't
+   *          exist.
+   */
+  nsISearchEngine getEngineByAlias(in AString alias);
+
+  /**
+   * Returns an engine with the specified name.
+   *
+   * @param   aEngineName
+   *          The name of the engine.
+   * @returns The corresponding nsISearchEngine object, or null if it doesn't
+   *          exist.
+   */
+  nsISearchEngine getEngineByName(in AString aEngineName);
+
+  /**
+   * Returns an array of all installed search engines.
+   *
+   * @returns an array of nsISearchEngine objects.
+   */
+  Promise getEngines();
+
+  /**
+   * Returns an array of all installed search engines whose hidden attribute is
+   * false.
+   *
+   * @returns an array of nsISearchEngine objects.
+   */
+  Promise getVisibleEngines();
+
+  /**
+   * Returns an array of all default search engines. This includes all loaded
+   * engines that aren't in the user's profile directory.
+   *
+   * @returns an array of nsISearchEngine objects.
+   */
+  Promise getDefaultEngines();
+
+  /**
+   * Returns an array of search engines installed by a given extension.
+   *
+   * @returns an array of nsISearchEngine objects.
+   */
+  Promise getEnginesByExtensionID(in AString extensionID);
+
+  /**
+   * Moves a visible search engine.
+   *
+   * @param  engine
+   *         The engine to move.
+   * @param  newIndex
+   *         The engine's new index in the set of visible engines.
+   *
+   * @throws NS_ERROR_FAILURE if newIndex is out of bounds, or if engine is
+   *         hidden.
+   */
+  Promise moveEngine(in nsISearchEngine engine, in long newIndex);
+
+  /**
+   * Removes the search engine. If the search engine is installed in a global
+   * location, this will just hide the engine. If the engine is in the user's
+   * profile directory, it will be removed from disk.
+   *
+   * @param  engine
+   *         The engine to remove.
+   */
+  Promise removeEngine(in nsISearchEngine engine);
+
+  /**
+   * The original Engine object that is the default for this region,
+   * ignoring changes the user may have subsequently made.
+   */
+  readonly attribute nsISearchEngine originalDefaultEngine;
+
+  /**
+   * The currently active search engine.
+   * Unless the application doesn't ship any search plugin, this should never
+   * be null. If the currently active engine is removed, this attribute will
+   * fallback first to the original default engine if it's not hidden, then to
+   * the first visible engine, and as a last resort it will unhide the original
+   * default engine.
+   */
+  attribute nsISearchEngine defaultEngine;
+
+  Promise getDefault();
+  Promise setDefault(in nsISearchEngine engine);
+
+  /**
+   * Gets a representation of the default engine in an anonymized JSON
+   * string suitable for recording in the Telemetry environment.
+   *
+   * @return an object containing anonymized info about the default engine:
+   *         name, loadPath, submissionURL (for default engines).
+   */
+  Promise getDefaultEngineInfo();
+
+  /**
+   * Determines if the provided URL represents results from a search engine, and
+   * provides details about the match.
+   *
+   * The lookup mechanism checks whether the domain name and path of the
+   * provided HTTP or HTTPS URL matches one of the known values for the visible
+   * search engines.  The match does not depend on which of the schemes is used.
+   * The expected URI parameter for the search terms must exist in the query
+   * string, but other parameters are ignored.
+   *
+   * @param url
+   *        String containing the URL to parse, for example
+   *        "https://www.google.com/search?q=terms".
+   */
+  nsISearchParseSubmissionResult parseSubmissionURL(in AString url);
+
+  /**
+   * Determines if a URL is related to search and if so, records the
+   * appropriate telemetry.
+   *
+   * @param url
+   *        String containing the URL to parse, for example
+   *        "https://www.google.com/search?q=terms".
+   */
+  boolean recordSearchURLTelemetry(in AString url);
+};
+
 [scriptable, uuid(150ef720-bbe2-4169-b9f3-ef7ec0654ced)]
 interface nsIBrowserSearchService : nsISupports
 {
   /**
    * Start asynchronous initialization.
    *
    * The callback is triggered once initialization is complete, which may be
    * immediately, if initialization has already been completed by some previous
@@ -563,11 +840,13 @@ interface nsIBrowserSearchService : nsIS
  */
 #define SEARCH_ENGINE_CURRENT      "engine-current";
 
 /**
  * Sent when the "default" engine is changed.
  */
 #define SEARCH_ENGINE_DEFAULT      "engine-default";
 
-
+#ifdef ANDROID
+#define nsISearchService nsIBrowserSearchService
+#endif
 
 %}
--- a/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
+++ b/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
@@ -35,24 +35,24 @@ const SearchAutocompleteProviderInternal
   /**
    * {array<{ {nsISearchEngine} engine, {array<string>} tokenAliases }>} Array
    * of engines that have "@" aliases.
    */
   tokenAliasEngines: [],
 
   initialize() {
     return new Promise((resolve, reject) => {
-      Services.search.init(status => {
+      Services.search.init(async status => {
         if (!Components.isSuccessCode(status)) {
           reject(new Error("Unable to initialize search service."));
         }
 
         try {
           // The initial loading of the search engines must succeed.
-          this._refresh();
+          await this._refresh();
 
           Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
 
           this.initialized = true;
           resolve();
         } catch (ex) {
           reject(ex);
         }
@@ -67,24 +67,24 @@ const SearchAutocompleteProviderInternal
       case "engine-added":
       case "engine-changed":
       case "engine-removed":
       case "engine-current":
         this._refresh();
     }
   },
 
-  _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.
-    Services.search.getEngines().forEach(e => this._addEngine(e));
+    (await Services.search.getEngines()).forEach(e => this._addEngine(e));
   },
 
   _addEngine(engine) {
     let domain = engine.getResultDomain();
     if (domain && !engine.hidden) {
       this.enginesByDomain.set(domain, engine);
     }
 
@@ -255,17 +255,17 @@ var PlacesSearchAutocompleteProvider = O
    */
   async tokenAliasEngines() {
     await this.ensureInitialized();
 
     return SearchAutocompleteProviderInternal.tokenAliasEngines.slice();
   },
 
   /**
-   * Use this to get the current engine rather than Services.search.currentEngine
+   * Use this to get the current engine rather than Services.search.defaultEngine
    * directly.  This method makes sure that the service is first initialized.
    *
    * @returns {nsISearchEngine} The current search engine.
    */
   async currentEngine() {
     await this.ensureInitialized();
 
     return Services.search.defaultEngine;
--- a/toolkit/components/processsingleton/MainProcessSingleton.js
+++ b/toolkit/components/processsingleton/MainProcessSingleton.js
@@ -44,22 +44,18 @@ MainProcessSingleton.prototype = {
       var brandName = brandBundle.GetStringFromName("brandShortName");
       var title = searchBundle.GetStringFromName("error_invalid_format_title");
       var msg = searchBundle.formatStringFromName("error_invalid_engine_msg2",
                                                   [brandName, engineURL.spec], 2);
       Services.ww.getNewPrompter(browser.ownerGlobal).alert(title, msg);
       return;
     }
 
-    Services.search.init(function(status) {
-      if (status != Cr.NS_OK)
-        return;
-
-      Services.search.addEngine(engineURL.spec, iconURL ? iconURL.spec : null, true);
-    });
+    Services.search.addEngine(engineURL.spec, iconURL ? iconURL.spec : null, true)
+      .catch(ex => Cu.reportError("Unable to add search engine to the search service: " + ex));
   },
 
   observe(subject, topic, data) {
     switch (topic) {
     case "app-startup": {
       ChromeUtils.import("resource://gre/modules/CustomElementsListener.jsm", null);
       Services.obs.addObserver(this, "xpcom-shutdown");
 
--- a/toolkit/components/search/SearchSuggestionController.jsm
+++ b/toolkit/components/search/SearchSuggestionController.jsm
@@ -139,17 +139,17 @@ this.SearchSuggestionController.prototyp
     this.stop();
 
     if (!Services.search.isInitialized) {
       throw new Error("Search not initialized yet (how did you get here?)");
     }
     if (typeof privateMode === "undefined") {
       throw new Error("The privateMode argument is required to avoid unintentional privacy leaks");
     }
-    if (!(engine instanceof Ci.nsISearchEngine)) {
+    if (!engine.getSubmission) {
       throw new Error("Invalid search engine");
     }
     if (!this.maxLocalResults && !this.maxRemoteResults) {
       throw new Error("Zero results expected, what are you trying to do?");
     }
     if (this.maxLocalResults < 0 || this.maxRemoteResults < 0) {
       throw new Error("Number of requested results must be positive");
     }
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -459,17 +459,18 @@ function storeRegion(region) {
         probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX";
         probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX";
         break;
       case "WINNT":
         probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_WIN";
         probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_WIN";
         break;
       default:
-        Cu.reportError("Platform " + Services.appinfo.OS + " has system country code but no search service telemetry probes");
+        Cu.reportError("Platform " + Services.appinfo.OS +
+          " has system country code but no search service telemetry probes");
         break;
     }
     if (probeUSMismatched && probeNonUSMismatched) {
       if (region == "US" || platformCC == "US") {
         // one of the 2 said US, so record if they are the same.
         Services.telemetry.getHistogramById(probeUSMismatched).add(region != platformCC);
       } else {
         // non-US - record if they are the same
@@ -1561,17 +1562,17 @@ Engine.prototype = {
           onError(Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
         }
         LOG("_onLoad: duplicate engine found, bailing");
         return;
       }
 
       // If requested, confirm the addition now that we have the title.
       // This property is only ever true for engines added via
-      // nsIBrowserSearchService::addEngine.
+      // nsISearchService::addEngine.
       if (aEngine._confirm) {
         var confirmation = aEngine._confirmAddEngine();
         LOG("_onLoad: confirm is " + confirmation.confirmed +
             "; useNow is " + confirmation.useNow);
         if (!confirmation.confirmed) {
           onError();
           return;
         }
@@ -1902,17 +1903,18 @@ Engine.prototype = {
                  this._isDefault) {
         var value;
         let condition = param.getAttribute("condition");
 
         // MozParams must have a condition to be valid
         if (!condition) {
           let engineLoc = this._location;
           let paramName = param.getAttribute("name");
-          LOG("_parseURL: MozParam (" + paramName + ") without a condition attribute found parsing engine: " + engineLoc);
+          LOG("_parseURL: MozParam (" + paramName +
+            ") without a condition attribute found parsing engine: " + engineLoc);
           continue;
         }
 
         switch (condition) {
           case "purpose":
             url.addParam(param.getAttribute("name"),
                          param.getAttribute("value"),
                          param.getAttribute("purpose"));
@@ -1926,17 +1928,18 @@ Engine.prototype = {
               url._addMozParam({"pref": param.getAttribute("pref"),
                                 "name": param.getAttribute("name"),
                                 "condition": "pref"});
             } catch (e) { }
             break;
           default:
             let engineLoc = this._location;
             let paramName = param.getAttribute("name");
-            LOG("_parseURL: MozParam (" + paramName + ") has an unknown condition: " + condition + ". Found parsing engine: " + engineLoc);
+            LOG("_parseURL: MozParam (" + paramName + ") has an unknown condition: " +
+              condition + ". Found parsing engine: " + engineLoc);
           break;
         }
       }
     }
 
     this._urls.push(url);
   },
 
@@ -2650,17 +2653,17 @@ function checkForSyncCompletion(aPromise
                                  "finished so no need to pursue asynchronous " +
                                  "initialization",
                                  Cr.NS_ERROR_ALREADY_INITIALIZED);
     }
     return aValue;
   });
 }
 
-// nsIBrowserSearchService
+// nsISearchService
 function SearchService() {
   this._initObservers = PromiseUtils.defer();
 }
 
 SearchService.prototype = {
   classID: Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}"),
 
   // The current status of initialization. Note that it does not determine if
@@ -2684,27 +2687,46 @@ SearchService.prototype = {
     if (gInitialized) {
       if (!Components.isSuccessCode(this._initRV)) {
         LOG("_ensureInitialized: failure");
         throw this._initRV;
       }
       return;
     }
 
-    let warning =
-      "Search service falling back to synchronous initialization. " +
-      "This is generally the consequence of an add-on using a deprecated " +
-      "search service API.";
-    Deprecated.warning(warning, "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIBrowserSearchService#async_warning");
-    LOG(warning);
-
-    this._syncInit();
-    if (!Components.isSuccessCode(this._initRV)) {
-      throw this._initRV;
-    }
+    let err = new Error("Something tried to use the search service before it's been " +
+      "properly intialized. Please examine the stack trace to figure out what and " +
+      "where to fix it:\n");
+    err.message += err.stack;
+    throw err;
+
+    // let warning =
+    //   "Search service falling back to synchronous initialization. " +
+    //   "This is generally the consequence of an add-on using a deprecated " +
+    //   "search service API.";
+    // Deprecated.warning(warning, "https://developer.mozilla.org/en-US/docs/" +
+    //   "XPCOM_Interface_Reference/nsISearchService#async_warning");
+    // LOG(warning);
+
+    // this._syncInit();
+    // if (!Components.isSuccessCode(this._initRV)) {
+    //   throw this._initRV;
+    // }
+  },
+
+  _promiseInitialized() {
+    return new Promise((resolve, reject) => {
+      this.init(retVal => {
+        if (!Components.isSuccessCode(retVal)) {
+          reject(retVal);
+        } else {
+          resolve();
+        }
+      });
+    });
   },
 
   // Synchronous implementation of the initializer.
   // Used by |_ensureInitialized| as a fallback if initialization is not
   // complete.
   _syncInit: function SRCH_SVC__syncInit() {
     LOG("_syncInit start");
     this._initStarted = true;
@@ -2836,17 +2858,17 @@ SearchService.prototype = {
     }
 
     return this.getEngineByName(defaultEngine);
   },
 
   resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() {
     let originalDefaultEngine = this.originalDefaultEngine;
     originalDefaultEngine.hidden = false;
-    this.currentEngine = originalDefaultEngine;
+    this.defaultEngine = originalDefaultEngine;
   },
 
   _buildCache: function SRCH_SVC__buildCache() {
     if (this._batchTask)
       this._batchTask.disarm();
 
     let cache = {};
     let locale = getLocale();
@@ -3779,17 +3801,17 @@ SearchService.prototype = {
     if (aWithHidden)
       return this._sortedEngines;
 
     return this._sortedEngines.filter(function(engine) {
                                         return !engine.hidden;
                                       });
   },
 
-  // nsIBrowserSearchService
+  // nsISearchService
   init: function SRCH_SVC_init(observer) {
     LOG("SearchService.init");
     let self = this;
     if (!this._initStarted) {
       TelemetryStopwatch.start("SEARCH_SERVICE_INIT_MS");
       this._initStarted = true;
       (async function task() {
         try {
@@ -3804,54 +3826,53 @@ SearchService.prototype = {
           } else {
             self._initObservers.reject(ex);
             TelemetryStopwatch.cancel("SEARCH_SERVICE_INIT_MS");
           }
         }
       })();
     }
     if (observer) {
+      const callback = observer.onInitComplete || observer;
       this._initObservers.promise.then(
         function onSuccess() {
           try {
-            observer.onInitComplete(self._initRV);
+            callback(self._initRV);
           } catch (e) {
             Cu.reportError(e);
           }
         },
         function onError(aReason) {
           Cu.reportError("Internal error while initializing SearchService: " + aReason);
-          observer.onInitComplete(Cr.NS_ERROR_UNEXPECTED);
+          callback(Cr.NS_ERROR_UNEXPECTED);
         }
       );
     }
   },
 
   get isInitialized() {
     return gInitialized;
   },
 
-  getEngines: function SRCH_SVC_getEngines(aCount) {
-    this._ensureInitialized();
+  async getEngines() {
+    await this._promiseInitialized();
     LOG("getEngines: getting all engines");
     var engines = this._getSortedEngines(true);
-    aCount.value = engines.length;
     return engines;
   },
 
-  getVisibleEngines: function SRCH_SVC_getVisible(aCount) {
-    this._ensureInitialized();
+  async getVisibleEngines() {
+    await this._promiseInitialized();
     LOG("getVisibleEngines: getting all visible engines");
     var engines = this._getSortedEngines(false);
-    aCount.value = engines.length;
     return engines;
   },
 
-  getDefaultEngines: function SRCH_SVC_getDefault(aCount) {
-    this._ensureInitialized();
+  async getDefaultEngines() {
+    await this._promiseInitialized();
     function isDefault(engine) {
       return engine._isDefault;
     }
     var engines = this._sortedEngines.filter(isDefault);
     var engineOrder = {};
     var engineName;
     var i = 1;
 
@@ -3902,97 +3923,91 @@ SearchService.prototype = {
       if (aIdx)
         return -1;
       if (bIdx)
         return 1;
 
       return a.name.localeCompare(b.name);
     }
     engines.sort(compareEngines);
-
-    aCount.value = engines.length;
     return engines;
   },
 
-  getEnginesByExtensionID: function SRCH_SVC_getEngines(aExtensionID, aCount) {
-    this._ensureInitialized();
-    LOG("getEngines: getting all engines for " + aExtensionID);
+  async getEnginesByExtensionID(extensionID) {
+    await this._promiseInitialized();
+    LOG("getEngines: getting all engines for " + extensionID);
     var engines = this._getSortedEngines(true).filter(function(engine) {
-      return engine._extensionID == aExtensionID;
+      return engine._extensionID == extensionID;
     });
-    aCount.value = engines.length;
     return engines;
   },
 
-
   getEngineByName: function SRCH_SVC_getEngineByName(aEngineName) {
     this._ensureInitialized();
     return this._engines[aEngineName] || null;
   },
 
   getEngineByAlias: function SRCH_SVC_getEngineByAlias(aAlias) {
     this._ensureInitialized();
     for (var engineName in this._engines) {
       var engine = this._engines[engineName];
       if (engine && (engine.alias == aAlias || engine._internalAliases.includes(aAlias))) {
         return engine;
       }
     }
     return null;
   },
 
-  addEngineWithDetails: function SRCH_SVC_addEWD(aName, iconURL, alias,
-                                                 description, method,
-                                                 template, extensionID) {
+  async addEngineWithDetails(name, iconURL, alias, description, method, template, extensionID) {
     let isCurrent = false;
     var params;
 
     if (iconURL && typeof iconURL == "object") {
       params = iconURL;
     } else {
       params = {
         iconURL,
         alias,
         description,
         method,
         template,
         extensionID,
       };
     }
 
-    this._ensureInitialized();
-    if (!aName)
+    await this._promiseInitialized();
+    if (!name)
       FAIL("Invalid name passed to addEngineWithDetails!");
     if (!params.template)
       FAIL("Invalid template passed to addEngineWithDetails!");
-    let existingEngine = this._engines[aName];
+    let existingEngine = this._engines[name];
     if (existingEngine) {
       if (params.extensionID &&
           existingEngine._loadPath.startsWith(`jar:[profile]/extensions/${params.extensionID}`)) {
         // This is a legacy extension engine that needs to be migrated to WebExtensions.
-        isCurrent = this.currentEngine == existingEngine;
-        this.removeEngine(existingEngine);
+        isCurrent = this.defaultEngine == existingEngine;
+        await this.removeEngine(existingEngine);
       } else {
         FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);
       }
     }
 
-    let newEngine = new Engine(sanitizeName(aName), false);
-    newEngine._initFromMetadata(aName, params);
+    let newEngine = new Engine(sanitizeName(name), false);
+    newEngine._initFromMetadata(name, params);
     newEngine._loadPath = "[other]addEngineWithDetails";
     if (params.extensionID) {
       newEngine._loadPath += ":" + params.extensionID;
     }
     this._addEngineToStore(newEngine);
     if (isCurrent) {
-      this.currentEngine = newEngine;
+      this.defaultEngine = newEngine;
     }
   },
 
-  addEnginesFromExtension(extension) {
+  async addEnginesFromExtension(extension) {
     let {IconDetails} = ExtensionParent;
     let {manifest} = extension;
 
     // General set of icons for an engine.
     let icons = extension.manifest.icons;
     let iconList = [];
     if (icons) {
       iconList = Object.entries(icons).map(icon => {
@@ -4011,69 +4026,66 @@ SearchService.prototype = {
       alias: searchProvider.keyword,
       extensionID: extension.id,
       isBuiltIn: extension.isPrivileged,
       suggestURL: searchProvider.suggest_url,
       suggestPostParams: searchProvider.suggest_url_post_params,
       queryCharset: searchProvider.encoding || "UTF-8",
       mozParams: searchProvider.params,
     };
-    this.addEngineWithDetails(searchProvider.name.trim(), params);
+    return this.addEngineWithDetails(searchProvider.name.trim(), params);
   },
 
-  addEngine: function SRCH_SVC_addEngine(aEngineURL, aIconURL, aConfirm,
-                                         aCallback, aExtensionID) {
-    LOG("addEngine: Adding \"" + aEngineURL + "\".");
-    this._ensureInitialized();
+  async addEngine(engineURL, iconURL, confirm, extensionID) {
+    LOG("addEngine: Adding \"" + engineURL + "\".");
+    await this._promiseInitialized();
+    let errCode;
     try {
-      var uri = makeURI(aEngineURL);
+      var uri = makeURI(engineURL);
       var engine = new Engine(uri, false);
-      if (aCallback) {
+      engine._setIcon(iconURL, false);
+      engine._confirm = confirm;
+      if (extensionID) {
+        engine._extensionID = extensionID;
+      }
+      errCode = await new Promise(resolve => {
         engine._installCallback = function(errorCode) {
-          try {
-            if (errorCode == null)
-              aCallback.onSuccess(engine);
-            else
-              aCallback.onError(errorCode);
-          } catch (ex) {
-            Cu.reportError("Error invoking addEngine install callback: " + ex);
-          }
+          resolve(errorCode);
           // Clear the reference to the callback now that it's been invoked.
           engine._installCallback = null;
         };
+        engine._initFromURIAndLoad(uri);
+      });
+      if (errCode) {
+        throw errCode;
       }
-      engine._initFromURIAndLoad(uri);
     } catch (ex) {
       // Drop the reference to the callback, if set
       if (engine)
         engine._installCallback = null;
-      FAIL("addEngine: Error adding engine:\n" + ex, Cr.NS_ERROR_FAILURE);
+      FAIL("addEngine: Error adding engine:\n" + ex, errCode || Cr.NS_ERROR_FAILURE);
     }
-    engine._setIcon(aIconURL, false);
-    engine._confirm = aConfirm;
-    if (aExtensionID) {
-      engine._extensionID = aExtensionID;
-    }
+    return engine;
   },
 
-  removeEngine: function SRCH_SVC_removeEngine(aEngine) {
-    this._ensureInitialized();
-    if (!aEngine)
+  async removeEngine(engine) {
+    await this._promiseInitialized();
+    if (!engine)
       FAIL("no engine passed to removeEngine!");
 
     var engineToRemove = null;
     for (var e in this._engines) {
-      if (aEngine.wrappedJSObject == this._engines[e])
+      if (engine.wrappedJSObject == this._engines[e])
         engineToRemove = this._engines[e];
     }
 
     if (!engineToRemove)
       FAIL("removeEngine: Can't find engine to remove!", Cr.NS_ERROR_FILE_NOT_FOUND);
 
-    if (engineToRemove == this.currentEngine) {
+    if (engineToRemove == this.defaultEngine) {
       this._currentEngine = null;
     }
 
     if (engineToRemove._readOnly) {
       // Just hide it (the "hidden" setter will notify) and remove its alias to
       // avoid future conflicts with other engines.
       engineToRemove.hidden = true;
       engineToRemove.alias = null;
@@ -4098,83 +4110,77 @@ SearchService.prototype = {
       delete this._engines[engineToRemove.name];
 
       // Since we removed an engine, we need to update the preferences.
       this._saveSortedEngineList();
     }
     notifyAction(engineToRemove, SEARCH_ENGINE_REMOVED);
   },
 
-  moveEngine: function SRCH_SVC_moveEngine(aEngine, aNewIndex) {
-    this._ensureInitialized();
-    if ((aNewIndex > this._sortedEngines.length) || (aNewIndex < 0))
+  async moveEngine(engine, newIndex) {
+    await this._promiseInitialized();
+    if ((newIndex > this._sortedEngines.length) || (newIndex < 0))
       FAIL("SRCH_SVC_moveEngine: Index out of bounds!");
-    if (!(aEngine instanceof Ci.nsISearchEngine))
+    if (!(engine instanceof Ci.nsISearchEngine) && !(engine instanceof Engine))
       FAIL("SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
-    if (aEngine.hidden)
+    if (engine.hidden)
       FAIL("moveEngine: Can't move a hidden engine!", Cr.NS_ERROR_FAILURE);
 
-    var engine = aEngine.wrappedJSObject;
+    engine = engine.wrappedJSObject;
 
     var currentIndex = this._sortedEngines.indexOf(engine);
     if (currentIndex == -1)
       FAIL("moveEngine: Can't find engine to move!", Cr.NS_ERROR_UNEXPECTED);
 
     // Our callers only take into account non-hidden engines when calculating
-    // aNewIndex, but we need to move it in the array of all engines, so we
-    // need to adjust aNewIndex accordingly. To do this, we count the number
+    // newIndex, but we need to move it in the array of all engines, so we
+    // need to adjust newIndex accordingly. To do this, we count the number
     // of hidden engines in the list before the engine that we're taking the
     // place of. We do this by first finding newIndexEngine (the engine that
     // we were supposed to replace) and then iterating through the complete
-    // engine list until we reach it, increasing aNewIndex for each hidden
+    // engine list until we reach it, increasing newIndex for each hidden
     // engine we find on our way there.
     //
     // This could be further simplified by having our caller pass in
-    // newIndexEngine directly instead of aNewIndex.
-    var newIndexEngine = this._getSortedEngines(false)[aNewIndex];
+    // newIndexEngine directly instead of newIndex.
+    var newIndexEngine = this._getSortedEngines(false)[newIndex];
     if (!newIndexEngine)
       FAIL("moveEngine: Can't find engine to replace!", Cr.NS_ERROR_UNEXPECTED);
 
     for (var i = 0; i < this._sortedEngines.length; ++i) {
       if (newIndexEngine == this._sortedEngines[i])
         break;
       if (this._sortedEngines[i].hidden)
-        aNewIndex++;
+        newIndex++;
     }
 
-    if (currentIndex == aNewIndex)
+    if (currentIndex == newIndex)
       return; // nothing to do!
 
     // Move the engine
     var movedEngine = this.__sortedEngines.splice(currentIndex, 1)[0];
-    this.__sortedEngines.splice(aNewIndex, 0, movedEngine);
+    this.__sortedEngines.splice(newIndex, 0, movedEngine);
 
     notifyAction(engine, SEARCH_ENGINE_CHANGED);
 
     // Since we moved an engine, we need to update the preferences.
     this._saveSortedEngineList();
   },
 
-  restoreDefaultEngines: function SRCH_SVC_resetDefaultEngines() {
+  restoreDefaultEngines() {
     this._ensureInitialized();
     for (let name in this._engines) {
       let e = this._engines[name];
       // Unhide all default engines
       if (e.hidden && e._isDefault)
         e.hidden = false;
     }
   },
 
-  get defaultEngine() { return this.currentEngine; },
-
-  set defaultEngine(val) {
-    this.currentEngine = val;
-  },
-
-  get currentEngine() {
+  get defaultEngine() {
     this._ensureInitialized();
     if (!this._currentEngine) {
       let name = this.getGlobalAttr("current");
       let engine = this.getEngineByName(name);
       if (engine && (this.getGlobalAttr("hash") == getVerificationHash(name) ||
                      engine._isDefault)) {
         // If the current engine is a default one, we can relax the
         // verification hash check to reduce the annoyance for users who
@@ -4188,43 +4194,43 @@ SearchService.prototype = {
     // If the current engine is not set or hidden, we fallback...
     if (!this._currentEngine || this._currentEngine.hidden) {
       // first to the original default engine
       let originalDefault = this.originalDefaultEngine;
       if (!originalDefault || originalDefault.hidden) {
         // then to the first visible engine
         let firstVisible = this._getSortedEngines(false)[0];
         if (firstVisible && !firstVisible.hidden) {
-          this.currentEngine = firstVisible;
+          this.defaultEngine = firstVisible;
           return firstVisible;
         }
         // and finally as a last resort we unhide the original default engine.
         if (originalDefault)
           originalDefault.hidden = false;
       }
       if (!originalDefault)
         return null;
 
       // If the current engine wasn't set or was hidden, we used a fallback
       // to pick a new current engine. As soon as we return it, this new
       // current engine will become user-visible, so we should persist it.
       // by calling the setter.
-      this.currentEngine = originalDefault;
+      this.defaultEngine = originalDefault;
     }
 
     return this._currentEngine;
   },
 
-  set currentEngine(val) {
+  set defaultEngine(val) {
     this._ensureInitialized();
     // Sometimes we get wrapped nsISearchEngine objects (external XPCOM callers),
     // and sometimes we get raw Engine JS objects (callers in this file), so
     // handle both.
     if (!(val instanceof Ci.nsISearchEngine) && !(val instanceof Engine))
-      FAIL("Invalid argument passed to currentEngine setter");
+      FAIL("Invalid argument passed to defaultEngine setter");
 
     var newCurrentEngine = this.getEngineByName(val.name);
     if (!newCurrentEngine)
       FAIL("Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED);
 
     if (!newCurrentEngine._isDefault) {
       // If a non default engine is being set as the current engine, ensure
       // its loadPath has a verification hash.
@@ -4255,22 +4261,32 @@ SearchService.prototype = {
 
     this.setGlobalAttr("current", newName);
     this.setGlobalAttr("hash", getVerificationHash(newName));
 
     notifyAction(this._currentEngine, SEARCH_ENGINE_DEFAULT);
     notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
   },
 
-  getDefaultEngineInfo() {
+  async getDefault() {
+    await this._promiseInitialized();
+    return this.defaultEngine;
+  },
+
+  async setDefault(engine) {
+    await this._promiseInitialized();
+    return this.defaultEngine = engine;
+  },
+
+  async getDefaultEngineInfo() {
     let result = {};
 
     let engine;
     try {
-      engine = this.defaultEngine;
+      engine = await this.getDefault();
     } catch (e) {
       // The defaultEngine getter will throw if there's no engine at all,
       // which shouldn't happen unless an add-on or a test deleted all of them.
       // Our preferences UI doesn't let users do that.
       Cu.reportError("getDefaultEngineInfo: No default engine");
     }
 
     if (!engine) {
@@ -4349,17 +4365,18 @@ SearchService.prototype = {
         }
 
         if (!sendSubmissionURL) {
           // ... or well known search domains.
           //
           // Starts with: www.google., search.aol., yandex.
           // or
           // Ends with: search.yahoo.com, .ask.com, .bing.com, .startpage.com, baidu.com, duckduckgo.com
-          const urlTest = /^(?:www\.google\.|search\.aol\.|yandex\.)|(?:search\.yahoo|\.ask|\.bing|\.startpage|\.baidu|duckduckgo)\.com$/;
+          const urlTest =
+            /^(?:www\.google\.|search\.aol\.|yandex\.)|(?:search\.yahoo|\.ask|\.bing|\.startpage|\.baidu|duckduckgo)\.com$/;
           sendSubmissionURL = urlTest.test(engineHost);
         }
       }
 
       if (sendSubmissionURL) {
         let uri = engine._getURLOfType("text/html")
                         .getSubmission("", engine, "searchbar").uri;
         uri = uri.mutate()
@@ -4582,17 +4599,17 @@ SearchService.prototype = {
         switch (aVerb) {
           case SEARCH_ENGINE_LOADED:
             var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
             LOG("nsSearchService::observe: Done installation of " + engine.name
                 + ".");
             this._addEngineToStore(engine.wrappedJSObject);
             if (engine.wrappedJSObject._useNow) {
               LOG("nsSearchService::observe: setting current");
-              this.currentEngine = aEngine;
+              this.defaultEngine = aEngine;
             }
             // The addition of the engine to the store always triggers an ADDED
             // or a CHANGED notification, that will trigger the task below.
             break;
           case SEARCH_ENGINE_ADDED:
           case SEARCH_ENGINE_CHANGED:
           case SEARCH_ENGINE_REMOVED:
             this.batchTask.disarm();
@@ -4709,17 +4726,17 @@ SearchService.prototype = {
 
   _removeObservers: function SRCH_SVC_removeObservers() {
     Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
     Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);
     Services.obs.removeObserver(this, TOPIC_LOCALES_CHANGE);
   },
 
   QueryInterface: ChromeUtils.generateQI([
-    Ci.nsIBrowserSearchService,
+    Ci.nsISearchService,
     Ci.nsIObserver,
     Ci.nsITimerCallback,
   ]),
 };
 
 
 const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
 
--- a/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
@@ -144,17 +144,17 @@ var TelemetryEnvironment = {
 
   // Policy to use when saving preferences. Exported for using them in tests.
   RECORD_PREF_STATE: 1, // Don't record the preference value
   RECORD_PREF_VALUE: 2, // We only record user-set prefs.
   RECORD_DEFAULTPREF_VALUE: 3, // We only record default pref if set
   RECORD_DEFAULTPREF_STATE: 4, // We only record if the pref exists
 
   // Testing method
-  testWatchPreferences(prefMap) {
+  async testWatchPreferences(prefMap) {
     return getGlobal()._watchPreferences(prefMap);
   },
 
   /**
    * Intended for use in tests only.
    *
    * In multiple tests we need a way to shut and re-start telemetry together
    * with TelemetryEnvironment. This is problematic due to the fact that
@@ -883,25 +883,24 @@ function EnvironmentCache() {
   this._watchedPrefs = DEFAULT_ENVIRONMENT_PREFS;
 
   this._currentEnvironment = {
     build: this._getBuild(),
     partner: this._getPartner(),
     system: this._getSystem(),
   };
 
-  this._updateSettings();
   this._addObservers();
 
   // Build the remaining asynchronous parts of the environment. Don't register change listeners
   // until the initial environment has been built.
 
-  let p = [];
+  let p = [ this._updateSettings() ];
   this._addonBuilder = new EnvironmentAddonBuilder(this);
-  p = [ this._addonBuilder.init() ];
+  p.push(this._addonBuilder.init());
 
   this._currentEnvironment.profile = {};
   p.push(this._updateProfile());
   if (AppConstants.MOZ_BUILD_APP == "browser") {
     p.push(this._loadAttributionAsync());
   }
   p.push(this._loadAutoUpdateAsync());
 
@@ -1046,20 +1045,20 @@ EnvironmentCache.prototype = {
     this._log.trace("shutdown");
     this._shutdown = true;
   },
 
   /**
    * Only used in tests, set the preferences to watch.
    * @param aPreferences A map of preferences names and their recording policy.
    */
-  _watchPreferences(aPreferences) {
+  async _watchPreferences(aPreferences) {
     this._stopWatchingPrefs();
     this._watchedPrefs = aPreferences;
-    this._updateSettings();
+    await this._updateSettings();
     this._startWatchingPrefs();
   },
 
   /**
    * Get an object containing the values for the watched preferences. Depending on the
    * policy, the value for a preference or whether it was changed by user is reported.
    *
    * @return An object containing the preferences values.
@@ -1250,17 +1249,17 @@ EnvironmentCache.prototype = {
     }
 
     return name;
   },
 
   /**
    * Update the default search engine value.
    */
-  _updateSearchEngine() {
+  async _updateSearchEngine() {
     if (!this._canQuerySearch) {
       this._log.trace("_updateSearchEngine - ignoring early call");
       return;
     }
 
     if (!Services.search) {
       // Just ignore cases where the search service is not implemented.
       return;
@@ -1271,35 +1270,35 @@ EnvironmentCache.prototype = {
       return;
     }
 
     // Make sure we have a settings section.
     this._currentEnvironment.settings = this._currentEnvironment.settings || {};
     // Update the search engine entry in the current environment.
     this._currentEnvironment.settings.defaultSearchEngine = this._getDefaultSearchEngine();
     this._currentEnvironment.settings.defaultSearchEngineData =
-      Services.search.getDefaultEngineInfo();
+      await Services.search.getDefaultEngineInfo();
 
     // Record the cohort identifier used for search defaults A/B testing.
     if (Services.prefs.prefHasUserValue(PREF_SEARCH_COHORT)) {
       const searchCohort = Services.prefs.getCharPref(PREF_SEARCH_COHORT);
       this._currentEnvironment.settings.searchCohort = searchCohort;
       TelemetryEnvironment.setExperimentActive("searchCohort", searchCohort);
     }
   },
 
   /**
    * Update the default search engine value and trigger the environment change.
    */
-  _onSearchEngineChange() {
+  async _onSearchEngineChange() {
     this._log.trace("_onSearchEngineChange");
 
     // Finally trigger the environment change notification.
     let oldEnvironment = Cu.cloneInto(this._currentEnvironment, myScope);
-    this._updateSearchEngine();
+    await this._updateSearchEngine();
     this._onEnvironmentChange("search-engine-changed", oldEnvironment);
   },
 
   /**
    * Refresh the Telemetry environment and trigger an environment change due to
    * a change in compositor process (normally this will mean we've fallen back
    * from out-of-process to in-process compositing).
    */
@@ -1399,17 +1398,17 @@ EnvironmentCache.prototype = {
     this._currentEnvironment.settings = this._currentEnvironment.settings || {};
     this._currentEnvironment.settings.isDefaultBrowser =
       this._sessionWasRestored ? this._isDefaultBrowser() : null;
   },
 
   /**
    * Update the cached settings data.
    */
-  _updateSettings() {
+  async _updateSettings() {
     let updateChannel = null;
     try {
       updateChannel = Utils.getUpdateChannel();
     } catch (e) {}
 
     this._currentEnvironment.settings = {
       blocklistEnabled: Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true),
       e10sEnabled: Services.appinfo.browserTabsRemoteAutostart,
@@ -1424,17 +1423,17 @@ EnvironmentCache.prototype = {
       sandbox: this._getSandboxData(),
     };
 
     this._currentEnvironment.settings.addonCompatibilityCheckEnabled =
       AddonManager.checkCompatibility;
 
     this._updateAttribution();
     this._updateDefaultBrowser();
-    this._updateSearchEngine();
+    await this._updateSearchEngine();
     this._updateAutoDownload();
   },
 
   _getSandboxData() {
     let effectiveContentProcessLevel = null;
     try {
       let sandboxSettings = Cc["@mozilla.org/sandbox/sandbox-settings;1"].
                             getService(Ci.mozISandboxSettings);
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -99,22 +99,24 @@ var initTable = {
   netUtils: ["@mozilla.org/network/util;1", "nsINetUtil"],
   loadContextInfo: ["@mozilla.org/load-context-info-factory;1", "nsILoadContextInfoFactory"],
   qms: ["@mozilla.org/dom/quota-manager-service;1", "nsIQuotaManagerService"],
   xulStore: ["@mozilla.org/xul/xulstore;1", "nsIXULStore"],
 };
 
 if (AppConstants.platform == "android") {
   initTable.androidBridge = ["@mozilla.org/android/bridge;1", "nsIAndroidBridge"];
+  if (AppConstants.MOZ_TOOLKIT_SEARCH) {
+    initTable.search = ["@mozilla.org/browser/search-service;1", "nsIBrowserSearchService"];
+  }
+} else if (AppConstants.MOZ_TOOLKIT_SEARCH) {
+  initTable.search = ["@mozilla.org/browser/search-service;1", "nsISearchService"];
 }
 if (AppConstants.MOZ_GECKO_PROFILER) {
   initTable.profiler = ["@mozilla.org/tools/profiler;1", "nsIProfiler"];
 }
-if (AppConstants.MOZ_TOOLKIT_SEARCH) {
-  initTable.search = ["@mozilla.org/browser/search-service;1", "nsIBrowserSearchService"];
-}
 if ("@mozilla.org/browser/enterprisepolicies;1" in Cc) {
   initTable.policies = ["@mozilla.org/browser/enterprisepolicies;1", "nsIEnterprisePolicies"];
 }
 
 XPCOMUtils.defineLazyServiceGetters(Services, initTable);
 
 initTable = undefined;
--- a/toolkit/modules/tests/xpcshell/test_Services.js
+++ b/toolkit/modules/tests/xpcshell/test_Services.js
@@ -51,17 +51,19 @@ function run_test() {
   checkService("sysinfo", Ci.nsIPropertyBag2);
   checkService("telemetry", Ci.nsITelemetry);
   checkService("tm", Ci.nsIThreadManager);
   checkService("uriFixup", Ci.nsIURIFixup);
   checkService("urlFormatter", Ci.nsIURLFormatter);
   checkService("vc", Ci.nsIVersionComparator);
   checkService("wm", Ci.nsIWindowMediator);
   checkService("ww", Ci.nsIWindowWatcher);
-  if ("nsIBrowserSearchService" in Ci) {
+  if ("nsISearchService" in Ci) {
+    checkService("search", Ci.nsISearchService);
+  } else if ("nsIBrowserSearchService" in Ci) {
     checkService("search", Ci.nsIBrowserSearchService);
   }
   if ("nsIAndroidBridge" in Ci) {
     checkService("androidBridge", Ci.nsIAndroidBridge);
   }
   if ("@mozilla.org/browser/enterprisepolicies;1" in Cc) {
     checkService("policies", Ci.nsIEnterprisePolicies);
   }