Bug 1524593 - nsISearchService (aka nsIBrowserSearchService, previously) refactor to be mostly an asynchronous, in preparation of WebExtension engines. r=daleharvey
authorMike de Boer <mdeboer@mozilla.com>
Sat, 02 Feb 2019 11:27:21 +0000
changeset 456558 add76bbdce4e452d1f921bcbc1224b30892d8393
parent 456557 9658187a54fbd9e7c5bda51fb4e5a041a7af77be
child 456559 0883d6f9b77a71662a16614b1265409ea57e2108
push id35489
push userrmaries@mozilla.com
push dateSat, 02 Feb 2019 21:36:03 +0000
treeherdermozilla-central@63348118ef1d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaleharvey
bugs1524593, 1492475, 1518543, 1523708
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1524593 - nsISearchService (aka nsIBrowserSearchService, previously) refactor to be mostly an asynchronous, in preparation of WebExtension engines. r=daleharvey This is a rollup of all the patches that have landed on the cedar project branch: https://hg.mozilla.org/projects/cedar/rev/891252fdd0b1a3e6b129025d94952ac30d922c7e Bug 1492475 - Part 1: Migrate most, if not all nsSearchService consumers to use async APIs. r=florian https://hg.mozilla.org/projects/cedar/rev/79b2eb2367aab104669bbc75c3b42290f7de1570 Bug 1492475 - Part 2: Move nsIBrowserSearchService.idl to toolkit/components/search/nsISearchService.idl and update references. r=florian https://hg.mozilla.org/projects/cedar/rev/a947d3cdf078032614edaa491ec3db1d046b55f4 Bug 1492475 - Part 3: The search service init() method should simply return a Promise. r=florian https://hg.mozilla.org/projects/cedar/rev/c1e172dfacad4b14ebdb352bee2fd946716acd59 Bug 1492475 - Part 4: Remove the synchronous initialization flow. r=florian https://hg.mozilla.org/projects/cedar/rev/cd41189eac88aa6023af1b0a060c15ddcd407952 Bug 1492475 - Part 5: Since async initialization of the search service now is implicit behavior, remove the distinctive verbiage used internally. r=florian https://hg.mozilla.org/projects/cedar/rev/2ae7189dfaa63cab0e264e7a2796b1610505c40a Bug 1492475 - Part 6: Update the cache build task to work with an actual Promise and re-initialize only once at the same time - all to fix race conditions here. r=florian https://hg.mozilla.org/projects/cedar/rev/c8ee92973f24a44496f2bee23c13e0c74b6e11d8 Bug 1492475 - Part 7: Make the region fetch not block the init flow, to ensure it's as fast as possible. r=florian https://hg.mozilla.org/projects/cedar/rev/c44e674e160ebab49ea5ba1ed5821bb8d3c30e53 Bug 1492475 - Part 8: Introduce an init flag, which can only be used privately, that allows to explicitly skip waiting for the region check process to complete. r=florian https://hg.mozilla.org/projects/cedar/rev/6c79eaf1d349638258d542ced0229d786f022683 Bug 1492475 - Part 9: Update unit tests to stop using 'currentEngine', in favor of 'defaultEngine'. r=Standard8 https://hg.mozilla.org/projects/cedar/rev/21b3aa17ee43dd0efd3c08564bbc7d747d4628b9 Bug 1492475 - Part 10: Update unit tests to be fully aware of the new, async signatures of the search service API and remove sync init flow tests. r=mkaply,florian https://hg.mozilla.org/projects/cedar/rev/ce5ba6901957903ade31888cdc6a52e2b828dac0 Bug 1492475 - Part 11: Repair incorrect usage of the `identifier` property of nsISearchEngine instances. r=florian https://hg.mozilla.org/projects/cedar/rev/fd177a7994b250605df4b98740bdd257373e21e5 Bug 1518543 - Fix up the Android (Fennec) nsISearchService shim to work with the new asynchronous API. r=florian https://hg.mozilla.org/projects/cedar/rev/3653d8ee22bb242b3ddc0222cb1f711b68b52f91 Bug 1523708 - Change the search service interaction in the show-heartbeat action to use the new async API. r=florian Differential Revision: https://phabricator.services.mozilla.com/D18355
browser/base/content/browser-pageActions.js
browser/base/content/browser.js
browser/base/content/test/about/browser_aboutHome_search_POST.js
browser/base/content/test/about/browser_aboutHome_search_composing.js
browser/base/content/test/about/browser_aboutHome_search_suggestion.js
browser/base/content/test/about/browser_aboutHome_search_telemetry.js
browser/base/content/test/about/head.js
browser/base/content/test/general/browser_contentSearchUI.js
browser/base/content/test/general/browser_keywordSearch.js
browser/base/content/test/general/browser_keywordSearch_postData.js
browser/base/content/test/general/browser_newTabDrop.js
browser/base/content/test/general/browser_newWindowDrop.js
browser/base/content/test/general/browser_tabDrop.js
browser/base/content/test/pageActions/browser_page_action_menu_add_search_engine.js
browser/base/content/test/performance/browser_urlbar_keyed_search.js
browser/base/content/test/performance/browser_urlbar_search.js
browser/components/enterprisepolicies/Policies.jsm
browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js
browser/components/enterprisepolicies/tests/browser/head.js
browser/components/extensions/parent/ext-chrome-settings-overrides.js
browser/components/extensions/parent/ext-search.js
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
browser/components/extensions/test/browser/browser_ext_omnibox.js
browser/components/extensions/test/browser/browser_ext_search.js
browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search.js
browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search_mozParam.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/newtab/test/browser/browser_asrouter_targeting.js
browser/components/newtab/test/unit/lib/TopSitesFeed.test.js
browser/components/newtab/test/unit/unit-entry.js
browser/components/preferences/in-content/search.js
browser/components/preferences/in-content/tests/browser_engines.js
browser/components/preferences/in-content/tests/browser_extension_controlled.js
browser/components/search/content/search-one-offs.js
browser/components/search/content/search.xml
browser/components/search/content/searchbar.js
browser/components/search/test/browser/SearchTestUtils.jsm
browser/components/search/test/browser/browser_426329.js
browser/components/search/test/browser/browser_addEngine.js
browser/components/search/test/browser/browser_amazon.js
browser/components/search/test/browser/browser_bing.js
browser/components/search/test/browser/browser_contextmenu.js
browser/components/search/test/browser/browser_ddg.js
browser/components/search/test/browser/browser_eBay.js
browser/components/search/test/browser/browser_google.js
browser/components/search/test/browser/browser_google_behavior.js
browser/components/search/test/browser/browser_hiddenOneOffs_cleanup.js
browser/components/search/test/browser/browser_hiddenOneOffs_diacritics.js
browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
browser/components/search/test/browser/browser_oneOffHeader.js
browser/components/search/test/browser/browser_searchEngine_behaviors.js
browser/components/search/test/browser/head.js
browser/components/tests/unit/test_distribution.js
browser/components/uitour/UITour.jsm
browser/components/uitour/test/browser_UITour.js
browser/components/uitour/test/browser_UITour3.js
browser/components/urlbar/tests/browser/browser_action_searchengine.js
browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
browser/components/urlbar/tests/browser/browser_canonizeURL.js
browser/components/urlbar/tests/browser/browser_urlbarPlaceholder.js
browser/components/urlbar/tests/browser/browser_urlbarSearchSingleWordNotification.js
browser/components/urlbar/tests/legacy/browser_autocomplete_a11y_label.js
browser/components/urlbar/tests/legacy/browser_autocomplete_enter_race.js
browser/components/urlbar/tests/legacy/browser_dragdropURL.js
browser/components/urlbar/tests/legacy/browser_search_favicon.js
browser/components/urlbar/tests/legacy/browser_urlbarAddonIframe.js
browser/components/urlbar/tests/legacy/browser_urlbarOneOffs.js
browser/components/urlbar/tests/legacy/browser_urlbarOneOffs_searchSuggestions.js
browser/components/urlbar/tests/legacy/browser_urlbarOneOffs_settings.js
browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions.js
browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions_opt-out.js
browser/components/urlbar/tests/legacy/browser_urlbarSearchTelemetry.js
browser/components/urlbar/tests/legacy/browser_urlbarStopSearchOnSelection.js
browser/components/urlbar/tests/legacy/browser_urlbarTokenAlias.js
browser/components/urlbar/tests/unit/head.js
browser/components/urlbar/tests/unit/test_UrlbarUtils_getShortcutOrURIAndPostData.js
browser/modules/ContentSearch.jsm
browser/modules/test/browser/browser_ContentSearch.js
browser/modules/test/browser/browser_UsageTelemetry_content.js
browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
docshell/base/nsDefaultURIFixup.cpp
docshell/base/nsDocShell.cpp
docshell/test/browser/browser_search_notification.js
docshell/test/browser/browser_uriFixupIntegration.js
docshell/test/unit/test_nsDefaultURIFixup_info.js
docshell/test/unit/test_nsDefaultURIFixup_search.js
dom/ipc/ContentParent.cpp
mobile/android/chrome/content/browser.js
mobile/android/tests/browser/robocop/testBrowserDiscovery.js
netwerk/base/moz.build
netwerk/base/nsIBrowserSearchService.idl
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/test/xpcshell/test_locale_data.js
toolkit/components/normandy/actions/ShowHeartbeatAction.jsm
toolkit/components/normandy/lib/NormandyDriver.jsm
toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
toolkit/components/places/tests/unifiedcomplete/test_autofill_search_engine_aliases.js
toolkit/components/places/tests/unifiedcomplete/test_autofill_search_engines.js
toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js
toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
toolkit/components/places/tests/unifiedcomplete/test_search_engine_current.js
toolkit/components/places/tests/unifiedcomplete/test_search_engine_host.js
toolkit/components/places/tests/unifiedcomplete/test_search_engine_restyle.js
toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
toolkit/components/processsingleton/MainProcessSingleton.js
toolkit/components/search/SearchSuggestionController.jsm
toolkit/components/search/moz.build
toolkit/components/search/nsISearchService.idl
toolkit/components/search/nsSearchService.js
toolkit/components/search/nsSearchSuggestions.js
toolkit/components/search/tests/xpcshell/head_search.js
toolkit/components/search/tests/xpcshell/test_645970.js
toolkit/components/search/tests/xpcshell/test_addEngineWithDetails.js
toolkit/components/search/tests/xpcshell/test_addEngineWithDetailsObject.js
toolkit/components/search/tests/xpcshell/test_addEngineWithExtensionID.js
toolkit/components/search/tests/xpcshell/test_addEngine_callback.js
toolkit/components/search/tests/xpcshell/test_async.js
toolkit/components/search/tests/xpcshell/test_async_addon.js
toolkit/components/search/tests/xpcshell/test_async_addon_no_override.js
toolkit/components/search/tests/xpcshell/test_async_disthidden.js
toolkit/components/search/tests/xpcshell/test_async_distribution.js
toolkit/components/search/tests/xpcshell/test_async_profile_engine.js
toolkit/components/search/tests/xpcshell/test_big_icon.js
toolkit/components/search/tests/xpcshell/test_currentEngine_fallback.js
toolkit/components/search/tests/xpcshell/test_defaultEngine.js
toolkit/components/search/tests/xpcshell/test_engineUpdate.js
toolkit/components/search/tests/xpcshell/test_engine_set_alias.js
toolkit/components/search/tests/xpcshell/test_geodefaults.js
toolkit/components/search/tests/xpcshell/test_hasEngineWithURL.js
toolkit/components/search/tests/xpcshell/test_hidden.js
toolkit/components/search/tests/xpcshell/test_identifiers.js
toolkit/components/search/tests/xpcshell/test_ignorelist.js
toolkit/components/search/tests/xpcshell/test_init_async_multiple.js
toolkit/components/search/tests/xpcshell/test_init_async_multiple_then_sync.js
toolkit/components/search/tests/xpcshell/test_invalid_engine_from_dir.js
toolkit/components/search/tests/xpcshell/test_json_cache.js
toolkit/components/search/tests/xpcshell/test_json_cache_ignorelist.js
toolkit/components/search/tests/xpcshell/test_list_json_locale.js
toolkit/components/search/tests/xpcshell/test_list_json_searchdefault_distro.js
toolkit/components/search/tests/xpcshell/test_list_json_searchorder.js
toolkit/components/search/tests/xpcshell/test_location.js
toolkit/components/search/tests/xpcshell/test_location_error.js
toolkit/components/search/tests/xpcshell/test_location_malformed_json.js
toolkit/components/search/tests/xpcshell/test_location_sync.js
toolkit/components/search/tests/xpcshell/test_location_timeout.js
toolkit/components/search/tests/xpcshell/test_location_timeout_xhr.js
toolkit/components/search/tests/xpcshell/test_migrateWebExtensionEngine.js
toolkit/components/search/tests/xpcshell/test_multipleIcons.js
toolkit/components/search/tests/xpcshell/test_nocache.js
toolkit/components/search/tests/xpcshell/test_nodb_pluschanges.js
toolkit/components/search/tests/xpcshell/test_notifications.js
toolkit/components/search/tests/xpcshell/test_parseSubmissionURL.js
toolkit/components/search/tests/xpcshell/test_purpose.js
toolkit/components/search/tests/xpcshell/test_reloadEngines.js
toolkit/components/search/tests/xpcshell/test_remove_profile_engine.js
toolkit/components/search/tests/xpcshell/test_require_engines_in_cache.js
toolkit/components/search/tests/xpcshell/test_save_sorted_engines.js
toolkit/components/search/tests/xpcshell/test_searchSuggest.js
toolkit/components/search/tests/xpcshell/test_selectedEngine.js
toolkit/components/search/tests/xpcshell/test_sendSubmissionURL.js
toolkit/components/search/tests/xpcshell/test_sync.js
toolkit/components/search/tests/xpcshell/test_sync_addon.js
toolkit/components/search/tests/xpcshell/test_sync_addon_no_override.js
toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js
toolkit/components/search/tests/xpcshell/test_sync_disthidden.js
toolkit/components/search/tests/xpcshell/test_sync_distribution.js
toolkit/components/search/tests/xpcshell/test_sync_fallback.js
toolkit/components/search/tests/xpcshell/test_sync_profile_engine.js
toolkit/components/search/tests/xpcshell/xpcshell.ini
toolkit/components/telemetry/app/TelemetryEnvironment.jsm
toolkit/components/telemetry/tests/marionette/tests/client/test_search_counts_across_sessions.py
toolkit/components/telemetry/tests/unit/head.js
toolkit/components/telemetry/tests/unit/test_SubsessionChaining.js
toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
toolkit/components/utils/ClientEnvironment.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
@@ -3775,23 +3775,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) {
@@ -3813,17 +3811,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) {
@@ -3889,31 +3887,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/base/content/test/about/browser_aboutHome_search_POST.js
+++ b/browser/base/content/test/about/browser_aboutHome_search_POST.js
@@ -5,34 +5,33 @@
 ignoreAllUncaughtExceptions();
 
 add_task(async function() {
   info("Check POST search engine support");
 
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function(browser) {
     return new Promise(resolve => {
       let searchObserver = async function search_observer(subject, topic, data) {
-        let currEngine = Services.search.defaultEngine;
+        let currEngine = await Services.search.getDefault();
         let engine = subject.QueryInterface(Ci.nsISearchEngine);
         info("Observer: " + data + " for " + engine.name);
 
         if (data != "engine-added")
           return;
 
         if (engine.name != "POST Search")
           return;
 
         Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
 
         // Ready to execute the tests!
         let needle = "Search for something awesome.";
 
-        let p = promiseContentSearchChange(browser, engine.name);
-        Services.search.defaultEngine = engine;
-        await p;
+        await Promise.all([promiseContentSearchChange(browser, engine.name),
+          Services.search.setDefault(engine)]);
         let promise = BrowserTestUtils.browserLoaded(browser);
         await ContentTask.spawn(browser, { needle }, async function(args) {
           let doc = content.document;
           let el = doc.querySelector(["#searchText", "#newtab-search-text"]);
           el.value = args.needle;
           doc.getElementById("searchSubmit").click();
         });
 
@@ -41,19 +40,19 @@ add_task(async function() {
         // When the search results load, check them for correctness.
         await ContentTask.spawn(browser, { needle }, async function(args) {
           let loadedText = content.document.body.textContent;
           ok(loadedText, "search page loaded");
           is(loadedText, "searchterms=" + escape(args.needle.replace(/\s/g, "+")),
              "Search text should arrive correctly");
         });
 
-        Services.search.defaultEngine = currEngine;
+        await Services.search.setDefault(currEngine);
         try {
-          Services.search.removeEngine(engine);
+          await Services.search.removeEngine(engine);
         } catch (ex) {}
         resolve();
       };
       Services.obs.addObserver(searchObserver, "browser-search-engine-modified");
       Services.search.addEngine("http://test:80/browser/browser/base/content/test/about/POSTSearchEngine.xml",
                                 null, false);
     });
   });
--- a/browser/base/content/test/about/browser_aboutHome_search_composing.js
+++ b/browser/base/content/test/about/browser_aboutHome_search_composing.js
@@ -4,20 +4,20 @@
 
 ignoreAllUncaughtExceptions();
 
 add_task(async function() {
   info("Clicking suggestion list while composing");
 
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, async function(browser) {
     // Add a test engine that provides suggestions and switch to it.
-    let currEngine = Services.search.defaultEngine;
+    let currEngine = await Services.search.getDefault();
     let engine = await promiseNewEngine("searchSuggestionEngine.xml");
     let p = promiseContentSearchChange(browser, engine.name);
-    Services.search.defaultEngine = engine;
+    await Services.search.setDefault(engine);
     await p;
 
     // Clear any search history results
     await new Promise((resolve, reject) => {
       FormHistory.update({op: "remove"}, {
         handleError(error) {
           reject(error);
         },
@@ -79,23 +79,21 @@ add_task(async function() {
       },
       caret: { start: 1, length: 0 },
     }, browser);
 
     info("Waiting for search suggestion table unhidden");
     await mutationPromise;
 
     // Click the second suggestion.
-    let expectedURL = Services.search.defaultEngine
-      .getSubmission("xbar", null, "homepage").uri.spec;
-    let loadPromise =
-      BrowserTestUtils.waitForDocLoadAndStopIt(expectedURL, gBrowser.selectedBrowser);
+    let expectedURL = (await Services.search.getDefault()).getSubmission("xbar", null, "homepage").uri.spec;
+    let loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(expectedURL, gBrowser.selectedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#TEMPID", {
       button: 0,
     }, browser);
     await loadPromise;
 
-    Services.search.defaultEngine = currEngine;
+    Services.search.setDefault(currEngine);
     try {
-      Services.search.removeEngine(engine);
+      await Services.search.removeEngine(engine);
     } catch (ex) { }
   });
 });
--- a/browser/base/content/test/about/browser_aboutHome_search_suggestion.js
+++ b/browser/base/content/test/about/browser_aboutHome_search_suggestion.js
@@ -5,21 +5,20 @@
 ignoreAllUncaughtExceptions();
 
 add_task(async function() {
   // See browser_contentSearchUI.js for comprehensive content search UI tests.
   info("Search suggestion smoke test");
 
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, async function(browser) {
     // Add a test engine that provides suggestions and switch to it.
-    let currEngine = Services.search.defaultEngine;
+    let currEngine = await Services.search.getDefault();
     let engine = await promiseNewEngine("searchSuggestionEngine.xml");
-    let p = promiseContentSearchChange(browser, engine.name);
-    Services.search.defaultEngine = engine;
-    await p;
+    await Promise.all([promiseContentSearchChange(browser, engine.name),
+      Services.search.setDefault(engine)]);
 
     await ContentTask.spawn(browser, null, async function() {
       // Type an X in the search input.
       let input = content.document.querySelector(["#searchText", "#newtab-search-text"]);
       input.focus();
     });
 
     await BrowserTestUtils.synthesizeKey("x", {}, browser);
@@ -49,14 +48,14 @@ add_task(async function() {
     await BrowserTestUtils.synthesizeKey("VK_DELETE", {}, browser);
 
     await ContentTask.spawn(browser, null, async function() {
       let table = content.document.getElementById("searchSuggestionTable");
       await ContentTaskUtils.waitForCondition(() => table.hidden,
         "Search suggestion table hidden");
     });
 
-    Services.search.defaultEngine = currEngine;
+    await Services.search.setDefault(currEngine);
     try {
-      Services.search.removeEngine(engine);
+      await Services.search.removeEngine(engine);
     } catch (ex) { }
   });
 });
--- a/browser/base/content/test/about/browser_aboutHome_search_telemetry.js
+++ b/browser/base/content/test/about/browser_aboutHome_search_telemetry.js
@@ -3,25 +3,24 @@
  */
 
 ignoreAllUncaughtExceptions();
 
 add_task(async function() {
   info("Check that performing a search fires a search event and records to Telemetry.");
 
   await BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, async function(browser) {
-    let currEngine = Services.search.defaultEngine;
+    let currEngine = await Services.search.getDefault();
     let engine = await promiseNewEngine("searchSuggestionEngine.xml");
     // Make this actually work in healthreport by giving it an ID:
     Object.defineProperty(engine.wrappedJSObject, "identifier",
                           { value: "org.mozilla.testsearchsuggestions" });
 
-    let p = promiseContentSearchChange(browser, engine.name);
-    Services.search.defaultEngine = engine;
-    await p;
+    await Promise.all([promiseContentSearchChange(browser, engine.name),
+      Services.search.setDefault(engine)]);
 
     await ContentTask.spawn(browser, { expectedName: engine.name }, async function(args) {
       let engineName = content.wrappedJSObject.gContentSearchController.defaultEngine.name;
       is(engineName, args.expectedName, "Engine name in DOM should match engine we just added");
     });
 
     let numSearchesBefore = 0;
     // Get the current number of recorded searches.
@@ -32,18 +31,17 @@ add_task(async function() {
         numSearchesBefore = hs[histogramKey].sum;
       }
     } catch (ex) {
       // No searches performed yet, not a problem, |numSearchesBefore| is 0.
     }
 
     let searchStr = "a search";
 
-    let expectedURL = Services.search.defaultEngine
-      .getSubmission(searchStr, null, "homepage").uri.spec;
+    let expectedURL = (await Services.search.getDefault()).getSubmission(searchStr, null, "homepage").uri.spec;
     let promise = BrowserTestUtils.waitForDocLoadAndStopIt(expectedURL, browser);
 
     // Perform a search to increase the SEARCH_COUNT histogram.
     await ContentTask.spawn(browser, { searchStr }, async function(args) {
       let doc = content.document;
       info("Perform a search.");
       let el = doc.querySelector(["#searchText", "#newtab-search-text"]);
       el.value = args.searchStr;
@@ -53,14 +51,14 @@ add_task(async function() {
     await promise;
 
     // Make sure the SEARCH_COUNTS histogram has the right key and count.
     let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
     Assert.ok(histogramKey in hs, "histogram with key should be recorded");
     Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
                  "histogram sum should be incremented");
 
-    Services.search.defaultEngine = currEngine;
+    await Services.search.setDefault(currEngine);
     try {
-      Services.search.removeEngine(engine);
+      await Services.search.removeEngine(engine);
     } catch (ex) {}
   });
 });
--- a/browser/base/content/test/about/head.js
+++ b/browser/base/content/test/about/head.js
@@ -159,29 +159,28 @@ function promiseContentSearchChange(brow
       });
     });
   });
 }
 
 /**
  * Wait for the search engine to be added.
  */
-function promiseNewEngine(basename) {
+async function promiseNewEngine(basename) {
   info("Waiting for engine to be added: " + basename);
-  return new Promise((resolve, reject) => {
-    let url = getRootDirectory(gTestPath) + basename;
-    Services.search.addEngine(url, "", false, {
-      onSuccess(engine) {
-        info("Search engine added: " + basename);
-        registerCleanupFunction(() => {
-          try {
-            Services.search.removeEngine(engine);
-          } catch (ex) { /* Can't remove the engine more than once */ }
-        });
-        resolve(engine);
-      },
-      onError(errCode) {
-        ok(false, "addEngine failed with error code " + errCode);
-        reject();
-      },
-    });
+  let url = getRootDirectory(gTestPath) + basename;
+  let engine;
+  try {
+    engine = await Services.search.addEngine(url, "", false);
+  } catch (errCode) {
+    ok(false, "addEngine failed with error code " + errCode);
+    throw errCode;
+  }
+
+  info("Search engine added: " + basename);
+  registerCleanupFunction(async () => {
+    try {
+      await Services.search.removeEngine(engine);
+    } catch (ex) { /* Can't remove the engine more than once */ }
   });
+
+  return engine;
 }
--- a/browser/base/content/test/general/browser_contentSearchUI.js
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -731,26 +731,26 @@ function promiseMsg(name, type, msgMan) 
       }
     });
   });
 }
 
 function setUpEngines() {
   return (async function() {
     info("Removing default search engines");
-    let currentEngineName = Services.search.defaultEngine.name;
-    let currentEngines = Services.search.getVisibleEngines();
+    let currentEngineName = (await Services.search.getDefault()).name;
+    let currentEngines = await Services.search.getVisibleEngines();
     info("Adding test search engines");
     let rootDir = getRootDirectory(gTestPath);
     let engine1 = await SearchTestUtils.promiseNewSearchEngine(
       rootDir + TEST_ENGINE_BASENAME);
     await SearchTestUtils.promiseNewSearchEngine(
       rootDir + TEST_ENGINE_2_BASENAME);
-    Services.search.defaultEngine = engine1;
+    await Services.search.setDefault(engine1);
     for (let engine of currentEngines) {
-      Services.search.removeEngine(engine);
+      await Services.search.removeEngine(engine);
     }
-    registerCleanupFunction(() => {
+    registerCleanupFunction(async () => {
       Services.search.restoreDefaultEngines();
-      Services.search.defaultEngine = Services.search.getEngineByName(currentEngineName);
+      await Services.search.setDefault(Services.search.getEngineByName(currentEngineName));
     });
   })();
 }
--- a/browser/base/content/test/general/browser_keywordSearch.js
+++ b/browser/base/content/test/general/browser_keywordSearch.js
@@ -86,17 +86,17 @@ function test() {
 
   registerCleanupFunction(function() {
     Services.ww.unregisterNotification(windowObserver);
 
     gBrowser.removeProgressListener(listener);
     gBrowser.removeTab(tab);
   });
 
-  nextTest();
+  Services.search.init().then(nextTest);
 }
 
 var gCurrTest;
 function nextTest() {
   if (gTests.length) {
     gCurrTest = gTests.shift();
     doTest();
   } else {
--- a/browser/base/content/test/general/browser_keywordSearch_postData.js
+++ b/browser/base/content/test/general/browser_keywordSearch_postData.js
@@ -16,32 +16,32 @@ var gTests = [
   },
 ];
 
 add_task(async function setup() {
   let engineAddedPromise = TestUtils.topicObserved("browser-search-engine-modified", (subject, data) => {
     return data == "engine-added";
   });
 
-  Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
-                            null, false);
+  const url = "http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml";
+  await Services.search.addEngine(url, null, false);
 
   let [subject, data] = await engineAddedPromise;
 
   let engine = subject.QueryInterface(Ci.nsISearchEngine);
   info("Observer: " + data + " for " + engine.name);
 
   if (engine.name != "POST Search") {
     Assert.ok(false, "Wrong search engine added");
   }
 
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
 
-  registerCleanupFunction(function() {
-    Services.search.removeEngine(engine);
+  registerCleanupFunction(async function() {
+    await Services.search.removeEngine(engine);
   });
 });
 
 add_task(async function() {
   for (let test of gTests) {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
     let browser = tab.linkedBrowser;
 
--- a/browser/base/content/test/general/browser_newTabDrop.js
+++ b/browser/base/content/test/general/browser_newTabDrop.js
@@ -1,30 +1,30 @@
 const ANY_URL = undefined;
 
 registerCleanupFunction(async function cleanup() {
   while (gBrowser.tabs.length > 1) {
     BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
   }
-  Services.search.defaultEngine = originalEngine;
+  await Services.search.setDefault(originalEngine);
   let engine = Services.search.getEngineByName("MozSearch");
-  Services.search.removeEngine(engine);
+  await Services.search.removeEngine(engine);
 });
 
 let originalEngine;
 add_task(async function test_setup() {
   // This test opens multiple tabs and some confirm dialogs, that takes long.
   requestLongerTimeout(2);
 
   // Stop search-engine loads from hitting the network
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+    "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 });
 
 // New Tab Button opens any link.
 add_task(async function single_url() {
   await dropText("mochi.test/first",
                  ["http://www.mochi.test/first"]);
 });
 add_task(async function single_javascript() {
--- a/browser/base/content/test/general/browser_newWindowDrop.js
+++ b/browser/base/content/test/general/browser_newWindowDrop.js
@@ -1,25 +1,25 @@
-registerCleanupFunction(function cleanup() {
-  Services.search.defaultEngine = originalEngine;
+registerCleanupFunction(async function cleanup() {
+  await Services.search.setDefault(originalEngine);
   let engine = Services.search.getEngineByName("MozSearch");
-  Services.search.removeEngine(engine);
+  await Services.search.removeEngine(engine);
 });
 
 let originalEngine;
 add_task(async function test_setup() {
   // Opening multiple windows on debug build takes too long time.
   requestLongerTimeout(10);
 
   // Stop search-engine loads from hitting the network
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+    "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 
   // Move New Window button to nav bar, to make it possible to drag and drop.
   let {CustomizableUI} = ChromeUtils.import("resource:///modules/CustomizableUI.jsm");
   let origPlacement = CustomizableUI.getPlacementOfWidget("new-window-button");
   if (!origPlacement || origPlacement.area != CustomizableUI.AREA_NAVBAR) {
     CustomizableUI.addWidgetToArea("new-window-button",
                                    CustomizableUI.AREA_NAVBAR,
                                    0);
--- a/browser/base/content/test/general/browser_tabDrop.js
+++ b/browser/base/content/test/general/browser_tabDrop.js
@@ -1,27 +1,27 @@
 const ANY_URL = undefined;
 
 registerCleanupFunction(async function cleanup() {
   while (gBrowser.tabs.length > 1) {
     BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
   }
-  Services.search.defaultEngine = originalEngine;
+  await Services.search.setDefault(originalEngine);
   let engine = Services.search.getEngineByName("MozSearch");
-  Services.search.removeEngine(engine);
+  await Services.search.removeEngine(engine);
 });
 
 let originalEngine;
 add_task(async function test_setup() {
   // Stop search-engine loads from hitting the network
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+    "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 });
 
 add_task(async function single_url() {
   await dropText("mochi.test/first",
                  ["http://www.mochi.test/first"]);
 });
 add_task(async function single_javascript() {
   await dropText("javascript:'bad'", []);
--- a/browser/base/content/test/pageActions/browser_page_action_menu_add_search_engine.js
+++ b/browser/base/content/test/pageActions/browser_page_action_menu_add_search_engine.js
@@ -61,17 +61,17 @@ add_task(async function one() {
     action = actions.find(a => a.id == "addSearchEngine");
     Assert.ok(!action, "Action should not be present in panel");
     button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
     Assert.ok(!button, "Action button should not be in panel");
 
     // Remove the engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_0");
-    Services.search.removeEngine(engine);
+    await Services.search.removeEngine(engine);
     await enginePromise;
 
     // Open the panel again.
     await promisePageActionPanelOpen();
     EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
     await promisePageActionPanelHidden();
 
     // The action should be present again.
@@ -202,17 +202,17 @@ add_task(async function many() {
     action = actions.find(a => a.id == "addSearchEngine");
     Assert.ok(!action, "Action should be gone");
     button = BrowserPageActions.panelButtonNodeForActionID("addSearchEngine");
     Assert.ok(!button, "Button should not be in panel");
 
     // Remove the first engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_0");
-    Services.search.removeEngine(engines.shift());
+    await Services.search.removeEngine(engines.shift());
     await enginePromise;
 
     // Open the panel again.  The action should be present and showing the first
     // engine.
     await promisePageActionPanelOpen();
     EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
     await promisePageActionPanelHidden();
     actions = PageActions.actionsInPanel(window);
@@ -224,17 +224,17 @@ add_task(async function many() {
     Assert.ok(button, "Button should be present in panel");
     Assert.equal(button.label, expectedTitle, "Button label");
     Assert.equal(button.classList.contains("subviewbutton-nav"), false,
                  "Button should not expand into a subview");
 
     // Remove the second engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_1");
-    Services.search.removeEngine(engines.shift());
+    await Services.search.removeEngine(engines.shift());
     await enginePromise;
 
     // Open the panel again and check the subview.  The subview should be
     // present now that there are two offered engines again.
     await promisePageActionPanelOpen();
     actions = PageActions.actionsInPanel(window);
     action = actions.find(a => a.id == "addSearchEngine");
     Assert.ok(action, "Action should be present in panel");
@@ -258,17 +258,17 @@ add_task(async function many() {
       "Subview children"
     );
     EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
     await promisePageActionPanelHidden();
 
     // Remove the third engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_2");
-    Services.search.removeEngine(engines.shift());
+    await Services.search.removeEngine(engines.shift());
     await enginePromise;
 
     // Open the panel again and check the subview.
     await promisePageActionPanelOpen();
     viewPromise = promisePageActionViewShown();
     EventUtils.synthesizeMouseAtCenter(button, {});
     await viewPromise;
     Assert.deepEqual(
@@ -318,17 +318,17 @@ add_task(async function urlbarOne() {
     Assert.ok(!action, "Action should not be present in urlbar");
     button = BrowserPageActions.urlbarButtonNodeForActionID("addSearchEngine");
     Assert.ok(!button, "Action button should not be in urlbar");
 
     // Remove the engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_0");
     placedPromise = promisePlacedInUrlbar();
-    Services.search.removeEngine(engine);
+    await Services.search.removeEngine(engine);
     await enginePromise;
 
     // The action should be present again.
     button = await placedPromise;
     actions = PageActions.actionsInUrlbar(window);
     action = actions.find(a => a.id == "addSearchEngine");
     Assert.ok(action, "Action should be present in urlbar");
     Assert.ok(button, "Action button should be in urlbar");
@@ -434,30 +434,30 @@ add_task(async function urlbarMany() {
     Assert.ok(!action, "Action should be gone");
     button = BrowserPageActions.urlbarButtonNodeForActionID("addSearchEngine");
     Assert.ok(!button, "Button should not be in urlbar");
 
     // Remove the first engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_0");
     placedPromise = promisePlacedInUrlbar();
-    Services.search.removeEngine(engines.shift());
+    await Services.search.removeEngine(engines.shift());
     await enginePromise;
 
     // The action should be placed again.
     button = await placedPromise;
     actions = PageActions.actionsInUrlbar(window);
     action = actions.find(a => a.id == "addSearchEngine");
     Assert.ok(action, "Action should be present in urlbar");
     Assert.ok(button, "Button should be in urlbar");
 
     // Remove the second engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_1");
-    Services.search.removeEngine(engines.shift());
+    await Services.search.removeEngine(engines.shift());
     await enginePromise;
 
     // Open the panel again and check the subview.  The subview should be
     // present now that there are two offered engines again.
     EventUtils.synthesizeMouseAtCenter(button, {});
     view = await waitForActivatedActionPanel();
     body = view.firstElementChild;
     Assert.deepEqual(
@@ -473,17 +473,17 @@ add_task(async function urlbarMany() {
     hiddenPromise =
       promisePanelHidden(BrowserPageActions.activatedActionPanelNode);
     EventUtils.synthesizeMouseAtCenter(button, {});
     await hiddenPromise;
 
     // Remove the third engine.
     enginePromise =
       promiseEngine("engine-removed", "page_action_menu_add_search_engine_2");
-    Services.search.removeEngine(engines.shift());
+    await Services.search.removeEngine(engines.shift());
     await enginePromise;
 
     // Open the panel again and check the subview.
     EventUtils.synthesizeMouseAtCenter(button, {});
     view = await waitForActivatedActionPanel();
     body = view.firstElementChild;
     Assert.deepEqual(
       Array.map(body.children, n => n.label),
--- a/browser/base/content/test/performance/browser_urlbar_keyed_search.js
+++ b/browser/base/content/test/performance/browser_urlbar_keyed_search.js
@@ -11,31 +11,33 @@ requestLongerTimeout(5);
  * be modifying your code to avoid the reflow.
  *
  * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
  * for tips on how to do that.
  */
 
 /* These reflows happen only the first time the awesomebar panel opens. */
 const EXPECTED_REFLOWS_FIRST_OPEN = [];
-if (AppConstants.DEBUG ||
-    AppConstants.platform == "win" ||
-    AppConstants.platform == "macosx") {
+if (AppConstants.platform != "macosx" &&
+    (AppConstants.DEBUG ||
+     AppConstants.platform == "win")) {
   EXPECTED_REFLOWS_FIRST_OPEN.push({
     stack: [
       "_rebuild@chrome://browser/content/search/search-one-offs.js",
-      "set popup@chrome://browser/content/search/search-one-offs.js",
+      /* This is limited to a one-line stack, because the next item is an async
+         function and as such not supported on all trees, according to bug 1501761.
+      "async*set popup@chrome://browser/content/search/search-one-offs.js",
       "_syncOneOffSearchesEnabled@chrome://browser/content/urlbarBindings.xml",
       "toggleOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "@chrome://browser/content/urlbarBindings.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
-      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",*/
     ],
   });
 }
 EXPECTED_REFLOWS_FIRST_OPEN.push(
   {
     stack: [
       "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
       "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
--- a/browser/base/content/test/performance/browser_urlbar_search.js
+++ b/browser/base/content/test/performance/browser_urlbar_search.js
@@ -11,32 +11,34 @@ requestLongerTimeout(5);
  * be modifying your code to avoid the reflow.
  *
  * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
  * for tips on how to do that.
  */
 
 /* These reflows happen only the first time the awesomebar panel opens. */
 const EXPECTED_REFLOWS_FIRST_OPEN = [];
-if (AppConstants.DEBUG ||
-    AppConstants.platform == "linux" ||
-    AppConstants.platform == "macosx" ||
-    AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+if (AppConstants.platform != "macosx" &&
+    (AppConstants.DEBUG ||
+     AppConstants.platform == "linux" ||
+     AppConstants.isPlatformAndVersionAtLeast("win", "10"))) {
   EXPECTED_REFLOWS_FIRST_OPEN.push({
     stack: [
       "_rebuild@chrome://browser/content/search/search-one-offs.js",
-      "set popup@chrome://browser/content/search/search-one-offs.js",
+      /* This is limited to a one-line stack, because the next item is an async
+         function and as such not supported on all trees, according to bug 1501761.
+      "async*set popup@chrome://browser/content/search/search-one-offs.js",
       "_syncOneOffSearchesEnabled@chrome://browser/content/urlbarBindings.xml",
       "toggleOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "_enableOrDisableOneOffSearches@chrome://browser/content/urlbarBindings.xml",
       "@chrome://browser/content/urlbarBindings.xml",
       "_openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openAutocompletePopup@chrome://browser/content/urlbarBindings.xml",
       "openPopup@chrome://global/content/bindings/autocomplete.xml",
-      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",
+      "set_popupOpen@chrome://global/content/bindings/autocomplete.xml",*/
     ],
   });
 }
 EXPECTED_REFLOWS_FIRST_OPEN.push(
   {
     stack: [
       "_handleOverflow@chrome://global/content/elements/autocomplete-richlistitem.js",
       "handleOverUnderflow@chrome://global/content/elements/autocomplete-richlistitem.js",
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -801,76 +801,76 @@ var Policies = {
 
   "SearchEngines": {
     onBeforeUIStartup(manager, param) {
       if (param.PreventInstalls) {
         manager.disallowFeature("installSearchEngine", true);
       }
     },
     onAllWindowsRestored(manager, param) {
-      Services.search.init(() => {
+      Services.search.init().then(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/enterprisepolicies/tests/browser/browser_policy_search_engine.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js
@@ -46,17 +46,17 @@ async function test_opensearch(shouldWor
        "Search bar should not have addengines attribute");
   }
   await BrowserTestUtils.removeTab(tab);
 }
 
 add_task(async function test_install_and_set_default() {
   // Make sure we are starting in an expected state to avoid false positive
   // test results.
-  isnot(Services.search.defaultEngine.name, "MozSearch",
+  isnot((await Services.search.getDefault()).name, "MozSearch",
         "Default search engine should not be MozSearch when test starts");
   is(Services.search.getEngineByName("Foo"), null,
      "Engine \"Foo\" should not be present when test starts");
 
   await setupPolicyEngineWithJson({
     "policies": {
       "SearchEngines": {
         "Add": [
@@ -64,31 +64,33 @@ add_task(async function test_install_and
             "Name": "MozSearch",
             "URLTemplate": "http://example.com/?q={searchTerms}",
           },
         ],
         "Default": "MozSearch",
       },
     },
   });
+  // Get in line, because the Search policy callbacks are async.
+  await TestUtils.waitForTick();
 
   // If this passes, it means that the new search engine was properly installed
   // *and* was properly set as the default.
-  is(Services.search.defaultEngine.name, "MozSearch",
+  is((await Services.search.getDefault()).name, "MozSearch",
      "Specified search engine should be the default");
 
   // Clean up
-  Services.search.removeEngine(Services.search.defaultEngine);
+  await Services.search.removeEngine(await Services.search.getDefault());
   EnterprisePolicyTesting.resetRunOnceState();
 });
 
 // Same as the last test, but with "PreventInstalls" set to true to make sure
 // it does not prevent search engines from being installed properly
 add_task(async function test_install_and_set_default_prevent_installs() {
-  isnot(Services.search.defaultEngine.name, "MozSearch",
+  isnot((await Services.search.getDefault()).name, "MozSearch",
         "Default search engine should not be MozSearch when test starts");
   is(Services.search.getEngineByName("Foo"), null,
      "Engine \"Foo\" should not be present when test starts");
 
   await setupPolicyEngineWithJson({
     "policies": {
       "SearchEngines": {
         "Add": [
@@ -97,22 +99,24 @@ add_task(async function test_install_and
             "URLTemplate": "http://example.com/?q={searchTerms}",
           },
         ],
         "Default": "MozSearch",
         "PreventInstalls": true,
       },
     },
   });
+  // Get in line, because the Search policy callbacks are async.
+  await TestUtils.waitForTick();
 
-  is(Services.search.defaultEngine.name, "MozSearch",
+  is((await Services.search.getDefault()).name, "MozSearch",
      "Specified search engine should be the default");
 
   // Clean up
-  Services.search.removeEngine(Services.search.defaultEngine);
+  await Services.search.removeEngine(await Services.search.getDefault());
   EnterprisePolicyTesting.resetRunOnceState();
 });
 
 add_task(async function test_opensearch_works() {
   // Clear out policies so we can test with no policies applied
   await setupPolicyEngineWithJson({
     "policies": {
     },
@@ -208,16 +212,18 @@ add_task(async function test_install_and
             "Name": "Foo",
             "URLTemplate": "http://example.com/?q={searchTerms}",
             "IconURL": iconURL,
           },
         ],
       },
     },
   });
+  // Get in line, because the Search policy callbacks are async.
+  await TestUtils.waitForTick();
 
   // If this passes, it means that the new search engine was properly installed
 
   let engine = Services.search.getEngineByName("Foo");
   isnot(engine, null,
      "Specified search engine should be installed");
 
   is(engine.wrappedJSObject.iconURI.spec, iconURL,
@@ -225,15 +231,17 @@ add_task(async function test_install_and
 
   await setupPolicyEngineWithJson({
   "policies": {
       "SearchEngines": {
         "Remove": ["Foo"],
       },
     },
   });
+  // Get in line, because the Search policy callbacks are async.
+  await TestUtils.waitForTick();
 
   // If this passes, it means that the specified engine was properly removed
   is(Services.search.getEngineByName("Foo"), null,
      "Specified search engine should not be installed");
 
   EnterprisePolicyTesting.resetRunOnceState();
 });
--- a/browser/components/enterprisepolicies/tests/browser/head.js
+++ b/browser/components/enterprisepolicies/tests/browser/head.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   EnterprisePolicyTesting,
   PoliciesPrefTracker,
 } = ChromeUtils.import("resource://testing-common/EnterprisePolicyTesting.jsm", null);
+const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm", null);
 
 PoliciesPrefTracker.start();
 
 async function setupPolicyEngineWithJson(json, customSchema) {
   PoliciesPrefTracker.restoreDefaultValues();
   if (typeof(json) != "object") {
     let filePath = getTestFilePath(json ? json : "non-existing-file.json");
     return EnterprisePolicyTesting.setupPolicyEngineWithJson(filePath, customSchema);
--- 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,44 +199,46 @@ 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 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(allow) {
                   if (allow) {
                     ExtensionSettingsStore.addSetting(
-                      extension.id, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => Services.search.defaultEngine.name);
+                      extension.id, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => defaultEngine.name);
                     Services.search.defaultEngine = Services.search.getEngineByName(engineName);
                   }
                 },
               },
             };
             Services.obs.notifyObservers(subject, "webextension-defaultsearch-prompt");
           }
         } else {
@@ -251,53 +253,56 @@ 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, () => Services.search.defaultEngine.name);
-      Services.search.defaultEngine = Services.search.getEngineByName(item.value);
+        extension.id, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => defaultEngine.name);
+      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/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -67,16 +67,17 @@ skip-if = debug # Bug 1522164
 [browser_ext_browserAction_simple.js]
 [browser_ext_browserAction_telemetry.js]
 [browser_ext_browserAction_theme_icons.js]
 [browser_ext_browsingData_formData.js]
 [browser_ext_browsingData_history.js]
 [browser_ext_browsingData_indexedDB.js]
 [browser_ext_browsingData_localStorage.js]
 [browser_ext_browsingData_pluginData.js]
+skip-if = (os == 'mac' && debug) # Plugin test causes Mac window to lose active state and times out other tests.
 [browser_ext_browsingData_serviceWorkers.js]
 [browser_ext_chrome_settings_overrides_home.js]
 [browser_ext_commands_execute_browser_action.js]
 [browser_ext_commands_execute_page_action.js]
 skip-if = (verify && (os == 'linux' || os == 'mac'))
 [browser_ext_commands_execute_sidebar_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -100,31 +100,34 @@ add_task(async function browseraction_po
   ok(!item.hidden);
   ok(item.disabled);
 
   await closeContextMenu(contentAreaContextMenu);
 
   await extension.unload();
 });
 
-function openContextMenu(menuId, targetId) {
-  return openChromeContextMenu(menuId, "#" + CSS.escape(targetId));
+function openContextMenu(menuId, targetId, win = window) {
+  return openChromeContextMenu(menuId, "#" + CSS.escape(targetId), win);
 }
 
 function waitForElementShown(element) {
   let win = element.ownerGlobal;
   let dwu = win.windowUtils;
   return BrowserTestUtils.waitForCondition(() => {
     info("Waiting for overflow button to have non-0 size");
     let bounds = dwu.getBoundsWithoutFlushing(element);
     return bounds.width > 0 && bounds.height > 0;
   });
 }
 
 add_task(async function browseraction_contextmenu_manage_extension() {
+  // Do the customize mode shuffle in a separate window, because it interferes
+  // with other tests.
+  let win = await BrowserTestUtils.openNewBrowserWindow();
   let id = "addon_id@example.com";
   let buttonId = `${makeWidgetId(id)}-browser-action`;
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "applications": {
         "gecko": {id},
       },
       "browser_action": {},
@@ -148,115 +151,120 @@ add_task(async function browseraction_co
     let expected = visible ? "visible" : "hidden";
     is(removeExtension.hidden, !visible, `Remove Extension should be ${expected}`);
     is(manageExtension.hidden, !visible, `Manage Extension should be ${expected}`);
     is(separator.hidden, !visible, `Separator after Manage Extension should be ${expected}`);
   }
 
   async function testContextMenu(menuId, customizing) {
     info(`Open browserAction context menu in ${menuId}`);
-    let menu = await openContextMenu(menuId, buttonId);
+    let menu = await openContextMenu(menuId, buttonId, win);
     await checkVisibility(menu, true);
 
     info(`Choosing 'Manage Extension' in ${menuId} should load options`);
     let optionsLoaded = extension.awaitMessage("options-loaded");
     let manageExtension = menu.querySelector(".customize-context-manageExtension");
-    await closeChromeContextMenu(menuId, manageExtension);
+    await closeChromeContextMenu(menuId, manageExtension, win);
     await optionsLoaded;
 
     info(`Remove the opened tab, and await customize mode to be restored if necessary`);
-    let tab = gBrowser.selectedTab;
+    let tab = win.gBrowser.selectedTab;
     is(tab.linkedBrowser.currentURI.spec, "about:addons");
     if (customizing) {
-      let customizationReady = BrowserTestUtils.waitForEvent(gNavToolbox, "customizationready");
-      gBrowser.removeTab(tab);
+      let customizationReady = BrowserTestUtils.waitForEvent(win.gNavToolbox, "customizationready");
+      win.gBrowser.removeTab(tab);
       await customizationReady;
     } else {
-      gBrowser.removeTab(tab);
+      win.gBrowser.removeTab(tab);
     }
 
     return menu;
   }
 
   async function main(customizing) {
     if (customizing) {
       info("Enter customize mode");
-      let customizationReady = BrowserTestUtils.waitForEvent(gNavToolbox, "customizationready");
-      gCustomizeMode.enter();
+      let customizationReady = BrowserTestUtils.waitForEvent(win.gNavToolbox, "customizationready");
+      win.gCustomizeMode.enter();
       await customizationReady;
     }
 
     info("Test toolbar context menu in browserAction");
     let toolbarCtxMenu = await testContextMenu("toolbar-context-menu", customizing);
 
     info("Check toolbar context menu in another button");
     let otherButtonId = "home-button";
-    await openContextMenu(toolbarCtxMenu.id, otherButtonId);
+    await openContextMenu(toolbarCtxMenu.id, otherButtonId, win);
     checkVisibility(toolbarCtxMenu, false);
     toolbarCtxMenu.hidePopup();
 
     info("Check toolbar context menu without triggerNode");
     toolbarCtxMenu.openPopup();
     checkVisibility(toolbarCtxMenu, false);
     toolbarCtxMenu.hidePopup();
 
     info("Pin the browserAction and another button to the overflow menu");
     CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
     CustomizableUI.addWidgetToArea(otherButtonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
 
     info("Wait until the overflow menu is ready");
-    let overflowButton = document.getElementById("nav-bar-overflow-button");
-    let icon = document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
+    let overflowButton = win.document.getElementById("nav-bar-overflow-button");
+    let icon = win.document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
     await waitForElementShown(icon);
 
     if (!customizing) {
       info("Open overflow menu");
-      let menu = document.getElementById("widget-overflow");
+      let menu = win.document.getElementById("widget-overflow");
       let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
       overflowButton.click();
       await shown;
     }
 
     info("Check overflow menu context menu in another button");
-    let overflowMenuCtxMenu = await openContextMenu("customizationPanelItemContextMenu", otherButtonId);
+    let overflowMenuCtxMenu = await openContextMenu("customizationPanelItemContextMenu", otherButtonId, win);
     checkVisibility(overflowMenuCtxMenu, false);
     overflowMenuCtxMenu.hidePopup();
 
     info("Test overflow menu context menu in browserAction");
     await testContextMenu(overflowMenuCtxMenu.id, customizing);
 
     info("Restore initial state");
     CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR);
     CustomizableUI.addWidgetToArea(otherButtonId, CustomizableUI.AREA_NAVBAR);
 
     if (customizing) {
       info("Exit customize mode");
-      let afterCustomization = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization");
-      gCustomizeMode.exit();
+      let afterCustomization = BrowserTestUtils.waitForEvent(win.gNavToolbox, "aftercustomization");
+      win.gCustomizeMode.exit();
       await afterCustomization;
     }
   }
 
   await extension.startup();
 
   info("Add a dummy tab to prevent about:addons from being loaded in the initial about:blank tab");
-  let dummyTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com", true, true);
+  let dummyTab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://example.com", true, true);
 
   info("Run tests in normal mode");
   await main(false);
 
   info("Run tests in customize mode");
   await main(true);
 
   info("Close the dummy tab and finish");
-  gBrowser.removeTab(dummyTab);
+  win.gBrowser.removeTab(dummyTab);
   await extension.unload();
+
+  await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function browseraction_contextmenu_remove_extension() {
+  // Do the customize mode shuffle in a separate window, because it interferes
+  // with other tests.
+  let win = await BrowserTestUtils.openNewBrowserWindow();
   let id = "addon_id@example.com";
   let name = "Awesome Add-on";
   let buttonId = `${makeWidgetId(id)}-browser-action`;
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       name,
       "applications": {
         "gecko": {id},
@@ -278,64 +286,64 @@ add_task(async function browseraction_co
   };
   Services.prompt = promptService;
   registerCleanupFunction(() => {
     Services.prompt = prompt;
   });
 
   async function testContextMenu(menuId, customizing) {
     info(`Open browserAction context menu in ${menuId}`);
-    let menu = await openContextMenu(menuId, buttonId);
+    let menu = await openContextMenu(menuId, buttonId, win);
 
     info(`Choosing 'Remove Extension' in ${menuId} should show confirm dialog`);
     let removeExtension = menu.querySelector(".customize-context-removeExtension");
-    await closeChromeContextMenu(menuId, removeExtension);
+    await closeChromeContextMenu(menuId, removeExtension, win);
     is(promptService._confirmExArgs[1], `Remove ${name}`);
     is(promptService._confirmExArgs[2], `Remove ${name} from ${brand}?`);
     is(promptService._confirmExArgs[4], "Remove");
     return menu;
   }
 
   async function main(customizing) {
     if (customizing) {
       info("Enter customize mode");
-      let customizationReady = BrowserTestUtils.waitForEvent(gNavToolbox, "customizationready");
-      gCustomizeMode.enter();
+      let customizationReady = BrowserTestUtils.waitForEvent(win.gNavToolbox, "customizationready");
+      win.gCustomizeMode.enter();
       await customizationReady;
     }
 
     info("Test toolbar context menu in browserAction");
     await testContextMenu("toolbar-context-menu", customizing);
 
     info("Pin the browserAction and another button to the overflow menu");
     CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
 
     info("Wait until the overflow menu is ready");
-    let overflowButton = document.getElementById("nav-bar-overflow-button");
-    let icon = document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
+    let overflowButton = win.document.getElementById("nav-bar-overflow-button");
+    let icon = win.document.getAnonymousElementByAttribute(overflowButton, "class", "toolbarbutton-icon");
     await waitForElementShown(icon);
 
     if (!customizing) {
       info("Open overflow menu");
-      let menu = document.getElementById("widget-overflow");
+      let menu = win.document.getElementById("widget-overflow");
       let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
       overflowButton.click();
       await shown;
     }
 
     info("Test overflow menu context menu in browserAction");
     await testContextMenu("customizationPanelItemContextMenu", customizing);
 
     info("Restore initial state");
     CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_NAVBAR);
 
     if (customizing) {
       info("Exit customize mode");
-      let afterCustomization = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization");
-      gCustomizeMode.exit();
+      let afterCustomization = BrowserTestUtils.waitForEvent(win.gNavToolbox, "aftercustomization");
+      win.gCustomizeMode.exit();
       await afterCustomization;
     }
   }
 
   await extension.startup();
 
   info("Run tests in normal mode");
   await main(false);
@@ -358,9 +366,11 @@ add_task(async function browseraction_co
   });
   await testContextMenu("toolbar-context-menu", false);
   await uninstalled;
 
   addon = await AddonManager.getAddonByID(id);
   ok(!addon, "Addon has been uninstalled");
 
   await extension.unload();
+
+  await BrowserTestUtils.closeWindow(win);
 });
--- a/browser/components/extensions/test/browser/browser_ext_omnibox.js
+++ b/browser/components/extensions/test/browser/browser_ext_omnibox.js
@@ -295,16 +295,20 @@ add_task(async function() {
     test: "test-multiple-suggest-calls",
     suggestions,
   });
   await testSuggestions({
     test: "test-suggestions-after-delay",
     suggestions,
   });
 
+  // When we're the first task to be added, `waitForExplicitFinish()` may not have
+  // been called yet. Let's just do that, otherwise the `monitorConsole` will make
+  // the test fail with a failing assertion.
+  SimpleTest.waitForExplicitFinish();
   // Start monitoring the console.
   let waitForConsole = new Promise(resolve => {
     SimpleTest.monitorConsole(resolve, [{
       message: new RegExp(`The keyword provided is already registered: "${keyword}"`),
     }]);
   });
 
   // Try registering another extension with the same keyword
--- a/browser/components/extensions/test/browser/browser_ext_search.js
+++ b/browser/components/extensions/test/browser/browser_ext_search.js
@@ -54,21 +54,21 @@ add_task(async function test_search() {
       },
     },
     background: `(${background})("${SEARCH_TERM}")`,
     useAddonManager: "temporary",
   });
   await extension.startup();
 
   let addonEngines = await extension.awaitMessage("engines");
-  let engines = Services.search.getEngines().filter(engine => !engine.hidden);
+  let engines = (await Services.search.getEngines()).filter(engine => !engine.hidden);
   is(addonEngines.length, engines.length, "Engine lengths are the same.");
   let defaultEngine = addonEngines.filter(engine => engine.isDefault === true);
   is(defaultEngine.length, 1, "One default engine");
-  is(defaultEngine[0].name, Services.search.defaultEngine.name, "Default engine is correct");
+  is(defaultEngine[0].name, (await Services.search.getDefault()).name, "Default engine is correct");
 
   let url = await extension.awaitMessage("searchLoaded");
   is(url, SEARCH_URL.replace("{searchTerms}", SEARCH_TERM + "1"), "Loaded page matches search");
 
   url = await extension.awaitMessage("searchLoaded");
   is(url, SEARCH_URL.replace("{searchTerms}", SEARCH_TERM + "2"), "Loaded page matches search");
 
   await extension.unload();
--- a/browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
+++ b/browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
@@ -4,23 +4,27 @@
 "use strict";
 
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 
 const EXTENSION1_ID = "extension1@mozilla.com";
 const EXTENSION2_ID = "extension2@mozilla.com";
 
-let defaultEngineName = Services.search.defaultEngine.name;
+var defaultEngineName;
 
-function restoreDefaultEngine() {
+async function restoreDefaultEngine() {
   let engine = Services.search.getEngineByName(defaultEngineName);
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
 }
-registerCleanupFunction(restoreDefaultEngine);
+
+add_task(async function setup() {
+  defaultEngineName = (await Services.search.getDefault()).name;
+  registerCleanupFunction(restoreDefaultEngine);
+});
 
 /* This tests setting a default engine. */
 add_task(async function test_extension_setting_default_engine() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_settings_overrides": {
         "search_provider": {
           "name": "DuckDuckGo",
@@ -29,21 +33,21 @@ add_task(async function test_extension_s
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
 
 /* This tests that uninstalling add-ons maintains the proper
  * search default. */
 add_task(async function test_extension_setting_multiple_default_engine() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_settings_overrides": {
@@ -67,29 +71,29 @@ add_task(async function test_extension_s
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   await ext2.unload();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
 
 /* This tests that uninstalling add-ons in reverse order maintains the proper
  * search default. */
 add_task(async function test_extension_setting_multiple_default_engine_reversed() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_settings_overrides": {
@@ -113,29 +117,29 @@ add_task(async function test_extension_s
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   await ext2.unload();
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
 
 /* This tests that when the user changes the search engine and the add-on
  * is unistalled, search stays with the user's choice. */
 add_task(async function test_user_changing_default_engine() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       "chrome_settings_overrides": {
@@ -146,24 +150,24 @@ add_task(async function test_user_changi
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let engine = Services.search.getEngineByName("Twitter");
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
 
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
   restoreDefaultEngine();
 });
 
 /* This tests that when the user changes the search engine while it is
  * disabled, user choice is maintained when the add-on is reenabled. */
 add_task(async function test_user_change_with_disabling() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -180,37 +184,37 @@ add_task(async function test_user_change
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let engine = Services.search.getEngineByName("Twitter");
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
   let addon = await AddonManager.getAddonByID(EXTENSION1_ID);
   await addon.disable();
   await disabledPromise;
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
   await addon.enable();
   await enabledPromise;
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
   await ext1.unload();
-  restoreDefaultEngine();
+  await restoreDefaultEngine();
 });
 
 /* This tests that when two add-ons are installed that change default
  * search and the first one is disabled, before the second one is installed,
  * when the first one is reenabled, the second add-on keeps the search. */
 add_task(async function test_two_addons_with_first_disabled_before_second() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -245,40 +249,40 @@ add_task(async function test_two_addons_
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
   let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
   await addon1.disable();
   await disabledPromise;
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 
   await ext2.startup();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
   await addon1.enable();
   await enabledPromise;
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
   await ext2.unload();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
 
 /* This tests that when two add-ons are installed that change default
  * search and the first one is disabled, the second one maintains
  * the search. */
 add_task(async function test_two_addons_with_first_disabled() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -313,40 +317,40 @@ add_task(async function test_two_addons_
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
   let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
   await addon1.disable();
   await disabledPromise;
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
   await addon1.enable();
   await enabledPromise;
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
   await ext2.unload();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
 
 /* This tests that when two add-ons are installed that change default
  * search and the second one is disabled, the first one properly
  * gets the search. */
 add_task(async function test_two_addons_with_second_disabled() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -381,33 +385,33 @@ add_task(async function test_two_addons_
         },
       },
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   await ext2.startup();
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
 
   let disabledPromise = awaitEvent("shutdown", EXTENSION2_ID);
   let addon2 = await AddonManager.getAddonByID(EXTENSION2_ID);
   await addon2.disable();
   await disabledPromise;
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
 
   let enabledPromise = awaitEvent("ready", EXTENSION2_ID);
   await addon2.enable();
   await enabledPromise;
 
-  is(Services.search.defaultEngine.name, "Twitter", "Default engine is Twitter");
+  is((await Services.search.getDefault()).name, "Twitter", "Default engine is Twitter");
   await ext2.unload();
 
-  is(Services.search.defaultEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  is((await Services.search.getDefault()).name, "DuckDuckGo", "Default engine is DuckDuckGo");
   await ext1.unload();
 
-  is(Services.search.defaultEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+  is((await Services.search.getDefault()).name, defaultEngineName, `Default engine is ${defaultEngineName}`);
 });
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
@@ -33,44 +33,36 @@ async function addBookmark(bookmark) {
     title: bookmark.title,
   });
 
   registerCleanupFunction(async function() {
     await PlacesUtils.bookmarks.eraseEverything();
   });
 }
 
-function addSearchEngine(basename) {
-  return new Promise((resolve, reject) => {
-    info("Waiting for engine to be added: " + basename);
-    let url = getRootDirectory(gTestPath) + basename;
-    Services.search.addEngine(url, "", false, {
-      onSuccess: (engine) => {
-        info(`Search engine added: ${basename}`);
-        registerCleanupFunction(() => Services.search.removeEngine(engine));
-        resolve(engine);
-      },
-      onError: (errCode) => {
-        ok(false, `addEngine failed with error code ${errCode}`);
-        reject();
-      },
-    });
-  });
+async function addSearchEngine(basename) {
+  info("Waiting for engine to be added: " + basename);
+  let url = getRootDirectory(gTestPath) + basename;
+  let engine = await Services.search.addEngine(url, "", false);
+
+  info(`Search engine added: ${basename}`);
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
+  return engine;
 }
 
 async function prepareSearchEngine() {
-  let oldCurrentEngine = Services.search.defaultEngine;
+  let oldDefaultEngine = await Services.search.getDefault();
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
   let engine = await addSearchEngine(TEST_ENGINE_BASENAME);
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
 
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
-    Services.search.defaultEngine = oldCurrentEngine;
+    await Services.search.setDefault(oldDefaultEngine);
 
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     gURLBar.popup.selectedIndex = -1;
     gURLBar.popup.hidePopup();
     ok(!gURLBar.popup.popupOpen, "popup should be closed");
 
     // Clicking suggestions causes visits to search results pages, so clear that
--- a/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_chrome_settings_overrides_update.js
@@ -56,26 +56,26 @@ add_task(async function test_overrides_u
           "is_default": true,
         },
       },
     },
   };
   let extension = ExtensionTestUtils.loadExtension(extensionInfo);
 
   let defaultHomepageURL = HomePage.get();
-  let defaultEngineName = Services.search.defaultEngine.name;
+  let defaultEngineName = (await Services.search.getDefault()).name;
 
   let prefPromise = promisePrefChanged(HOMEPAGE_URI);
   await extension.startup();
   await prefPromise;
 
   equal(extension.version, "1.0", "The installed addon has the expected version.");
   ok(HomePage.get().endsWith(HOMEPAGE_URI),
      "Home page url is overridden by the extension.");
-  equal(Services.search.defaultEngine.name,
+  equal((await Services.search.getDefault()).name,
         "DuckDuckGo",
         "Default engine is overridden by the extension");
 
   extensionInfo.manifest = {
     "version": "2.0",
     "applications": {
       "gecko": {
         "id": EXTENSION_ID,
@@ -86,16 +86,16 @@ add_task(async function test_overrides_u
   prefPromise = promisePrefChanged(defaultHomepageURL);
   await extension.upgrade(extensionInfo);
   await prefPromise;
 
   equal(extension.version, "2.0", "The updated addon has the expected version.");
   equal(HomePage.get(),
         defaultHomepageURL,
         "Home page url reverted to the default after update.");
-  equal(Services.search.defaultEngine.name,
+  equal((await Services.search.getDefault()).name,
         defaultEngineName,
         "Default engine reverted to the default after update.");
 
   await extension.unload();
 
   await promiseShutdownManager();
 });
--- a/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search.js
@@ -14,17 +14,17 @@ const kSearchTerm = "foo";
 const kSearchTermIntl = "日";
 const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
 
 AddonTestUtils.init(this);
 AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42", "42");
 
 add_task(async function setup() {
   await AddonTestUtils.promiseStartupManager();
-  Services.search.init();
+  await Services.search.init();
 });
 
 add_task(async function test_extension_adding_engine() {
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       "icons": {
         "16": "foo.ico",
         "32": "foo32.ico",
@@ -113,18 +113,18 @@ add_task(async function test_upgrade_def
       "version": "0.1",
     },
     useAddonManager: "temporary",
   });
 
   await ext1.startup();
 
   let engine = Services.search.getEngineByName("MozSearch");
-  Services.search.defaultEngine = engine;
-  Services.search.moveEngine(engine, 1);
+  await Services.search.setDefault(engine);
+  await Services.search.moveEngine(engine, 1);
 
   await ext1.upgrade({
     manifest: {
       "chrome_settings_overrides": {
         "search_provider": {
           "name": "MozSearch",
           "keyword": "MozSearch",
           "search_url": "https://example.com/?q={searchTerms}",
@@ -137,17 +137,18 @@ add_task(async function test_upgrade_def
       },
       "version": "0.2",
     },
     useAddonManager: "temporary",
   });
 
   engine = Services.search.getEngineByName("MozSearch");
   equal(Services.search.defaultEngine, engine, "Default engine should still be MozSearch");
-  equal(Services.search.getEngines().indexOf(engine), 1, "Engine is in position 1");
+  equal((await Services.search.getEngines()).map(e => e.name).indexOf(engine.name),
+        1, "Engine is in position 1");
 
   await ext1.unload();
   await delay();
 
   engine = Services.search.getEngineByName("MozSearch");
   ok(!engine, "Engine should not exist");
 });
 
--- a/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search_mozParam.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_search_mozParam.js
@@ -11,17 +11,17 @@ AddonTestUtils.createAppInfo("xpcshell@t
 
 let {
   promiseShutdownManager,
   promiseStartupManager,
 } = AddonTestUtils;
 
 add_task(async function setup() {
   await promiseStartupManager();
-  Services.search.init();
+  await Services.search.init();
   registerCleanupFunction(async () => {
     await promiseShutdownManager();
   });
 });
 
 /* This tests setting moz params. */
 add_task(async function test_extension_setting_moz_params() {
   let defaultBranch = Services.prefs.getDefaultBranch("browser.search.");
--- 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,89 +175,88 @@ 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;
   }
 
   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));
+    await Services.search.init();
 
-    // 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/newtab/test/browser/browser_asrouter_targeting.js
+++ b/browser/components/newtab/test/browser/browser_asrouter_targeting.js
@@ -183,34 +183,34 @@ add_task(async function check_needsUpdat
   QueryCache.queries.CheckBrowserNeedsUpdate.setUp(false);
 
   is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), null,
     "Should not select message because update count == 0");
 });
 
 add_task(async function checksearchEngines() {
   const result = await ASRouterTargeting.Environment.searchEngines;
-  const expectedInstalled = Services.search.getVisibleEngines()
+  const expectedInstalled = (await Services.search.getVisibleEngines())
     .map(engine => engine.identifier)
     .sort()
     .join(",");
   ok(result.installed.length,
     "searchEngines.installed should be a non-empty array");
   is(result.installed.sort().join(","), expectedInstalled,
     "searchEngines.installed should be an array of visible search engines");
   ok(result.current && typeof result.current === "string",
     "searchEngines.current should be a truthy string");
-  is(result.current, Services.search.defaultEngine.identifier,
+  is(result.current, (await Services.search.getDefault()).identifier,
     "searchEngines.current should be the current engine name");
 
-  const message = {id: "foo", targeting: `searchEngines[.current == ${Services.search.defaultEngine.identifier}]`};
+  const message = {id: "foo", targeting: `searchEngines[.current == ${(await Services.search.getDefault()).identifier}]`};
   is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
     "should select correct item by searchEngines.current");
 
-  const message2 = {id: "foo", targeting: `searchEngines[${Services.search.getVisibleEngines()[0].identifier} in .installed]`};
+  const message2 = {id: "foo", targeting: `searchEngines[${(await Services.search.getVisibleEngines())[0].identifier} in .installed]`};
   is(await ASRouterTargeting.findMatchingMessage({messages: [message2]}), message2,
     "should select correct item by searchEngines.installed");
 });
 
 add_task(async function checkisDefaultBrowser() {
   const expected = ShellService.isDefaultBrowser();
   const result = ASRouterTargeting.Environment.isDefaultBrowser;
   is(typeof result, "boolean",
--- a/browser/components/newtab/test/unit/lib/TopSitesFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/TopSitesFeed.test.js
@@ -1119,17 +1119,17 @@ describe("Top Sites Feed", () => {
       assert.equal(feed.store.dispatch.firstCall.args[0].data.links[0].url, url);
       assert.equal(feed.store.dispatch.secondCall.args[0].data.links[0].url, FAKE_LINKS[0].url);
     });
   });
 
   describe("improvesearch.noDefaultSearchTile experiment", () => {
     const NO_DEFAULT_SEARCH_TILE_PREF = "improvesearch.noDefaultSearchTile";
     beforeEach(() => {
-      sandbox.stub(global.Services.search, "defaultEngine").value({identifier: "google", searchForm: "google.com"});
+      global.Services.search.getDefault = async () => {identifier: "google", searchForm: "google.com"};
       feed.store.state.Prefs.values[NO_DEFAULT_SEARCH_TILE_PREF] = true;
     });
     it("should filter out alexa top 5 search from the default sites", async () => {
       const TOP_5_TEST = [
         "google.com",
         "search.yahoo.com",
         "yahoo.com",
         "bing.com",
@@ -1157,17 +1157,17 @@ describe("Top Sites Feed", () => {
     it("should not filter out current default search from pinned sites even if it matches the current default search", async () => {
       links = [{url: "foo.com"}];
       fakeNewTabUtils.pinnedLinks.links = [{url: "google.com"}];
       const urlsReturned = (await feed.getLinksWithDefaults()).map(link => link.url);
       assert.include(urlsReturned, "google.com");
     });
     it("should call refresh and set ._currentSearchHostname to the new engine hostname when the the default search engine has been set", () => {
       sinon.stub(feed, "refresh");
-      sandbox.stub(global.Services.search, "defaultEngine").value({identifier: "ddg", searchForm: "duckduckgo.com"});
+      global.Services.search.getDefault = async () => {identifier: "ddg", searchForm: "duckduckgo.com"};
       feed.observe(null, "browser-search-engine-modified", "engine-current");
       assert.equal(feed._currentSearchHostname, "duckduckgo");
       assert.calledOnce(feed.refresh);
     });
     it("should call refresh when the experiment pref has changed", () => {
       sinon.stub(feed, "refresh");
 
       feed.onAction({type: at.PREF_CHANGED, data: {name: NO_DEFAULT_SEARCH_TILE_PREF, value: true}});
@@ -1182,17 +1182,17 @@ describe("Top Sites Feed", () => {
     beforeEach(() => {
       feed.store.state.Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT_PREF] = true;
       feed.store.state.Prefs.values[SEARCH_SHORTCUTS_SEARCH_ENGINES_PREF] = "google,amazon";
       feed.store.state.Prefs.values[SEARCH_SHORTCUTS_HAVE_PINNED_PREF] = "";
       const searchEngines = [
         {wrappedJSObject: {_internalAliases: ["@google"]}},
         {wrappedJSObject: {_internalAliases: ["@amazon"]}},
       ];
-      global.Services.search.getDefaultEngines = () => searchEngines;
+      global.Services.search.getDefaultEngines = async () => searchEngines;
       fakeNewTabUtils.pinnedLinks.pin = sinon.stub().callsFake((site, index) => {
         fakeNewTabUtils.pinnedLinks.links[index] = site;
       });
     });
 
     it("should properly disable search improvements if the pref is off", async () => {
       sandbox.stub(global.Services.prefs, "clearUserPref");
       sandbox.spy(feed.pinnedCache, "expire");
@@ -1362,17 +1362,17 @@ describe("Top Sites Feed", () => {
         fakeNewTabUtils.pinnedLinks.links[6] = {url: ""};
         await feed._maybeInsertSearchShortcuts(fakeNewTabUtils.pinnedLinks.links);
         assert.notOk(fakeNewTabUtils.pinnedLinks.links.find(s => s && s.url === "https://google.com"));
         assert.notOk(fakeNewTabUtils.pinnedLinks.links.find(s => s && s.url === "https://amazon.com"));
       });
 
       it("should not pin a shortcut if the corresponding search engine is not available", async () => {
         // Make Amazon search engine unavailable
-        global.Services.search.getDefaultEngines = () => [{wrappedJSObject: {_internalAliases: ["@google"]}}];
+        global.Services.search.getDefaultEngines = async () => [{wrappedJSObject: {_internalAliases: ["@google"]}}];
         fakeNewTabUtils.pinnedLinks.links.fill(null);
         await feed._maybeInsertSearchShortcuts(fakeNewTabUtils.pinnedLinks.links);
         assert.notOk(fakeNewTabUtils.pinnedLinks.links.find(s => s && s.url === "https://amazon.com"));
       });
 
       it("should not pin a search shortcut if it's been pinned before", async () => {
         fakeNewTabUtils.pinnedLinks.links.fill(null);
         feed.store.state.Prefs.values[SEARCH_SHORTCUTS_HAVE_PINNED_PREF] = "google,amazon";
--- a/browser/components/newtab/test/unit/unit-entry.js
+++ b/browser/components/newtab/test/unit/unit-entry.js
@@ -227,17 +227,16 @@ const TEST_GLOBAL = {
       getVisibleEngines: () => [{identifier: "google"}, {identifier: "bing"}],
       defaultEngine: {
         identifier: "google",
         searchForm: "https://www.google.com/search?q=&ie=utf-8&oe=utf-8&client=firefox-b",
         wrappedJSObject: {
           __internalAliases: ["@google"],
         },
       },
-      currentEngine: {identifier: "google", searchForm: "https://www.google.com/search?q=&ie=utf-8&oe=utf-8&client=firefox-b"},
     },
     scriptSecurityManager: {
       createNullPrincipal() {},
       getSystemPrincipal() {},
     },
     wm: {getMostRecentWindow: () => window, getEnumerator: () => []},
     ww: {registerNotification() {}, unregisterNotification() {}},
     appinfo: {appBuildID: "20180710100040"},
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -139,24 +139,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");
@@ -384,19 +386,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");
   let cell = tree.getCellAt(event.clientX, event.clientY);
@@ -406,22 +407,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;
   },
@@ -459,23 +469,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;
@@ -487,36 +497,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;
   },
@@ -557,17 +567,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/preferences/in-content/tests/browser_engines.js
+++ b/browser/components/preferences/in-content/tests/browser_engines.js
@@ -3,17 +3,17 @@ add_task(async function() {
   let prefs = await openPreferencesViaOpenPreferencesAPI("search", {leaveOpen: true});
   is(prefs.selectedPane, "paneSearch", "Search pane is selected by default");
   let doc = gBrowser.contentDocument;
 
   let tree = doc.querySelector("#engineList");
   ok(!tree.hidden, "The search engine list should be visible when Search is requested");
 
   // Check for default search engines to be displayed in the engineList
-  let defaultEngines = Services.search.getDefaultEngines();
+  let defaultEngines = await Services.search.getDefaultEngines();
   for (let i = 0; i < defaultEngines.length; i++) {
       let engine = defaultEngines[i];
       let cellName = tree.view.getCellText(i, tree.columns.getNamedColumn("engineName"));
       is(cellName, engine.name, "Default search engine " + engine.name + " displayed correctly");
   }
 
   // Avoid duplicated keywords
   tree.view.setCellText(0, tree.columns.getNamedColumn("engineKeyword"), "keyword");
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -437,16 +437,20 @@ add_task(async function testExtensionCon
       search_provider: {
         name: "DuckDuckGo",
         search_url: "https://duckduckgo.com/?q={searchTerms}",
         is_default: true,
       },
     },
   };
 
+  // This test is comparing nsISearchEngines by reference, so we need to initialize
+  // the SearchService here.
+  await Services.search.init();
+
   function setEngine(engine) {
     doc.querySelector(`#defaultEngine menuitem[label="${engine.name}"]`)
        .doCommand();
   }
 
   is(gBrowser.currentURI.spec, "about:preferences#search",
      "#search should be in the URI for about:preferences");
 
@@ -486,16 +490,18 @@ add_task(async function testExtensionCon
   await waitForMessageHidden(controlledContent.id);
 
   is(initialEngine, Services.search.defaultEngine,
      "default search engine is set back to default");
   is(controlledContent.hidden, true, "The extension controlled row is hidden");
 
   // Setting the engine back to the extension's engine does not show the message.
   setEngine(extensionEngine);
+  // Wait a tick for the Search Service's promises to resolve.
+  await new Promise(resolve => executeSoon(resolve));
 
   is(extensionEngine, Services.search.defaultEngine,
      "default search engine is set back to extension");
   is(controlledContent.hidden, true, "The extension controlled row is still hidden");
 
   // Set the engine to the initial one and verify an upgrade doesn't change it.
   setEngine(initialEngine);
   await waitForMessageHidden(controlledContent.id);
--- 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/search/content/searchbar.js
+++ b/browser/components/search/content/searchbar.js
@@ -114,29 +114,25 @@ class MozSearchbar extends MozXULElement
 
     Services.obs.addObserver(this.observer, "browser-search-engine-modified");
     Services.obs.addObserver(this.observer, "browser-search-service");
 
     this._initialized = true;
 
     (window.delayedStartupPromise || Promise.resolve()).then(() => {
       window.requestIdleCallback(() => {
-        Services.search.init(aStatus => {
+        Services.search.init().then(aStatus => {
           // Bail out if the binding's been destroyed
           if (!this._initialized)
             return;
 
-          if (Components.isSuccessCode(aStatus)) {
-            // Refresh the display (updating icon, etc)
-            this.updateDisplay();
-            BrowserSearch.updateOpenSearchBadge();
-          } else {
-            Cu.reportError("Cannot initialize search service, bailing out: " + aStatus);
-          }
-        });
+          // Refresh the display (updating icon, etc)
+          this.updateDisplay();
+          BrowserSearch.updateOpenSearchBadge();
+        }).catch(status => Cu.reportError("Cannot initialize search service, bailing out: " + status));
       });
     });
 
     // Wait until the popupshowing event to avoid forcing immediate
     // attachment of the search-one-offs binding.
     this.textbox.popup.addEventListener("popupshowing", () => {
       let oneOffButtons = this.textbox.popup.oneOffButtons;
       // Some accessibility tests create their own <searchbar> that doesn't
--- a/browser/components/search/test/browser/SearchTestUtils.jsm
+++ b/browser/components/search/test/browser/SearchTestUtils.jsm
@@ -21,23 +21,14 @@ var SearchTestUtils = Object.freeze({
    * at the end of the test.
    *
    * @param {string}   url                     The URL of the engine to add.
    * @param {Function} registerCleanupFunction Pass the registerCleanupFunction
    *                                           from the test's scope.
    * @returns {Promise} Returns a promise that is resolved with the new engine
    *                    or rejected if it fails.
    */
-  promiseNewSearchEngine(url) {
-    return new Promise((resolve, reject) => {
-      Services.search.addEngine(url, "", false, {
-        onSuccess(engine) {
-          gTestGlobals.registerCleanupFunction(() => Services.search.removeEngine(engine));
-          resolve(engine);
-        },
-        onError(errCode) {
-          gTestGlobals.Assert.ok(false, `addEngine failed with error code ${errCode}`);
-          reject();
-        },
-      });
-    });
+  async promiseNewSearchEngine(url) {
+    let engine = await Services.search.addEngine(url, "", false);
+    gTestGlobals.registerCleanupFunction(async () => Services.search.removeEngine(engine));
+    return engine;
   },
 });
--- a/browser/components/search/test/browser/browser_426329.js
+++ b/browser/components/search/test/browser/browser_426329.js
@@ -63,17 +63,17 @@ function promiseSetEngine() {
     function observer(aSub, aTopic, aData) {
       switch (aData) {
         case "engine-added":
           let engine = ss.getEngineByName("Bug 426329");
           ok(engine, "Engine was added.");
           ss.defaultEngine = engine;
           break;
         case "engine-current":
-          ok(ss.defaultEngine.name == "Bug 426329", "currentEngine set");
+          ok(ss.defaultEngine.name == "Bug 426329", "defaultEngine set");
           searchBar = BrowserSearch.searchBar;
           searchButton = searchBar.querySelector(".search-go-button");
           ok(searchButton, "got search-go-button");
           searchBar.value = "test";
 
           Services.obs.removeObserver(observer, "browser-search-engine-modified");
           resolve();
           break;
@@ -102,16 +102,18 @@ function promiseRemoveEngine() {
     ss.removeEngine(engine);
   });
 }
 
 
 var preSelectedBrowser;
 var preTabNo;
 async function prepareTest() {
+  await Services.search.init();
+
   preSelectedBrowser = gBrowser.selectedBrowser;
   preTabNo = gBrowser.tabs.length;
   searchBar = BrowserSearch.searchBar;
 
   await SimpleTest.promiseFocus();
 
   if (document.activeElement == searchBar)
     return;
--- a/browser/components/search/test/browser/browser_addEngine.js
+++ b/browser/components/search/test/browser/browser_addEngine.js
@@ -62,32 +62,32 @@ var gTests = [
       is(engine, engineFromSS, "engine is obtainable via getEngineByName");
 
       let aEngine = gSS.getEngineByAlias("fooalias");
       ok(!aEngine, "Alias was not parsed from engine description");
 
       gSS.defaultEngine = engine;
     },
     current(engine) {
-      let currentEngine = gSS.defaultEngine;
-      is(engine, currentEngine, "engine is current");
+      let defaultEngine = gSS.defaultEngine;
+      is(engine, defaultEngine, "engine is current");
       is(engine.name, this.engine.name, "current engine was changed successfully");
 
       gSS.removeEngine(engine);
     },
     removed(engine) {
-      // Remove the observer before calling the currentEngine getter,
-      // as that getter will set the currentEngine to the original default
+      // Remove the observer before calling the defaultEngine getter,
+      // as that getter will set the defaultEngine to the original default
       // which will trigger a notification causing the test to loop over all
       // engines.
       Services.obs.removeObserver(observer, "browser-search-engine-modified");
 
-      let currentEngine = gSS.defaultEngine;
-      ok(currentEngine, "An engine is present.");
-      isnot(currentEngine.name, this.engine.name, "Current engine reset after removal");
+      let defaultEngine = gSS.defaultEngine;
+      ok(defaultEngine, "An engine is present.");
+      isnot(defaultEngine.name, this.engine.name, "Current engine reset after removal");
 
       nextTest();
     },
   },
 ];
 
 var gCurrentTest = null;
 function nextTest() {
@@ -96,10 +96,10 @@ function nextTest() {
     info("Running " + gCurrentTest.name);
     gCurrentTest.run();
   } else
     executeSoon(finish);
 }
 
 function test() {
   waitForExplicitFinish();
-  nextTest();
+  gSS.init().then(nextTest);
 }
--- a/browser/components/search/test/browser/browser_amazon.js
+++ b/browser/components/search/test/browser/browser_amazon.js
@@ -4,17 +4,19 @@
 /*
  * Test Amazon search plugin URLs
  */
 
 "use strict";
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 
-function test() {
+add_task(async function test() {
+  await Services.search.init();
+
   let engine = Services.search.getEngineByName("Amazon.com");
   ok(engine, "Amazon.com");
 
   let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
@@ -74,9 +76,9 @@ function test() {
           ],
           mozparams: {},
         },
       ],
     },
   };
 
   isSubObjectOf(EXPECTED_ENGINE, engine, "Amazon");
-}
+});
--- a/browser/components/search/test/browser/browser_bing.js
+++ b/browser/components/search/test/browser/browser_bing.js
@@ -4,17 +4,19 @@
 /*
  * Test Bing search plugin URLs
  */
 
 "use strict";
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 
-function test() {
+add_task(async function test() {
+  await Services.search.init();
+
   let engine = Services.search.getEngineByName("Bing");
   ok(engine, "Bing");
 
   let base = "https://www.bing.com/search?q=foo&pc=MOZI";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
@@ -110,9 +112,9 @@ function test() {
           ],
           mozparams: {},
         },
       ],
     },
   };
 
   isSubObjectOf(EXPECTED_ENGINE, engine, "Bing");
-}
+});
--- a/browser/components/search/test/browser/browser_contextmenu.js
+++ b/browser/components/search/test/browser/browser_contextmenu.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  *  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /*
  * Test searching for the selected text using the context menu
  */
 
 add_task(async function() {
   const ss = Services.search;
+  await ss.init();
   const ENGINE_NAME = "Foo";
   let contextMenu;
 
   // We want select events to be fired.
   await SpecialPowers.pushPrefEnv({set: [["dom.select_events.enabled", true]]});
 
   let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
   let originalValue = envService.get("XPCSHELL_TEST_PROFILE_DIR");
@@ -30,17 +31,17 @@ add_task(async function() {
         case "engine-added":
           let engine = ss.getEngineByName(ENGINE_NAME);
           ok(engine, "Engine was added.");
           ss.defaultEngine = engine;
           envService.set("XPCSHELL_TEST_PROFILE_DIR", originalValue);
           resProt.setSubstitution("search-plugins", originalSubstitution);
           break;
         case "engine-current":
-          is(ss.defaultEngine.name, ENGINE_NAME, "currentEngine set");
+          is(ss.defaultEngine.name, ENGINE_NAME, "defaultEngine set");
           resolve();
           break;
         case "engine-removed":
           Services.obs.removeObserver(observer, "browser-search-engine-modified");
           if (searchDonePromise) {
             searchDonePromise();
           }
           break;
--- a/browser/components/search/test/browser/browser_ddg.js
+++ b/browser/components/search/test/browser/browser_ddg.js
@@ -4,17 +4,19 @@
 /*
  * Test DuckDuckGo search plugin URLs
  */
 
 "use strict";
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 
-function test() {
+add_task(async function test() {
+  await Services.search.init();
+
   let engine = Services.search.getEngineByName("DuckDuckGo");
   ok(engine, "DuckDuckGo");
 
   let base = "https://duckduckgo.com/?q=foo";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
@@ -100,9 +102,9 @@ function test() {
             },
           ],
         },
       ],
     },
   };
 
   isSubObjectOf(EXPECTED_ENGINE, engine, "DuckDuckGo");
-}
+});
--- a/browser/components/search/test/browser/browser_eBay.js
+++ b/browser/components/search/test/browser/browser_eBay.js
@@ -4,17 +4,19 @@
 /*
  * Test eBay search plugin URLs
  */
 
 "use strict";
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 
-function test() {
+add_task(async function test() {
+  await Services.search.init();
+
   let engine = Services.search.getEngineByName("eBay");
   ok(engine, "eBay");
 
   let base = "https://rover.ebay.com/rover/1/711-53200-19255-0/1?ff3=4&toolid=20004&campid=5338192028&customid=&mpre=https://www.ebay.com/sch/foo";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
@@ -63,9 +65,9 @@ function test() {
           ],
           mozparams: {},
         },
       ],
     },
   };
 
   isSubObjectOf(EXPECTED_ENGINE, engine, "eBay");
-}
+});
--- a/browser/components/search/test/browser/browser_google.js
+++ b/browser/components/search/test/browser/browser_google.js
@@ -36,17 +36,19 @@ let expectedEngine = {
         ],
         mozparams: {
         },
       },
     ],
   },
 };
 
-function test() {
+add_task(async function test() {
+  await Services.search.init();
+
   let engine = Services.search.getEngineByName("Google");
   ok(engine, "Found Google search engine");
 
   let region = Services.prefs.getCharPref("browser.search.region");
   let code = "";
   switch (region) {
     case "US":
       code = "firefox-b-1-d";
@@ -90,9 +92,9 @@ function test() {
   is(Services.search.parseSubmissionURL(base).terms, "foo",
      "Check result parsing");
   let alternateBase = base.replace("www.google.com", "www.google.fr");
   is(Services.search.parseSubmissionURL(alternateBase).terms, "foo",
      "Check alternate domain");
 
   // Check all other engine properties.
   isSubObjectOf(expectedEngine, engine, "Google");
-}
+});
--- a/browser/components/search/test/browser/browser_google_behavior.js
+++ b/browser/components/search/test/browser/browser_google_behavior.js
@@ -54,16 +54,20 @@ function promiseContentSearchReady(brows
           content.removeEventListener("ContentSearchService", listener);
           resolve();
         }
       });
     });
   });
 }
 
+add_task(async function setup() {
+  await Services.search.init();
+});
+
 for (let engine of searchEngineDetails) {
   add_task(async function() {
     let previouslySelectedEngine = Services.search.defaultEngine;
 
     registerCleanupFunction(function() {
       Services.search.defaultEngine = previouslySelectedEngine;
     });
 
--- a/browser/components/search/test/browser/browser_hiddenOneOffs_cleanup.js
+++ b/browser/components/search/test/browser/browser_hiddenOneOffs_cleanup.js
@@ -1,54 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 const testPref = "Foo,FooDupe";
 
-function promiseNewEngine(basename) {
-  return new Promise((resolve, reject) => {
-    info("Waiting for engine to be added: " + basename);
-    Services.search.init({
-      onInitComplete() {
-        let url = getRootDirectory(gTestPath) + basename;
-        Services.search.addEngine(url, "", false, {
-          onSuccess(engine) {
-            info("Search engine added: " + basename);
-            resolve(engine);
-          },
-          onError(errCode) {
-            ok(false, "addEngine failed with error code " + errCode);
-            reject();
-          },
-        });
-      },
-    });
-  });
+async function promiseNewEngine(basename) {
+  info("Waiting for engine to be added: " + basename);
+  let url = getRootDirectory(gTestPath) + basename;
+  let engine = await Services.search.addEngine(url, "", false);
+  info("Search engine added: " + basename);
+  return engine;
 }
 
 add_task(async function test_remove() {
   await promiseNewEngine("testEngine_dupe.xml");
   await promiseNewEngine("testEngine.xml");
   Services.prefs.setCharPref("browser.search.hiddenOneOffs", testPref);
 
   info("Removing testEngine_dupe.xml");
-  Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
+  await Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
 
   let hiddenOneOffs =
     Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
 
   is(hiddenOneOffs.length, 1,
      "hiddenOneOffs has the correct engine count post removal.");
   is(hiddenOneOffs.some(x => x == "FooDupe"), false,
      "Removed Engine is not in hiddenOneOffs after removal");
   is(hiddenOneOffs.some(x => x == "Foo"), true,
      "Current hidden engine is not affected by removal.");
 
   info("Removing testEngine.xml");
-  Services.search.removeEngine(Services.search.getEngineByName("Foo"));
+  await Services.search.removeEngine(Services.search.getEngineByName("Foo"));
 
   is(Services.prefs.getCharPref("browser.search.hiddenOneOffs"), "",
      "hiddenOneOffs is empty after removing all hidden engines.");
 });
 
 add_task(async function test_add() {
   await promiseNewEngine("testEngine.xml");
   info("setting prefs to " + testPref);
@@ -77,23 +64,23 @@ add_task(async function test_diacritics(
   let hiddenOneOffs =
     Preferences.get("browser.search.hiddenOneOffs").split(",");
   is(hiddenOneOffs.some(x => x == diacritic_engine), false,
      "Observer cleans up added hidden engines that include a diacritic.");
 
   Preferences.set("browser.search.hiddenOneOffs", diacritic_engine);
 
   info("Removing testEngine_diacritics.xml");
-  Services.search.removeEngine(Services.search.getEngineByName(diacritic_engine));
+  await Services.search.removeEngine(Services.search.getEngineByName(diacritic_engine));
 
   hiddenOneOffs =
     Preferences.get("browser.search.hiddenOneOffs").split(",");
   is(hiddenOneOffs.some(x => x == diacritic_engine), false,
      "Observer cleans up removed hidden engines that include a diacritic.");
 });
 
-registerCleanupFunction(() => {
+registerCleanupFunction(async () => {
   info("Removing testEngine.xml");
-  Services.search.removeEngine(Services.search.getEngineByName("Foo"));
+  await Services.search.removeEngine(Services.search.getEngineByName("Foo"));
   info("Removing testEngine_dupe.xml");
-  Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
+  await Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
   Services.prefs.clearUserPref("browser.search.hiddenOneOffs");
 });
--- a/browser/components/search/test/browser/browser_hiddenOneOffs_diacritics.js
+++ b/browser/components/search/test/browser/browser_hiddenOneOffs_diacritics.js
@@ -14,20 +14,20 @@ let searchIcon;
 
 add_task(async function init() {
   let searchbar = await gCUITestUtils.addSearchBar();
   registerCleanupFunction(() => {
     gCUITestUtils.removeSearchBar();
   });
   searchIcon = searchbar.querySelector(".searchbar-search-button");
 
-  let currentEngine = Services.search.defaultEngine;
+  let defaultEngine = await Services.search.getDefault();
   await promiseNewEngine("testEngine_diacritics.xml", {setAsCurrent: false});
-  registerCleanupFunction(() => {
-    Services.search.defaultEngine = currentEngine;
+  registerCleanupFunction(async () => {
+    await Services.search.setDefault(defaultEngine);
     Services.prefs.clearUserPref("browser.search.hiddenOneOffs");
   });
 });
 
 add_task(async function test_hidden() {
   Preferences.set("browser.search.hiddenOneOffs", diacritic_engine);
 
   let promise = promiseEvent(searchPopup, "popupshown");
--- a/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
+++ b/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
@@ -7,27 +7,28 @@ const URLBAR_BASE_ID = "urlbar-engine-on
 const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
 
 const urlbar = document.getElementById("urlbar");
 const searchPopup = document.getElementById("PopupSearchAutoComplete");
 const urlbarPopup = document.getElementById("PopupAutoCompleteRichResult");
 const searchOneOff = searchPopup.oneOffButtons;
 const urlBarOneOff = urlbarPopup.oneOffSearchButtons;
 
-let originalEngine = Services.search.defaultEngine;
+var originalEngine;
 
-function resetEngine() {
-  Services.search.defaultEngine = originalEngine;
+async function resetEngine() {
+  await Services.search.setDefault(originalEngine);
 }
 
 registerCleanupFunction(resetEngine);
 
 let searchIcon;
 
 add_task(async function init() {
+  originalEngine = await Services.search.getDefault();
   let searchbar = await gCUITestUtils.addSearchBar();
   registerCleanupFunction(() => {
     gCUITestUtils.removeSearchBar();
   });
   searchIcon = searchbar.querySelector(".searchbar-search-button");
 
   await promiseNewEngine(TEST_ENGINE_BASENAME, {
     setAsCurrent: false,
@@ -39,17 +40,17 @@ add_task(async function test_searchBarCh
                                                        searchOneOff,
                                                        SEARCHBAR_BASE_ID);
 
   const setDefaultEngineMenuItem = searchOneOff.querySelector(
     ".search-one-offs-context-set-default"
   );
 
   // Click the set default engine menu item.
-  let promise = promiseCurrentEngineChanged();
+  let promise = promisedefaultEngineChanged();
   EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {});
 
   // This also checks the engine correctly changed.
   await promise;
 
   Assert.equal(oneOffButton.id, SEARCHBAR_BASE_ID + originalEngine.name,
                "Should now have the original engine's id for the button");
   Assert.equal(oneOffButton.getAttribute("tooltiptext"), originalEngine.name,
@@ -73,46 +74,46 @@ add_task(async function test_urlBarChang
                                                        urlBarOneOff,
                                                        URLBAR_BASE_ID);
 
   const setDefaultEngineMenuItem = urlBarOneOff.querySelector(
     ".search-one-offs-context-set-default"
   );
 
   // Click the set default engine menu item.
-  let promise = promiseCurrentEngineChanged();
+  let promise = promisedefaultEngineChanged();
   EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {});
 
   // This also checks the engine correctly changed.
   await promise;
 
-  let currentEngine = Services.search.defaultEngine;
+  let defaultEngine = await Services.search.getDefault();
 
   // For the urlbar, we should keep the new engine's icon.
-  Assert.equal(oneOffButton.id, URLBAR_BASE_ID + currentEngine.name,
+  Assert.equal(oneOffButton.id, URLBAR_BASE_ID + defaultEngine.name,
                "Should now have the original engine's id for the button");
-  Assert.equal(oneOffButton.getAttribute("tooltiptext"), currentEngine.name,
+  Assert.equal(oneOffButton.getAttribute("tooltiptext"), defaultEngine.name,
                "Should now have the original engine's name for the tooltip");
-  Assert.equal(oneOffButton.image, currentEngine.iconURI.spec,
+  Assert.equal(oneOffButton.image, defaultEngine.iconURI.spec,
                "Should now have the original engine's uri for the image");
 
   await promiseClosePopup(urlbarPopup);
 });
 
 /**
  * Promises that an engine change has happened for the current engine, which
  * has resulted in the test engine now being the current engine.
  *
  * @returns {Promise} Resolved once the test engine is set as the current engine.
  */
-function promiseCurrentEngineChanged() {
+function promisedefaultEngineChanged() {
   return new Promise(resolve => {
     function observer(aSub, aTopic, aData) {
       if (aData == "engine-current") {
-        Assert.equal(Services.search.defaultEngine.name, TEST_ENGINE_NAME, "currentEngine set");
+        Assert.equal(Services.search.defaultEngine.name, TEST_ENGINE_NAME, "defaultEngine set");
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         resolve();
       }
     }
 
     Services.obs.addObserver(observer, "browser-search-engine-modified");
   });
 }
--- a/browser/components/search/test/browser/browser_oneOffHeader.js
+++ b/browser/components/search/test/browser/browser_oneOffHeader.js
@@ -126,18 +126,17 @@ add_task(async function test_text() {
     document.getAnonymousElementByAttribute(searchPopup, "anonid",
                                             "searchbar-engine");
 
   await synthesizeNativeMouseMove(searchbarEngine);
   SimpleTest.executeSoon(() => {
     EventUtils.synthesizeMouseAtCenter(searchbarEngine, {});
   });
 
-  let url = Services.search.defaultEngine
-                           .getSubmission(searchbar.textbox.value).uri.spec;
+  let url = (await Services.search.getDefault()).getSubmission(searchbar.textbox.value).uri.spec;
   await promiseTabLoadEvent(gBrowser.selectedTab, url);
 
   // Move the cursor out of the panel area to avoid messing with other tests.
   await synthesizeNativeMouseMove(searchbar);
 });
 
 add_task(async function cleanup() {
   searchbar.textbox.value = "";
--- a/browser/components/search/test/browser/browser_searchEngine_behaviors.js
+++ b/browser/components/search/test/browser/browser_searchEngine_behaviors.js
@@ -76,31 +76,31 @@ add_task(async function test_setup() {
   await gCUITestUtils.addSearchBar();
   registerCleanupFunction(() => {
     gCUITestUtils.removeSearchBar();
   });
 });
 
 for (let engine of SEARCH_ENGINE_DETAILS) {
   add_task(async function() {
-    let previouslySelectedEngine = Services.search.defaultEngine;
+    let previouslySelectedEngine = await Services.search.getDefault();
 
-    registerCleanupFunction(function() {
-      Services.search.defaultEngine = previouslySelectedEngine;
+    registerCleanupFunction(async function() {
+      await Services.search.setDefault(previouslySelectedEngine);
     });
 
     await testSearchEngine(engine);
   });
 }
 
 async function testSearchEngine(engineDetails) {
   let engine = Services.search.getEngineByName(engineDetails.name);
   Assert.ok(engine, `${engineDetails.name} is installed`);
 
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
   engine.alias = engineDetails.alias;
 
   let base = engineDetails.baseURL;
 
   // Test search URLs (including purposes).
   let url = engine.getSubmission("foo").uri.spec;
   Assert.equal(url, base + engineDetails.codes.submission, "Check search URL for 'foo'");
   let sb = BrowserSearch.searchBar;
--- a/browser/components/search/test/browser/head.js
+++ b/browser/components/search/test/browser/head.js
@@ -53,49 +53,35 @@ function promiseEvent(aTarget, aEventNam
  *   - {String} [iconURL]       The icon to use for the search engine.
  *   - {Boolean} [setAsCurrent] Whether to set the new engine to be the
  *                              current engine or not.
  *   - {String} [testPath]      Used to override the current test path if this
  *                              file is used from a different directory.
  * @returns {Promise} The promise is resolved once the engine is added, or
  *                    rejected if the addition failed.
  */
-function promiseNewEngine(basename, options = {}) {
-  return new Promise((resolve, reject) => {
-    // Default the setAsCurrent option to true.
-    let setAsCurrent =
-      options.setAsCurrent == undefined ? true : options.setAsCurrent;
-    info("Waiting for engine to be added: " + basename);
-    Services.search.init({
-      onInitComplete() {
-        let url = getRootDirectory(options.testPath || gTestPath) + basename;
-        let current = Services.search.defaultEngine;
-        Services.search.addEngine(url, options.iconURL || "", false, {
-          onSuccess(engine) {
-            info("Search engine added: " + basename);
-            if (setAsCurrent) {
-              Services.search.defaultEngine = engine;
-            }
-            registerCleanupFunction(() => {
-              if (setAsCurrent) {
-                Services.search.defaultEngine = current;
-              }
-              Services.search.removeEngine(engine);
-              info("Search engine removed: " + basename);
-            });
-            resolve(engine);
-          },
-          onError(errCode) {
-            ok(false, "addEngine failed with error code " + errCode);
-            reject();
-          },
-        });
-      },
-    });
+async function promiseNewEngine(basename, options = {}) {
+  // Default the setAsCurrent option to true.
+  let setAsCurrent = options.setAsCurrent == undefined ? true : options.setAsCurrent;
+  info("Waiting for engine to be added: " + basename);
+  let url = getRootDirectory(options.testPath || gTestPath) + basename;
+  let current = await Services.search.getDefault();
+  let engine = await Services.search.addEngine(url, options.iconURL || "", false);
+  info("Search engine added: " + basename);
+  if (setAsCurrent) {
+    await Services.search.setDefault(engine);
+  }
+  registerCleanupFunction(async () => {
+    if (setAsCurrent) {
+      await Services.search.setDefault(current);
+    }
+    await Services.search.removeEngine(engine);
+    info("Search engine removed: " + basename);
   });
+  return engine;
 }
 
 let promiseStateChangeFrameScript = "data:," + encodeURIComponent(`(${
   () => {
     /* globals docShell, sendAsyncMessage */
 
     const global = this;
     const LISTENER = Symbol("listener");
--- a/browser/components/tests/unit/test_distribution.js
+++ b/browser/components/tests/unit/test_distribution.js
@@ -141,17 +141,12 @@ add_task(async function() {
   Assert.equal(defaultBranch.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Locale Set");
   // This value was overridden by a language specific setting
   Assert.equal(defaultBranch.getComplexValue("distribution.test.language.set", Ci.nsIPrefLocalizedString).data, "Language Set");
   // Language should not override locale
   Assert.notEqual(defaultBranch.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Language Set");
 
   Services.prefs.setCharPref("distribution.searchplugins.defaultLocale", "de-DE");
 
-  await new Promise(resolve => {
-    Services.search.init(function() {
-      Assert.equal(Services.search.isInitialized, true);
-      var engine = Services.search.getEngineByName("Google");
-      Assert.equal(engine.description, "override-de-DE");
-      resolve();
-    });
-  });
+  await Services.search.init();
+  var engine = Services.search.getEngineByName("Google");
+  Assert.equal(engine.description, "override-de-DE");
 });
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -1453,29 +1453,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),
@@ -1658,27 +1653,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/components/uitour/test/browser_UITour.js
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -351,58 +351,52 @@ var tests = [
 
           // Cleanup
           CustomizableUI.removeWidgetFromArea("panic-button");
           done();
         });
       });
     });
   },
-  function test_search(done) {
-    Services.search.init(rv => {
-      if (!Components.isSuccessCode(rv)) {
-        ok(false, "search service init failed: " + rv);
-        done();
-        return;
-      }
-      let defaultEngine = Services.search.defaultEngine;
-      gContentAPI.getConfiguration("search", data => {
-        let visibleEngines = Services.search.getVisibleEngines();
-        let expectedEngines = visibleEngines.filter((engine) => engine.identifier)
-                                            .map((engine) => "searchEngine-" + engine.identifier);
+  taskify(async function test_search() {
+    let defaultEngine = await Services.search.getDefault();
+    let visibleEngines = await Services.search.getVisibleEngines();
+    let expectedEngines = visibleEngines.filter((engine) => engine.identifier)
+                                        .map((engine) => "searchEngine-" + engine.identifier);
 
-        let engines = data.engines;
-        ok(Array.isArray(engines), "data.engines should be an array");
-        is(engines.sort().toString(), expectedEngines.sort().toString(),
-           "Engines should be as expected");
+    let data = await new Promise(resolve => gContentAPI.getConfiguration("search", resolve));
+    let engines = data.engines;
+    ok(Array.isArray(engines), "data.engines should be an array");
+    is(engines.sort().toString(), expectedEngines.sort().toString(),
+       "Engines should be as expected");
 
-        is(data.searchEngineIdentifier, defaultEngine.identifier,
-           "the searchEngineIdentifier property should contain the defaultEngine's identifier");
+    is(data.searchEngineIdentifier, defaultEngine.identifier,
+       "the searchEngineIdentifier property should contain the defaultEngine's identifier");
+
+    let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
+    someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
 
-        let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
-        someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
+    await new Promise(resolve => {
+      let observe = function(subject, topic, verb) {
+        info("browser-search-engine-modified: " + verb);
+        if (verb == "engine-current") {
+          is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
+          done();
+        }
+      };
+      Services.obs.addObserver(observe, "browser-search-engine-modified");
+      registerCleanupFunction(async () => {
+        // Clean up
+        Services.obs.removeObserver(observe, "browser-search-engine-modified");
+        await Services.search.setDefault(defaultEngine);
+      });
 
-        let observe = function(subject, topic, verb) {
-          info("browser-search-engine-modified: " + verb);
-          if (verb == "engine-current") {
-            is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
-            done();
-          }
-        };
-        Services.obs.addObserver(observe, "browser-search-engine-modified");
-        registerCleanupFunction(() => {
-          // Clean up
-          Services.obs.removeObserver(observe, "browser-search-engine-modified");
-          Services.search.defaultEngine = defaultEngine;
-        });
-
-        gContentAPI.setDefaultSearchEngine(someOtherEngineID);
-      });
+      gContentAPI.setDefaultSearchEngine(someOtherEngineID);
     });
-  },
+  }),
   taskify(async function test_treatment_tag() {
     let ac = new TelemetryArchiveTesting.Checker();
     await ac.promiseInit();
     await gContentAPI.setTreatmentTag("foobar", "baz");
     // Wait until the treatment telemetry is sent before looking in the archive.
     await BrowserTestUtils.waitForContentEvent(gTestTab.linkedBrowser, "mozUITourNotification", false,
                                                event => event.detail.event === "TreatmentTag:TelemetrySent");
     await new Promise((resolve) => {
--- a/browser/components/uitour/test/browser_UITour3.js
+++ b/browser/components/uitour/test/browser_UITour3.js
@@ -148,25 +148,19 @@ add_UITour_task(async function test_info
 
   // Cleanup.
   await hideInfoPromise();
 
   popup.removeAttribute("animate");
 });
 
 add_UITour_task(async function test_getConfiguration_selectedSearchEngine() {
-  await new Promise((resolve) => {
-    Services.search.init(async function(rv) {
-      ok(Components.isSuccessCode(rv), "Search service initialized");
-      let engine = Services.search.defaultEngine;
-      let data = await getConfigurationPromise("selectedSearchEngine");
-      is(data.searchEngineIdentifier, engine.identifier, "Correct engine identifier");
-      resolve();
-    });
-  });
+  let engine = await Services.search.getDefault();
+  let data = await getConfigurationPromise("selectedSearchEngine");
+  is(data.searchEngineIdentifier, engine.identifier, "Correct engine identifier");
 });
 
 add_UITour_task(async function test_setSearchTerm() {
   // Place the search bar in the navigation toolbar temporarily.
   await SpecialPowers.pushPrefEnv({ set: [
     ["browser.search.widget.inNavBar", true],
   ]});
 
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine.js
@@ -2,27 +2,27 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that a search result has the correct attributes and visits the
  * expected URL for the engine.
  */
 
 add_task(async function() {
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+    "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
 
   registerCleanupFunction(async function() {
-    Services.search.defaultEngine = originalEngine;
-    Services.search.removeEngine(engine);
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engine);
     try {
       BrowserTestUtils.removeTab(tab);
     } catch (ex) { /* tab may have already been closed in case of failure */ }
     await PlacesUtils.history.clear();
   });
 
   await promiseAutocompleteResultPopup("open a search");
   let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
--- a/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
+++ b/browser/components/urlbar/tests/browser/browser_action_searchengine_alias.js
@@ -2,28 +2,34 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that search result obtained using a search keyword gives an entry with
  * the correct attributes and visits the expected URL for the engine.
  */
 
 add_task(async function() {
-  const ICON_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC";
-  Services.search.addEngineWithDetails("MozSearch", ICON_URI, "moz", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  const ICON_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAA" +
+    "CQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BL" +
+    "i4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkb" +
+    "G7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1v" +
+    "bjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlA" +
+    "fwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FA" +
+    "EWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC";
+  await Services.search.addEngineWithDetails("MozSearch", ICON_URI, "moz", "",
+    "GET", "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
 
   registerCleanupFunction(async function() {
-    Services.search.defaultEngine = originalEngine;
-    Services.search.removeEngine(engine);
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engine);
     try {
       BrowserTestUtils.removeTab(tab);
     } catch (ex) { /* tab may have already been closed in case of failure */ }
     await PlacesUtils.history.clear();
   });
 
   await promiseAutocompleteResultPopup("moz open a search");
   let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
--- a/browser/components/urlbar/tests/browser/browser_canonizeURL.js
+++ b/browser/components/urlbar/tests/browser/browser_canonizeURL.js
@@ -20,17 +20,18 @@ add_task(async function checkCtrlWorks()
     ["example.net", "http://example.net/", { ctrlKey: true }],
     ["http://example", "http://example/", { ctrlKey: true }],
     ["example:8080", "http://example:8080/", { ctrlKey: true }],
     ["ex-ample.foo", "http://ex-ample.foo/", { ctrlKey: true }],
     ["example.foo/bar ", "http://example.foo/bar", { ctrlKey: true }],
     ["1.1.1.1", "http://1.1.1.1/", { ctrlKey: true }],
     ["ftp://example", "ftp://example/", { ctrlKey: true }],
     ["ftp.example.bar", "http://ftp.example.bar/", { ctrlKey: true }],
-    ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec, { ctrlKey: true }],
+    ["ex ample", (await Services.search.getDefault()).getSubmission("ex ample", null, "keyword").uri.spec,
+      { ctrlKey: true }],
   ];
 
   // Disable autoFill for this test, since it could mess up the results.
   await SpecialPowers.pushPrefEnv({set: [
     ["browser.urlbar.autoFill", false],
     ["browser.urlbar.ctrlCanonizesURLs", true],
   ]});
 
@@ -44,19 +45,19 @@ add_task(async function checkCtrlWorks()
     await promiseLoad;
   }
 });
 
 add_task(async function checkPrefTurnsOffCanonize() {
   // Add a dummy search engine to avoid hitting the network.
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
-  registerCleanupFunction(() => { Services.search.defaultEngine = oldCurrentEngine; });
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
+  registerCleanupFunction(async () => Services.search.setDefault(oldDefaultEngine));
 
   let tabsToClose = [];
   // Ensure we don't end up loading something in the current tab becuase it's empty:
   if (gBrowser.selectedTab.isEmpty) {
     tabsToClose.push(await BrowserTestUtils.openNewForegroundTab({gBrowser, opening: "about:mozilla"}));
   }
   let initialTabURL = gBrowser.selectedBrowser.currentURI.spec;
   let initialTab = gBrowser.selectedTab;
--- a/browser/components/urlbar/tests/browser/browser_urlbarPlaceholder.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarPlaceholder.js
@@ -5,90 +5,94 @@
  * This test ensures the placeholder is set correctly for different search
  * engines.
  */
 
 "use strict";
 
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
-const originalEngine = Services.search.defaultEngine;
-const expectedString = gBrowserBundle.formatStringFromName("urlbar.placeholder",
-  [originalEngine.name], 1);
-var extraEngine;
+var originalEngine, extraEngine, expectedString;
 var tabs = [];
 
 add_task(async function setup() {
+  originalEngine = await Services.search.getDefault();
+  expectedString = gBrowserBundle.formatStringFromName("urlbar.placeholder", [originalEngine.name], 1);
+
   let rootDir = getRootDirectory(gTestPath);
   extraEngine = await SearchTestUtils.promiseNewSearchEngine(rootDir + TEST_ENGINE_BASENAME);
 
   // Force display of a tab with a URL bar, to clear out any possible placeholder
   // initialization listeners that happen on startup.
   let urlTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
   BrowserTestUtils.removeTab(urlTab);
 
-  registerCleanupFunction(() => {
-    Services.search.defaultEngine = originalEngine;
+  registerCleanupFunction(async () => {
+    await Services.search.setDefault(originalEngine);
     for (let tab of tabs) {
       BrowserTestUtils.removeTab(tab);
     }
   });
 });
 
 add_task(async function test_change_default_engine_updates_placeholder() {
   tabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser));
 
-  Services.search.defaultEngine = extraEngine;
+  await Services.search.setDefault(extraEngine);
 
   await TestUtils.waitForCondition(
     () => gURLBar.getAttribute("placeholder") == gURLBar.getAttribute("defaultPlaceholder"),
     "The placeholder should match the default placeholder for non-built-in engines.");
+  Assert.equal(gURLBar.getAttribute("placeholder"), gURLBar.getAttribute("defaultPlaceholder"));
 
-  Services.search.defaultEngine = originalEngine;
+  await Services.search.setDefault(originalEngine);
 
   await TestUtils.waitForCondition(
     () => gURLBar.getAttribute("placeholder") == expectedString,
     "The placeholder should include the engine name for built-in engines.");
+  Assert.equal(gURLBar.getAttribute("placeholder"), expectedString);
 });
 
 add_task(async function test_delayed_update_placeholder() {
   // Since we can't easily test for startup changes, we'll at least test the delay
   // of update for the placeholder works.
   let urlTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
   tabs.push(urlTab);
 
   // Open a tab with a blank URL bar.
   let blankTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   tabs.push(blankTab);
 
   // Pretend we've "initialized".
-  BrowserSearch._updateURLBarPlaceholder(extraEngine, true);
+  BrowserSearch._updateURLBarPlaceholder(extraEngine.name, true);
 
   Assert.equal(gURLBar.getAttribute("placeholder"), expectedString,
     "Placeholder should be unchanged.");
 
   // Now switch to a tab with something in the URL Bar.
   await BrowserTestUtils.switchTab(gBrowser, urlTab);
 
   await TestUtils.waitForCondition(
     () => gURLBar.getAttribute("placeholder") == gURLBar.getAttribute("defaultPlaceholder"),
     "The placeholder should have updated in the background.");
 
   // Do it the other way to check both named engine and fallback code paths.
   await BrowserTestUtils.switchTab(gBrowser, blankTab);
 
-  BrowserSearch._updateURLBarPlaceholder(originalEngine, true);
+  BrowserSearch._updateURLBarPlaceholder(originalEngine.name, true);
+  await TestUtils.waitForTick();
 
   Assert.equal(gURLBar.getAttribute("placeholder"), gURLBar.getAttribute("defaultPlaceholder"),
     "Placeholder should be unchanged.");
 
   await BrowserTestUtils.switchTab(gBrowser, urlTab);
 
   await TestUtils.waitForCondition(
     () => gURLBar.getAttribute("placeholder") == expectedString,
     "The placeholder should include the engine name for built-in engines.");
 
   // Now check when we have a URL displayed, the placeholder is updated straight away.
-  BrowserSearch._updateURLBarPlaceholder(extraEngine);
+  BrowserSearch._updateURLBarPlaceholder(extraEngine.name);
+  await TestUtils.waitForTick();
 
   Assert.equal(gURLBar.getAttribute("placeholder"), gURLBar.getAttribute("defaultPlaceholder"),
     "Placeholder should be the default.");
 });
--- a/browser/components/urlbar/tests/browser/browser_urlbarSearchSingleWordNotification.js
+++ b/browser/components/urlbar/tests/browser/browser_urlbarSearchSingleWordNotification.js
@@ -27,20 +27,17 @@ function promiseNotification(aBrowser, v
 }
 
 async function runURLBarSearchTest({valueToOpen, expectSearch, expectNotification, aWindow = window}) {
   aWindow.gURLBar.value = valueToOpen;
   let expectedURI;
   if (!expectSearch) {
     expectedURI = "http://" + valueToOpen + "/";
   } else {
-    await new Promise(resolve => {
-      Services.search.init(resolve);
-    });
-    expectedURI = Services.search.defaultEngine.getSubmission(valueToOpen, null, "keyword").uri.spec;
+    expectedURI = (await Services.search.getDefault()).getSubmission(valueToOpen, null, "keyword").uri.spec;
   }
   aWindow.gURLBar.focus();
   let docLoadPromise =
     BrowserTestUtils.waitForDocLoadAndStopIt(expectedURI, aWindow.gBrowser.selectedBrowser);
   EventUtils.synthesizeKey("VK_RETURN", {}, aWindow);
 
   await Promise.all([
     docLoadPromise,
--- a/browser/components/urlbar/tests/legacy/browser_autocomplete_a11y_label.js
+++ b/browser/components/urlbar/tests/legacy/browser_autocomplete_a11y_label.js
@@ -25,23 +25,23 @@ add_task(async function switchToTab() {
 
   await UrlbarTestUtils.promisePopupClose(window);
   gBrowser.removeTab(tab);
 });
 
 add_task(async function searchSuggestions() {
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
   Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
-  registerCleanupFunction(function() {
-    Services.search.defaultEngine = oldCurrentEngine;
+  registerCleanupFunction(async function() {
+    await Services.search.setDefault(oldDefaultEngine);
     Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
   });
 
   await promiseAutocompleteResultPopup("foo");
   let length = await UrlbarTestUtils.getResultCount(window);
   // Don't assume that the search doesn't match history or bookmarks left around
   // by earlier tests.
--- a/browser/components/urlbar/tests/legacy/browser_autocomplete_enter_race.js
+++ b/browser/components/urlbar/tests/legacy/browser_autocomplete_enter_race.js
@@ -58,31 +58,31 @@ add_task(taskWithNewTab(async function t
   // Disable autocomplete.
   let suggestHistory = Preferences.get("browser.urlbar.suggest.history");
   Preferences.set("browser.urlbar.suggest.history", false);
   let suggestBookmarks = Preferences.get("browser.urlbar.suggest.bookmark");
   Preferences.set("browser.urlbar.suggest.bookmark", false);
   let suggestOpenPages = Preferences.get("browser.urlbar.suggest.openpage");
   Preferences.set("browser.urlbar.suggest.openpage", false);
 
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+    "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 
-  function cleanup() {
+  async function cleanup() {
     Preferences.set("browser.urlbar.suggest.history", suggestHistory);
     Preferences.set("browser.urlbar.suggest.bookmark", suggestBookmarks);
     Preferences.set("browser.urlbar.suggest.openpage", suggestOpenPages);
 
-    Services.search.defaultEngine = originalEngine;
+    await Services.search.setDefault(originalEngine);
     let mozSearchEngine = Services.search.getEngineByName("MozSearch");
     if (mozSearchEngine) {
-      Services.search.removeEngine(mozSearchEngine);
+      await Services.search.removeEngine(mozSearchEngine);
     }
   }
   registerCleanupFunction(cleanup);
 
   gURLBar.focus();
   gURLBar.value = "e";
   EventUtils.sendString("x");
   EventUtils.synthesizeKey("KEY_Enter");
--- a/browser/components/urlbar/tests/legacy/browser_dragdropURL.js
+++ b/browser/components/urlbar/tests/legacy/browser_dragdropURL.js
@@ -7,29 +7,29 @@ const DRAG_TEXT = "Firefox is awesome";
 const DRAG_TEXT_URL = "http://example.com/?q=Firefox+is+awesome";
 const DRAG_WORD = "Firefox";
 const DRAG_WORD_URL = "http://example.com/?q=Firefox";
 
 registerCleanupFunction(async function cleanup() {
   while (gBrowser.tabs.length > 1) {
     BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
   }
-  Services.search.defaultEngine = originalEngine;
+  await Services.search.setDefault(originalEngine);
   let engine = Services.search.getEngineByName("MozSearch");
-  Services.search.removeEngine(engine);
+  await Services.search.removeEngine(engine);
 });
 
 let originalEngine;
 add_task(async function test_setup() {
   // Stop search-engine loads from hitting the network
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+    "http://example.com/?q={searchTerms}");
   let engine = Services.search.getEngineByName("MozSearch");
-  originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 });
 
 add_task(async function checkDragURL() {
   await BrowserTestUtils.withNewTab(TEST_URL, function(browser) {
     // Have to use something other than the URL bar as a source, so picking the
     // home button somewhat arbitrarily:
     EventUtils.synthesizeDrop(document.getElementById("home-button"), gURLBar,
                               [[{type: "text/plain", data: DRAG_URL}]], "copy", window);
--- a/browser/components/urlbar/tests/legacy/browser_search_favicon.js
+++ b/browser/components/urlbar/tests/legacy/browser_search_favicon.js
@@ -1,34 +1,34 @@
 var gOriginalEngine;
 var gEngine;
 var gRestyleSearchesPref = "browser.urlbar.restyleSearches";
 
-registerCleanupFunction(() => {
+registerCleanupFunction(async () => {
   Services.prefs.clearUserPref(gRestyleSearchesPref);
-  Services.search.defaultEngine = gOriginalEngine;
-  Services.search.removeEngine(gEngine);
+  await Services.search.setDefault(gOriginalEngine);
+  await Services.search.removeEngine(gEngine);
   return PlacesUtils.history.clear();
 });
 
 add_task(async function() {
   Services.prefs.setBoolPref(gRestyleSearchesPref, true);
 
   // This test is sensitive to the mouse position hovering awesome
   // bar elements, so make sure it doesnt
   await EventUtils.synthesizeNativeMouseMove(document.documentElement, 0, 0);
 });
 
 add_task(async function() {
-  Services.search.addEngineWithDetails("SearchEngine", "", "", "",
-                                       "GET", "http://s.example.com/search");
+  await Services.search.addEngineWithDetails("SearchEngine", "", "", "",
+    "GET", "http://s.example.com/search");
   gEngine = Services.search.getEngineByName("SearchEngine");
   gEngine.addParam("q", "{searchTerms}", null);
-  gOriginalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = gEngine;
+  gOriginalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(gEngine);
 
   let uri = NetUtil.newURI("http://s.example.com/search?q=foobar&client=1");
   await PlacesTestUtils.addVisits({ uri, title: "Foo - SearchEngine Search" });
 
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
 
   // The first autocomplete result has the action searchengine, while
   // the second result is the "search favicon" element.
--- a/browser/components/urlbar/tests/legacy/browser_urlbarAddonIframe.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarAddonIframe.js
@@ -100,17 +100,17 @@ add_task(async function() {
     promiseEvent("input")[1],
     promiseEvent("reset")[1],
     promiseEvent("result")[1],
     promiseAutocompleteResultPopup(value, window, true),
   ]);
 
   // Check the heuristic result.
   let result = promiseValues[2];
-  let engineName = Services.search.defaultEngine.name;
+  let engineName = (await Services.search.getDefault()).name;
   Assert.deepEqual(
     PlacesUtils.parseActionUrl(result.url),
     {
       type: "searchengine",
       params: {
         engineName,
         input: "test",
         searchQuery: "test",
--- a/browser/components/urlbar/tests/legacy/browser_urlbarOneOffs.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarOneOffs.js
@@ -5,17 +5,17 @@ let gMaxResults;
 add_task(async function init() {
   Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
   gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
 
   // Add a search suggestion engine and move it to the front so that it appears
   // as the first one-off.
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  Services.search.moveEngine(engine, 0);
+  await Services.search.moveEngine(engine, 0);
 
   registerCleanupFunction(async function() {
     await hidePopup();
     await PlacesUtils.history.clear();
   });
 
   await PlacesUtils.history.clear();
 
@@ -162,26 +162,26 @@ add_task(async function() {
 add_task(async function searchWith() {
   let typedValue = "foo";
   await promiseAutocompleteResultPopup(typedValue);
   await waitForAutocompleteResultAt(0);
   assertState(0, -1, typedValue);
 
   let item = gURLBar.popup.richlistbox.firstElementChild;
   Assert.equal(item._actionText.textContent,
-               "Search with " + Services.search.defaultEngine.name,
+               "Search with " + (await Services.search.getDefault()).name,
                "Sanity check: first result's action text");
 
   // Alt+Down to the first one-off.  Now the first result and the first one-off
   // should both be selected.
   EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true });
   assertState(0, 0, typedValue);
 
   let engineName = gURLBar.popup.oneOffSearchButtons.selectedButton.engine.name;
-  Assert.notEqual(engineName, Services.search.defaultEngine.name,
+  Assert.notEqual(engineName, (await Services.search.getDefault()).name,
                   "Sanity check: First one-off engine should not be " +
                   "the current engine");
   Assert.equal(item._actionText.textContent,
                "Search with " + engineName,
                "First result's action text should be updated");
 
   await hidePopup();
 });
@@ -229,18 +229,18 @@ add_task(async function oneOffReturn() {
   await resultsPromise;
 
   gBrowser.removeTab(gBrowser.selectedTab);
 });
 
 add_task(async function collapsedOneOffs() {
   // Disable all the engines but the current one, check the oneoffs are
   // collapsed and that moving up selects the last match.
-  let engines = Services.search.getVisibleEngines()
-                               .filter(e => e.name != Services.search.defaultEngine.name);
+  let defaultEngine = await Services.search.getDefault();
+  let engines = (await Services.search.getVisibleEngines()).filter(e => e.name != defaultEngine.name);
   await SpecialPowers.pushPrefEnv({"set": [
     [ "browser.search.hiddenOneOffs", engines.map(e => e.name).join(",") ],
   ]});
 
   let typedValue = "foo";
   await promiseAutocompleteResultPopup(typedValue, window, true);
   await waitForAutocompleteResultAt(0);
   assertState(0, -1);
--- a/browser/components/urlbar/tests/legacy/browser_urlbarOneOffs_searchSuggestions.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarOneOffs_searchSuggestions.js
@@ -3,21 +3,21 @@ const TEST_ENGINE_BASENAME = "searchSugg
 add_task(async function init() {
   await PlacesUtils.history.clear();
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.oneOffSearches", true],
           ["browser.urlbar.suggest.searches", true]],
   });
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.moveEngine(engine, 0);
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.moveEngine(engine, 0);
+  await Services.search.setDefault(engine);
   registerCleanupFunction(async function() {
-    Services.search.defaultEngine = oldCurrentEngine;
+    await Services.search.setDefault(oldDefaultEngine);
 
     await PlacesUtils.history.clear();
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
 });
 
--- a/browser/components/urlbar/tests/legacy/browser_urlbarOneOffs_settings.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarOneOffs_settings.js
@@ -8,17 +8,17 @@ let gMaxResults;
 add_task(async function init() {
   Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
   gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
 
   // Add a search suggestion engine and move it to the front so that it appears
   // as the first one-off.
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  Services.search.moveEngine(engine, 0);
+  await Services.search.moveEngine(engine, 0);
 
   registerCleanupFunction(async function() {
     await hidePopup();
     await PlacesUtils.history.clear();
   });
 
   await PlacesUtils.history.clear();
 
--- a/browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions.js
@@ -2,21 +2,21 @@ const SUGGEST_URLBAR_PREF = "browser.url
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
 // Must run first.
 add_task(async function prepare() {
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
-    Services.search.defaultEngine = oldCurrentEngine;
+    await Services.search.setDefault(oldDefaultEngine);
 
     // Clicking suggestions causes visits to search results pages, so clear that
     // history now.
     await PlacesUtils.history.clear();
 
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
@@ -28,17 +28,17 @@ add_task(async function clickSuggestion(
   gURLBar.focus();
   await promiseAutocompleteResultPopup("foo");
   let [idx, suggestion, engineName] = await promiseFirstSuggestion();
   Assert.equal(engineName,
                "browser_searchSuggestionEngine%20searchSuggestionEngine.xml",
                "Expected suggestion engine");
   let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
 
-  let uri = Services.search.defaultEngine.getSubmission(suggestion).uri;
+  let uri = (await Services.search.getDefault()).getSubmission(suggestion).uri;
   let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
                                                    false, uri.spec);
   item.click();
   await loadPromise;
   BrowserTestUtils.removeTab(tab);
 });
 
 async function testPressEnterOnSuggestion(expectedUrl = null, keyModifiers = {}) {
@@ -46,17 +46,17 @@ async function testPressEnterOnSuggestio
   gURLBar.focus();
   await promiseAutocompleteResultPopup("foo");
   let [idx, suggestion, engineName] = await promiseFirstSuggestion();
   Assert.equal(engineName,
                "browser_searchSuggestionEngine%20searchSuggestionEngine.xml",
                "Expected suggestion engine");
 
   if (!expectedUrl) {
-    expectedUrl = Services.search.defaultEngine.getSubmission(suggestion).uri.spec;
+    expectedUrl = (await Services.search.getDefault()).getSubmission(suggestion).uri.spec;
   }
 
   let promiseLoad =
     BrowserTestUtils.waitForDocLoadAndStopIt(expectedUrl, gBrowser.selectedBrowser);
 
   for (let i = 0; i < idx; ++i) {
     EventUtils.synthesizeKey("KEY_ArrowDown");
   }
--- a/browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions_opt-out.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarSearchSuggestions_opt-out.js
@@ -9,29 +9,29 @@ const CHOICE_PREF = "browser.urlbar.user
 const TIMES_PREF = "browser.urlbar.timesBeforeHidingSuggestionsHint";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 const ONEOFF_PREF = "browser.urlbar.oneOffSearches";
 const NO_RESULTS_TIMEOUT_MS = 500;
 
 add_task(async function prepare() {
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   let defaults = Services.prefs.getDefaultBranch("browser.urlbar.");
   let searchSuggestionsDefault = defaults.getBoolPref("suggest.searches");
   defaults.setBoolPref("suggest.searches", true);
   let suggestionsChoice = Services.prefs.getBoolPref(CHOICE_PREF);
   Services.prefs.setBoolPref(CHOICE_PREF, false);
   let oneOffs = Services.prefs.getBoolPref(ONEOFF_PREF);
   Services.prefs.setBoolPref(ONEOFF_PREF, true);
   registerCleanupFunction(async function() {
     defaults.setBoolPref("suggest.searches", searchSuggestionsDefault);
-    Services.search.defaultEngine = oldCurrentEngine;
+    await Services.search.setDefault(oldDefaultEngine);
     Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
     Services.prefs.setBoolPref(CHOICE_PREF, suggestionsChoice);
     Services.prefs.setBoolPref(ONEOFF_PREF, oneOffs);
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
--- a/browser/components/urlbar/tests/legacy/browser_urlbarSearchTelemetry.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarSearchTelemetry.js
@@ -4,22 +4,22 @@ const SUGGEST_URLBAR_PREF = "browser.url
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
 
 // Must run first.
 add_task(async function prepare() {
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 
   registerCleanupFunction(async function() {
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
-    Services.search.defaultEngine = oldCurrentEngine;
+    await Services.search.setDefault(oldDefaultEngine);
 
     // Clicking urlbar results causes visits to their associated pages, so clear
     // that history now.
     await PlacesUtils.history.clear();
 
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
@@ -103,17 +103,17 @@ add_task(async function searchSuggestion
  *   and click a result.
  */
 async function compareCounts(clickCallback) {
   // Search events triggered by clicks (not the Return key in the urlbar) are
   // recorded in three places:
   // * Telemetry histogram named "SEARCH_COUNTS"
   // * FHR
 
-  let engine = Services.search.defaultEngine;
+  let engine = await Services.search.getDefault();
   let engineID = "org.mozilla.testsearchsuggestions";
 
   // First, get the current counts.
 
   // telemetry histogram SEARCH_COUNTS
   let histogramCount = 0;
   let histogramKey = engineID + ".urlbar";
   let histogram;
--- a/browser/components/urlbar/tests/legacy/browser_urlbarStopSearchOnSelection.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarStopSearchOnSelection.js
@@ -16,21 +16,21 @@ const TEST_ENGINE_NUM_EXPECTED_RESULTS =
 add_task(async function init() {
   await PlacesUtils.history.clear();
   await SpecialPowers.pushPrefEnv({
     set: [["browser.urlbar.suggest.searches", true]],
   });
   // Add a test search engine that returns suggestions on a delay.
   let engine = await SearchTestUtils.promiseNewSearchEngine(
     getRootDirectory(gTestPath) + TEST_ENGINE_BASENAME);
-  let oldCurrentEngine = Services.search.defaultEngine;
-  Services.search.moveEngine(engine, 0);
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.moveEngine(engine, 0);
+  await Services.search.setDefault(engine);
   registerCleanupFunction(async () => {
-    Services.search.defaultEngine = oldCurrentEngine;
+    await Services.search.setDefault(oldDefaultEngine);
     await PlacesUtils.history.clear();
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
 });
 
 add_task(async function mainTest() {
--- a/browser/components/urlbar/tests/legacy/browser_urlbarTokenAlias.js
+++ b/browser/components/urlbar/tests/legacy/browser_urlbarTokenAlias.js
@@ -3,23 +3,23 @@
 
 // This test checks "@" search engine aliases ("token aliases") in the urlbar.
 
 "use strict";
 
 const ALIAS = "@test";
 
 add_task(async function init() {
-  Services.search.addEngineWithDetails("Test", {
+  await Services.search.addEngineWithDetails("Test", {
     alias: ALIAS,
     template: "http://example.com/?search={searchTerms}",
   });
   registerCleanupFunction(async function() {
     let engine = Services.search.getEngineByName("Test");
-    Services.search.removeEngine(engine);
+    await Services.search.removeEngine(engine);
     // Make sure the popup is closed for the next test.
     gURLBar.handleRevert();
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
 });
 
 
@@ -145,17 +145,17 @@ add_task(async function inputDoesntMatch
 
 
 // Selecting a non-heuristic (non-first) search engine result with an alias and
 // empty query should put the alias in the urlbar and highlight it.
 add_task(async function nonHeuristicAliases() {
   // Get the list of token alias engines (those with aliases that start with
   // "@").
   let tokenEngines = [];
-  for (let engine of Services.search.getEngines()) {
+  for (let engine of await Services.search.getEngines()) {
     let aliases = [];
     if (engine.alias) {
       aliases.push(engine.alias);
     }
     aliases.push(...engine.wrappedJSObject._internalAliases);
     let tokenAliases = aliases.filter(a => a.startsWith("@"));
     if (tokenAliases.length) {
       tokenEngines.push({ engine, tokenAliases });
--- a/browser/components/urlbar/tests/unit/head.js
+++ b/browser/components/urlbar/tests/unit/head.js
@@ -153,17 +153,16 @@ function makeTestServer(port = -1) {
  */
 async function addTestEngine(basename, httpServer = undefined) {
   httpServer = httpServer || makeTestServer();
   httpServer.registerDirectory("/", do_get_cwd());
   let dataUrl =
     "http://localhost:" + httpServer.identity.primaryPort + "/data/";
 
   info("Adding engine: " + basename);
-  await new Promise(resolve => Services.search.init(resolve));
   return new Promise(resolve => {
     Services.obs.addObserver(function obs(subject, topic, data) {
       let engine = subject.QueryInterface(Ci.nsISearchEngine);
       info("Observed " + data + " for " + engine.name);
       if (data != "engine-added" || engine.name != basename) {
         return;
       }
 
--- a/browser/components/urlbar/tests/unit/test_UrlbarUtils_getShortcutOrURIAndPostData.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarUtils_getShortcutOrURIAndPostData.js
@@ -133,23 +133,25 @@ async function setupKeywords() {
   for (let item of testData) {
     let data = item[0];
     if (data instanceof bmKeywordData) {
       await PlacesUtils.bookmarks.insert({ url: data.uri, parentGuid: folder.guid });
       await PlacesUtils.keywords.insert({ keyword: data.keyword, url: data.uri.spec, postData: data.postData });
     }
 
     if (data instanceof searchKeywordData) {
-      Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
+      await Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
       let addedEngine = Services.search.getEngineByName(data.keyword);
       if (data.postData) {
         let [paramName, paramValue] = data.postData.split("=");
         addedEngine.addParam(paramName, paramValue, null);
       }
       gAddedEngines.push(addedEngine);
     }
   }
 }
 
 async function cleanupKeywords() {
   await PlacesUtils.bookmarks.remove(folder);
-  gAddedEngines.map(Services.search.removeEngine);
+  for (let engine of gAddedEngines)
+    await Services.search.removeEngine(engine);
+  gAddedEngines = [];
 }
--- 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,
@@ -550,14 +550,13 @@ var ContentSearch = {
       if (!(prop in data)) {
         throw new Error("Message data missing required property: " + prop);
       }
     }
   },
 
   _initService() {
     if (!this._initServicePromise) {
-      this._initServicePromise =
-        new Promise(resolve => Services.search.init(resolve));
+      this._initServicePromise = Services.search.init();
     }
     return this._initServicePromise;
   },
 };
--- a/browser/modules/test/browser/browser_ContentSearch.js
+++ b/browser/modules/test/browser/browser_ContentSearch.js
@@ -6,94 +6,96 @@ const TEST_MSG = "ContentSearchTest";
 const CONTENT_SEARCH_MSG = "ContentSearch";
 const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
 
 /* import-globals-from ../../../components/search/test/browser/head.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/browser/components/search/test/browser/head.js",
   this);
 
-let originalEngine = Services.search.defaultEngine;
+var originalEngine;
 
 add_task(async function setup() {
+  originalEngine = await Services.search.getDefault();
+
   await SpecialPowers.pushPrefEnv({
     set: [["browser.newtab.preload", false]],
   });
 
   await promiseNewEngine("testEngine.xml", {
     setAsCurrent: true,
     testPath: "chrome://mochitests/content/browser/browser/components/search/test/browser/",
   });
 
-  registerCleanupFunction(() => {
-    Services.search.defaultEngine = originalEngine;
+  registerCleanupFunction(async () => {
+    await Services.search.setDefault(originalEngine);
   });
 });
 
 add_task(async function GetState() {
   let { mm } = await addTab();
   mm.sendAsyncMessage(TEST_MSG, {
     type: "GetState",
   });
   let msg = await waitForTestMsg(mm, "State");
   checkMsg(msg, {
     type: "State",
     data: await currentStateObj(),
   });
 });
 
-add_task(async function SetCurrentEngine() {
+add_task(async function SetDefaultEngine() {
   let { mm } = await addTab();
-  let newCurrentEngine = null;
-  let oldCurrentEngine = Services.search.defaultEngine;
-  let engines = Services.search.getVisibleEngines();
+  let newDefaultEngine = null;
+  let oldDefaultEngine = await Services.search.getDefault();
+  let engines = await Services.search.getVisibleEngines();
   for (let engine of engines) {
-    if (engine != oldCurrentEngine) {
-      newCurrentEngine = engine;
+    if (engine != oldDefaultEngine) {
+      newDefaultEngine = engine;
       break;
     }
   }
-  if (!newCurrentEngine) {
+  if (!newDefaultEngine) {
     info("Couldn't find a non-selected search engine, " +
          "skipping this part of the test");
     return;
   }
   mm.sendAsyncMessage(TEST_MSG, {
     type: "SetCurrentEngine",
-    data: newCurrentEngine.name,
+    data: newDefaultEngine.name,
   });
   let deferred = PromiseUtils.defer();
   Services.obs.addObserver(function obs(subj, topic, data) {
     info("Test observed " + data);
     if (data == "engine-current") {
       ok(true, "Test observed engine-current");
       Services.obs.removeObserver(obs, "browser-search-engine-modified");
       deferred.resolve();
     }
   }, "browser-search-engine-modified");
   let searchPromise = waitForTestMsg(mm, "CurrentEngine");
   info("Waiting for test to observe engine-current...");
   await deferred.promise;
   let msg = await searchPromise;
   checkMsg(msg, {
     type: "CurrentEngine",
-    data: await currentEngineObj(newCurrentEngine),
+    data: await defaultEngineObj(newDefaultEngine),
   });
 
-  Services.search.defaultEngine = oldCurrentEngine;
+  await Services.search.setDefault(oldDefaultEngine);
   msg = await waitForTestMsg(mm, "CurrentEngine");
   checkMsg(msg, {
     type: "CurrentEngine",
-    data: await currentEngineObj(oldCurrentEngine),
+    data: await defaultEngineObj(oldDefaultEngine),
   });
 });
 
 add_task(async function modifyEngine() {
   let { mm } = await addTab();
-  let engine = Services.search.defaultEngine;
+  let engine = await Services.search.getDefault();
   let oldAlias = engine.alias;
   engine.alias = "ContentSearchTest";
   let msg = await waitForTestMsg(mm, "CurrentState");
   checkMsg(msg, {
     type: "CurrentState",
     data: await currentStateObj(),
   });
   engine.alias = oldAlias;
@@ -101,17 +103,17 @@ add_task(async function modifyEngine() {
   checkMsg(msg, {
     type: "CurrentState",
     data: await currentStateObj(),
   });
 });
 
 add_task(async function search() {
   let { browser } = await addTab();
-  let engine = Services.search.defaultEngine;
+  let engine = await Services.search.getDefault();
   let data = {
     engineName: engine.name,
     searchString: "ContentSearchTest",
     healthReportKey: "ContentSearchTest",
     searchPurpose: "ContentSearchTest",
   };
   let submissionURL =
     engine.getSubmission(data.searchString, "", data.whence).uri.spec;
@@ -120,17 +122,17 @@ add_task(async function search() {
 });
 
 add_task(async function searchInBackgroundTab() {
   // This test is like search(), but it opens a new tab after starting a search
   // in another.  In other words, it performs a search in a background tab.  The
   // search page should be loaded in the same tab that performed the search, in
   // the background tab.
   let { browser } = await addTab();
-  let engine = Services.search.defaultEngine;
+  let engine = await Services.search.getDefault();
   let data = {
     engineName: engine.name,
     searchString: "ContentSearchTest",
     healthReportKey: "ContentSearchTest",
     searchPurpose: "ContentSearchTest",
   };
   let submissionURL =
     engine.getSubmission(data.searchString, "", data.whence).uri.spec;
@@ -159,17 +161,17 @@ add_task(async function badImage() {
      "Sanity check: icon array buffer of engine in expected state " +
      "should be null: " + expectedEngine.iconBuffer);
   checkMsg(finalCurrentStateMsg, {
     type: "CurrentState",
     data: expectedCurrentState,
   });
   // Removing the engine triggers a final CurrentState message.  Wait for it so
   // it doesn't trip up subsequent tests.
-  Services.search.removeEngine(engine);
+  await Services.search.removeEngine(engine);
   await waitForTestMsg(mm, "CurrentState");
 });
 
 add_task(async function GetSuggestions_AddFormHistoryEntry_RemoveFormHistoryEntry() {
   let { mm } = await addTab();
 
   // Add the test engine that provides suggestions.
   let vals = await waitForNewEngine(mm, "contentSearchSuggestions.xml", 0);
@@ -244,17 +246,17 @@ add_task(async function GetSuggestions_A
       engineName: engine.name,
       searchString: searchStr,
       formHistory: [],
       remote: [searchStr + "foo", searchStr + "bar"],
     },
   });
 
   // Finally, clean up by removing the test engine.
-  Services.search.removeEngine(engine);
+  await Services.search.removeEngine(engine);
   await waitForTestMsg(mm, "CurrentState");
 });
 
 async function performSearch(browser, data, expectedURL) {
   let mm = browser.messageManager;
   let stoppedPromise = BrowserTestUtils.browserStopped(browser, expectedURL);
   mm.sendAsyncMessage(TEST_MSG, {
     type: "Search",
@@ -328,61 +330,49 @@ function waitForNewEngine(mm, basename, 
   let expectedSearchEvents = ["CurrentState", "CurrentState"];
   // engine-changed for each of the images
   for (let i = 0; i < numImages; i++) {
     expectedSearchEvents.push("CurrentState");
   }
   let eventPromises = expectedSearchEvents.map(e => waitForTestMsg(mm, e));
 
   // Wait for addEngine().
-  let addDeferred = PromiseUtils.defer();
   let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, "", false, {
-    onSuccess(engine) {
-      info("Search engine added: " + basename);
-      addDeferred.resolve(engine);
-    },
-    onError(errCode) {
-      ok(false, "addEngine failed with error code " + errCode);
-      addDeferred.reject();
-    },
-  });
-
-  return Promise.all([addDeferred.promise].concat(eventPromises));
+  return Promise.all([Services.search.addEngine(url, "", false)].concat(eventPromises));
 }
 
 async function addTab() {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab");
   registerCleanupFunction(() => gBrowser.removeTab(tab));
 
   let url = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
   let mm = tab.linkedBrowser.messageManager;
   mm.loadFrameScript(url, false);
   return { browser: tab.linkedBrowser, mm };
 }
 
 var currentStateObj = async function() {
   let state = {
     engines: [],
-    currentEngine: await currentEngineObj(),
+    currentEngine: await defaultEngineObj(),
   };
-  for (let engine of Services.search.getVisibleEngines()) {
+  for (let engine of await Services.search.getVisibleEngines()) {
     let uri = engine.getIconURLBySize(16, 16);
     state.engines.push({
       name: engine.name,
       iconBuffer: await arrayBufferFromDataURI(uri),
       hidden: false,
       identifier: engine.identifier,
     });
   }
   return state;
 };
 
-var currentEngineObj = async function() {
-  let engine = Services.search.defaultEngine;
+var defaultEngineObj = async function() {
+  let engine = await Services.search.getDefault();
   let uriFavicon = engine.getIconURLBySize(16, 16);
   let bundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
   return {
     name: engine.name,
     placeholder: bundle.formatStringFromName("searchWithEngine", [engine.name], 1),
     iconBuffer: await arrayBufferFromDataURI(uriFavicon),
   };
 };
--- a/browser/modules/test/browser/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content.js
@@ -3,47 +3,47 @@
 const BASE_PROBE_NAME = "browser.engagement.navigation.";
 const SCALAR_CONTEXT_MENU = BASE_PROBE_NAME + "contextmenu";
 const SCALAR_ABOUT_NEWTAB = BASE_PROBE_NAME + "about_newtab";
 
 add_task(async function setup() {
   // Create two new search engines. Mark one as the default engine, so
   // the test don't crash. We need to engines for this test as the searchbar
   // in content doesn't display the default search engine among the one-off engines.
-  Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
-  Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
   // Make the first engine the default search engine.
   let engineDefault = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engineDefault;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engineDefault);
 
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
-  Services.search.moveEngine(engineOneOff, 0);
+  await Services.search.moveEngine(engineOneOff, 0);
 
   await SpecialPowers.pushPrefEnv({"set": [
     ["dom.select_events.enabled", true], // We want select events to be fired.
   ]});
 
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(async function() {
-    Services.search.defaultEngine = originalEngine;
-    Services.search.removeEngine(engineDefault);
-    Services.search.removeEngine(engineOneOff);
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engineDefault);
+    await Services.search.removeEngine(engineOneOff);
     await PlacesUtils.history.clear();
     Services.telemetry.setEventRecordingEnabled("navigation", false);
     Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 add_task(async function test_context_menu() {
   // Let's reset the Telemetry data.
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
@@ -7,43 +7,43 @@ add_task(async function setup() {
   // allow it enougth time to save. So it throws. This disables all the uncaught
   // exception in this file and that's the reason why we split about:home tests
   // out of the other UsageTelemetry files.
   ignoreAllUncaughtExceptions();
 
   // Create two new search engines. Mark one as the default engine, so
   // the test don't crash. We need to engines for this test as the searchbar
   // in content doesn't display the default search engine among the one-off engines.
-  Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
-  Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
   // Make the first engine the default search engine.
   let engineDefault = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engineDefault;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engineDefault);
 
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
-  Services.search.moveEngine(engineOneOff, 0);
+  await Services.search.moveEngine(engineOneOff, 0);
 
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(async function() {
-    Services.search.defaultEngine = originalEngine;
-    Services.search.removeEngine(engineDefault);
-    Services.search.removeEngine(engineOneOff);
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engineDefault);
+    await Services.search.removeEngine(engineOneOff);
     await PlacesUtils.history.clear();
     Services.telemetry.setEventRecordingEnabled("navigation", false);
     Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 add_task(async function test_abouthome_activitystream_simpleQuery() {
   // Let's reset the counts.
--- a/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
@@ -56,44 +56,44 @@ add_task(async function setup() {
   await gCUITestUtils.addSearchBar();
   registerCleanupFunction(() => {
     gCUITestUtils.removeSearchBar();
   });
 
   // Create two new search engines. Mark one as the default engine, so
   // the test don't crash. We need to engines for this test as the searchbar
   // doesn't display the default search engine among the one-off engines.
-  Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
-  Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
   // Make the first engine the default search engine.
   let engineDefault = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engineDefault;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engineDefault);
 
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
-  Services.search.moveEngine(engineOneOff, 0);
+  await Services.search.moveEngine(engineOneOff, 0);
 
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
-  registerCleanupFunction(function() {
+  registerCleanupFunction(async function() {
     Services.telemetry.canRecordExtended = oldCanRecord;
-    Services.search.defaultEngine = originalEngine;
-    Services.search.removeEngine(engineDefault);
-    Services.search.removeEngine(engineOneOff);
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engineDefault);
+    await Services.search.removeEngine(engineOneOff);
     Services.telemetry.setEventRecordingEnabled("navigation", false);
   });
 });
 
 add_task(async function test_plainQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
@@ -191,25 +191,19 @@ add_task(async function test_oneOff_ente
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   let resultMethodHist =
     TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
-  let suggestionEngine = await new Promise((resolve, reject) => {
-    Services.search.addEngine(url, "", false, {
-      onSuccess(engine) { resolve(engine); },
-      onError() { reject(); },
-    });
-  });
-
-  let previousEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = suggestionEngine;
+  let suggestionEngine = await Services.search.addEngine(url, "", false);
+  let previousEngine = await Services.search.getDefault();
+  await Services.search.setDefault(suggestionEngine);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Type a query. Suggestions should be generated by the test engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("query");
 
   info("Select the second result, press Alt+Down to take us to the first one-off engine.");
@@ -218,18 +212,18 @@ add_task(async function test_oneOff_ente
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enterSelection,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
-  Services.search.defaultEngine = previousEngine;
-  Services.search.removeEngine(suggestionEngine);
+  await Services.search.setDefault(previousEngine);
+  await Services.search.removeEngine(suggestionEngine);
   BrowserTestUtils.removeTab(tab);
 });
 
 // Performs a search using a click on a one-off button.  This only tests the
 // FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
 // everything else.
 add_task(async function test_oneOff_click() {
   // Let's reset the counts.
@@ -262,25 +256,19 @@ add_task(async function test_suggestion_
   let resultMethodHist =
     TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
   let search_hist =
     TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
-  let suggestionEngine = await new Promise((resolve, reject) => {
-    Services.search.addEngine(url, "", false, {
-      onSuccess(engine) { resolve(engine); },
-      onError() { reject(); },
-    });
-  });
-
-  let previousEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = suggestionEngine;
+  let suggestionEngine = await Services.search.addEngine(url, "", false);
+  let previousEngine = await Services.search.getDefault();
+  await Services.search.setDefault(suggestionEngine);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("query");
   info("Clicking the searchbar suggestion.");
   clickSearchbarSuggestion("queryfoo");
@@ -305,55 +293,49 @@ add_task(async function test_suggestion_
   ]);
 
   // Check the histograms as well.
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.click,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
-  Services.search.defaultEngine = previousEngine;
-  Services.search.removeEngine(suggestionEngine);
+  await Services.search.setDefault(previousEngine);
+  await Services.search.removeEngine(suggestionEngine);
   BrowserTestUtils.removeTab(tab);
 });
 
 // Selects and presses the Return (Enter) key on the first suggestion offered by
 // the test search engine.  This only tests the
 // FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_suggestion_click
 // covers everything else.
 add_task(async function test_suggestion_enterSelection() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   let resultMethodHist =
     TelemetryTestUtils.getAndClearHistogram("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
-  let suggestionEngine = await new Promise((resolve, reject) => {
-    Services.search.addEngine(url, "", false, {
-      onSuccess(engine) { resolve(engine); },
-      onError() { reject(); },
-    });
-  });
-
-  let previousEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = suggestionEngine;
+  let suggestionEngine = await Services.search.addEngine(url, "", false);
+  let previousEngine = await Services.search.getDefault();
+  await Services.search.setDefault(suggestionEngine);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Type a query. Suggestions should be generated by the test engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await searchInSearchbar("query");
   info("Select the second result and press Return.");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   EventUtils.synthesizeKey("KEY_Enter");
   await p;
 
   let resultMethods = resultMethodHist.snapshot();
   checkHistogramResults(resultMethods,
     URLBAR_SELECTED_RESULT_METHODS.enterSelection,
     "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
 
-  Services.search.defaultEngine = previousEngine;
-  Services.search.removeEngine(suggestionEngine);
+  await Services.search.setDefault(previousEngine);
+  await Services.search.removeEngine(suggestionEngine);
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -67,49 +67,43 @@ function clickURLBarSuggestion(entryName
 }
 
 /**
  * Create an engine to generate search suggestions and add it as default
  * for this test.
  */
 async function withNewSearchEngine(taskFn) {
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
-  let suggestionEngine = await new Promise((resolve, reject) => {
-    Services.search.addEngine(url, "", false, {
-      onSuccess(engine) { resolve(engine); },
-      onError() { reject(); },
-    });
-  });
-
-  let previousEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = suggestionEngine;
+  let suggestionEngine = await Services.search.addEngine(url, "", false);
+  let previousEngine = await Services.search.getDefault();
+  await Services.search.setDefault(suggestionEngine);
 
   try {
     await taskFn(suggestionEngine);
   } finally {
-    Services.search.defaultEngine = previousEngine;
-    Services.search.removeEngine(suggestionEngine);
+    await Services.search.setDefault(previousEngine);
+    await Services.search.removeEngine(suggestionEngine);
   }
 }
 
 add_task(async function setup() {
   // Create a new search engine.
-  Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
-                                       "http://example.com/?q={searchTerms}");
+  await Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+                                             "http://example.com/?q={searchTerms}");
 
   // Make it the default search engine.
   let engine = Services.search.getEngineByName("MozSearch");
-  let originalEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = engine;
+  let originalEngine = await Services.search.getDefault();
+  await Services.search.setDefault(engine);
 
   // Give it some mock internal aliases.
   engine.wrappedJSObject.__internalAliases = ["@mozaliasfoo", "@mozaliasbar"];
 
   // And the first one-off engine.
-  Services.search.moveEngine(engine, 0);
+  await Services.search.moveEngine(engine, 0);
 
   // Enable search suggestions in the urlbar.
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
 
   // Enable the urlbar one-off buttons.
   Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
 
@@ -129,18 +123,18 @@ add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.urlbar.maxHistoricalSearchSuggestions", 0]]});
 
   // Use the default matching bucket configuration.
   await SpecialPowers.pushPrefEnv({"set": [["browser.urlbar.matchBuckets", "general:5,suggestion:4"]]});
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(async function() {
     Services.telemetry.canRecordExtended = oldCanRecord;
-    Services.search.defaultEngine = originalEngine;
-    Services.search.removeEngine(engine);
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engine);
     Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled);
     Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
     await PlacesUtils.history.clear();
     Services.telemetry.setEventRecordingEnabled("navigation", false);
   });
 });
 
 add_task(async function test_simpleQuery() {
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -7,17 +7,17 @@
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIProtocolHandler.h"
 
 #include "nsIFile.h"
 #include <algorithm>
 
 #ifdef MOZ_TOOLKIT_SEARCH
-#  include "nsIBrowserSearchService.h"
+#  include "nsISearchService.h"
 #endif
 
 #include "nsIURIFixup.h"
 #include "nsIURIMutator.h"
 #include "nsDefaultURIFixup.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
@@ -436,17 +436,17 @@ 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 =
+  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
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -215,17 +215,17 @@
 #endif
 
 #if NS_PRINT_PREVIEW
 #  include "nsIDocumentViewerPrint.h"
 #  include "nsIWebBrowserPrint.h"
 #endif
 
 #ifdef MOZ_TOOLKIT_SEARCH
-#  include "nsIBrowserSearchService.h"
+#  include "nsISearchService.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::net;
 
 // Threshold value in ms for META refresh based redirects
 #define REFRESH_REDIRECT_TIMER 15000
@@ -13159,17 +13159,17 @@ void nsDocShell::MaybeNotifyKeywordSearc
     dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
     if (contentChild) {
       contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword);
     }
     return;
   }
 
 #ifdef MOZ_TOOLKIT_SEARCH
-  nsCOMPtr<nsIBrowserSearchService> searchSvc =
+  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
--- a/docshell/test/browser/browser_search_notification.js
+++ b/docshell/test/browser/browser_search_notification.js
@@ -1,30 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 add_task(async function () {
   const kSearchEngineID = "test_urifixup_search_engine";
   const kSearchEngineURL = "http://localhost/?search={searchTerms}";
-  Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
-                                       kSearchEngineURL);
+  await Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", kSearchEngineURL);
 
-  let oldDefaultEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID);
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(Services.search.getEngineByName(kSearchEngineID));
 
-  let selectedName = Services.search.defaultEngine.name;
+  let selectedName = (await Services.search.getDefault()).name;
   Assert.equal(selectedName, kSearchEngineID, "Check fake search engine is selected");
 
-  registerCleanupFunction(function() {
+  registerCleanupFunction(async function() {
     if (oldDefaultEngine) {
-      Services.search.defaultEngine = oldDefaultEngine;
+      await Services.search.setDefault(oldDefaultEngine);
     }
     let engine = Services.search.getEngineByName(kSearchEngineID);
     if (engine) {
-      Services.search.removeEngine(engine);
+      await Services.search.removeEngine(engine);
     }
   });
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   gBrowser.selectedTab = tab;
 
   gURLBar.value = "firefox health report";
   gURLBar.handleCommand();
--- a/docshell/test/browser/browser_uriFixupIntegration.js
+++ b/docshell/test/browser/browser_uriFixupIntegration.js
@@ -3,31 +3,30 @@
 
 "use strict";
 
 const kSearchEngineID = "browser_urifixup_search_engine";
 const kSearchEngineURL = "http://example.com/?search={searchTerms}";
 
 add_task(async function setup() {
   // Add a new fake search engine.
-  Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
-                                       kSearchEngineURL);
+  await Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", kSearchEngineURL);
 
-  let oldDefaultEngine = Services.search.defaultEngine;
-  Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID);
+  let oldDefaultEngine = await Services.search.getDefault();
+  await Services.search.setDefault(Services.search.getEngineByName(kSearchEngineID));
 
   // Remove the fake engine when done.
-  registerCleanupFunction(() => {
+  registerCleanupFunction(async () => {
     if (oldDefaultEngine) {
-      Services.search.defaultEngine = oldDefaultEngine;
+      await Services.search.setDefault(oldDefaultEngine);
     }
 
     let engine = Services.search.getEngineByName(kSearchEngineID);
     if (engine) {
-      Services.search.removeEngine(engine);
+      await Services.search.removeEngine(engine);
     }
   });
 });
 
 add_task(async function test() {
   for (let searchParams of ["foo bar", "brokenprotocol:somethingelse"]) {
     // Add a new blank tab.
     gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
--- a/docshell/test/unit/test_nsDefaultURIFixup_info.js
+++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js
@@ -1,43 +1,11 @@
-var prefList = ["browser.fixup.typo.scheme", "keyword.enabled",
-                "browser.fixup.domainwhitelist.whitelisted"];
-for (let pref of prefList) {
-  Services.prefs.setBoolPref(pref, true);
-}
-
 const kSearchEngineID = "test_urifixup_search_engine";
 const kSearchEngineURL = "http://www.example.org/?search={searchTerms}";
-Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
-                                     kSearchEngineURL);
-
-Services.io.getProtocolHandler("resource")
-        .QueryInterface(Ci.nsIResProtocolHandler)
-        .setSubstitution("search-plugins",
-                         Services.io.newURI("chrome://mozapps/locale/searchplugins/"));
-
-var oldDefaultEngine = Services.search.defaultEngine;
-Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID);
-
-var selectedName = Services.search.defaultEngine.name;
-Assert.equal(selectedName, kSearchEngineID);
-
 const kForceHostLookup = "browser.fixup.dns_first_for_single_words";
-registerCleanupFunction(function() {
-  if (oldDefaultEngine) {
-    Services.search.defaultEngine = oldDefaultEngine;
-  }
-  let engine = Services.search.getEngineByName(kSearchEngineID);
-  if (engine) {
-    Services.search.removeEngine(engine);
-  }
-  Services.prefs.clearUserPref("keyword.enabled");
-  Services.prefs.clearUserPref("browser.fixup.typo.scheme");
-  Services.prefs.clearUserPref(kForceHostLookup);
-});
 
 // TODO(bug 1522134), this test should also use
 // combinations of the following flags.
 var flagInputs = [
   Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
   Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI,
   Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS,
 ];
@@ -519,32 +487,65 @@ if (Services.appinfo.OS.toLowerCase().st
     protocolChange: true,
   });
 }
 
 function sanitize(input) {
   return input.replace(/\r|\n/g, "").trim();
 }
 
+add_task(async function setup() {
+  var prefList = ["browser.fixup.typo.scheme", "keyword.enabled",
+                  "browser.fixup.domainwhitelist.whitelisted"];
+  for (let pref of prefList) {
+    Services.prefs.setBoolPref(pref, true);
+  }
+
+  Services.io.getProtocolHandler("resource")
+          .QueryInterface(Ci.nsIResProtocolHandler)
+          .setSubstitution("search-plugins",
+                           Services.io.newURI("chrome://mozapps/locale/searchplugins/"));
+
+  await Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", kSearchEngineURL);
+
+  var oldCurrentEngine = await Services.search.getDefault();
+  await Services.search.setDefault(Services.search.getEngineByName(kSearchEngineID));
+
+  var selectedName = (await Services.search.getDefault()).name;
+  Assert.equal(selectedName, kSearchEngineID);
+
+  registerCleanupFunction(async function() {
+    if (oldCurrentEngine) {
+      await Services.search.setDefault(oldCurrentEngine);
+    }
+    let engine = Services.search.getEngineByName(kSearchEngineID);
+    if (engine) {
+      await Services.search.removeEngine(engine);
+    }
+    Services.prefs.clearUserPref("keyword.enabled");
+    Services.prefs.clearUserPref("browser.fixup.typo.scheme");
+    Services.prefs.clearUserPref(kForceHostLookup);
+  });
+});
 
 var gSingleWordHostLookup = false;
-function run_test() {
+add_task(async function run_test() {
   // Only keywordlookup things should be affected by requiring a DNS lookup for single-word hosts:
   info("Check only keyword lookup testcases should be affected by requiring DNS for single hosts");
   let affectedTests = testcases.filter(t => !t.keywordLookup && t.affectedByDNSForSingleHosts);
   if (affectedTests.length) {
     for (let testcase of affectedTests) {
       info("Affected: " + testcase.input);
     }
   }
   Assert.equal(affectedTests.length, 0);
   do_single_test_run();
   gSingleWordHostLookup = true;
   do_single_test_run();
-}
+});
 
 function do_single_test_run() {
   Services.prefs.setBoolPref(kForceHostLookup, gSingleWordHostLookup);
 
   let relevantTests = gSingleWordHostLookup ? testcases.filter(t => t.keywordLookup) :
                                               testcases;
 
   for (let { input: testInput,
--- a/docshell/test/unit/test_nsDefaultURIFixup_search.js
+++ b/docshell/test/unit/test_nsDefaultURIFixup_search.js
@@ -1,38 +1,12 @@
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
-Services.prefs.setBoolPref("keyword.enabled", true);
-
 const kSearchEngineID = "test_urifixup_search_engine";
 const kSearchEngineURL = "http://www.example.org/?search={searchTerms}";
-Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
-                                     kSearchEngineURL);
-
-Services.io.getProtocolHandler("resource")
-        .QueryInterface(Ci.nsIResProtocolHandler)
-        .setSubstitution("search-plugins",
-                         Services.io.newURI("chrome://mozapps/locale/searchplugins/"));
-
-var oldDefaultEngine = Services.search.defaultEngine;
-Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID);
-
-var selectedName = Services.search.defaultEngine.name;
-Assert.equal(selectedName, kSearchEngineID);
-
-registerCleanupFunction(function() {
-  if (oldDefaultEngine) {
-    Services.search.defaultEngine = oldDefaultEngine;
-  }
-  let engine = Services.search.getEngineByName(kSearchEngineID);
-  if (engine) {
-    Services.search.removeEngine(engine);
-  }
-  Services.prefs.clearUserPref("keyword.enabled");
-});
 
 var isWin = AppConstants.platform == "win";
 
 var data = [
   {
     // Valid should not be changed.
     wrong: "https://example.com/this/is/a/test.html",
     fixed: "https://example.com/this/is/a/test.html",
@@ -98,16 +72,44 @@ var extProtocolSvc = Cc["@mozilla.org/ur
 if (extProtocolSvc && extProtocolSvc.externalProtocolHandlerExists("mailto")) {
   data.push({
     wrong: "mailto:foo@bar.com",
     fixed: "mailto:foo@bar.com",
   });
 }
 
 var len = data.length;
+
+add_task(async function setup() {
+  Services.prefs.setBoolPref("keyword.enabled", true);
+  Services.io.getProtocolHandler("resource")
+          .QueryInterface(Ci.nsIResProtocolHandler)
+          .setSubstitution("search-plugins",
+                           Services.io.newURI("chrome://mozapps/locale/searchplugins/"));
+
+  await Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", kSearchEngineURL);
+
+  var oldCurrentEngine = await Services.search.getDefault();
+  await Services.search.setDefault(Services.search.getEngineByName(kSearchEngineID));
+
+  var selectedName = (await Services.search.getDefault()).name;
+  Assert.equal(selectedName, kSearchEngineID);
+
+  registerCleanupFunction(async function() {
+    if (oldCurrentEngine) {
+      await Services.search.setDefault(oldCurrentEngine);
+    }
+    let engine = Services.search.getEngineByName(kSearchEngineID);
+    if (engine) {
+      await Services.search.removeEngine(engine);
+    }
+    Services.prefs.clearUserPref("keyword.enabled");
+  });
+});
+
 // Make sure we fix what needs fixing
 add_task(function test_fix_unknown_schemes() {
   for (let i = 0; i < len; ++i) {
     let item = data[i];
     let result =
       Services.uriFixup.createFixupURI(item.wrong,
                               Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS).spec;
     Assert.equal(result, item.fixed);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -256,17 +256,17 @@
 #  if defined(XP_LINUX)
 #    include "mozilla/SandboxInfo.h"
 #    include "mozilla/SandboxBroker.h"
 #    include "mozilla/SandboxBrokerPolicyFactory.h"
 #  endif
 #endif
 
 #ifdef MOZ_TOOLKIT_SEARCH
-#  include "nsIBrowserSearchService.h"
+#  include "nsISearchService.h"
 #endif
 
 #ifdef XP_WIN
 #  include "mozilla/audio/AudioNotificationSender.h"
 #  include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef ACCESSIBILITY
@@ -4134,17 +4134,17 @@ 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 =
+  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) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1026,17 +1026,17 @@ var BrowserApp = {
       let name;
       if (Services.prefs.prefHasUserValue("browser.search.defaultenginename")) {
         name = Services.prefs.getCharPref("browser.search.defaultenginename");
       }
       if (!name && Services.prefs.prefHasUserValue("browser.search.defaultenginename.US")) {
         name = Services.prefs.getCharPref("browser.search.defaultenginename.US");
       }
       if (name) {
-        Services.search.init(() => {
+        Services.search.init().then(() => {
           let engine = Services.search.getEngineByName(name);
           if (engine) {
             Services.search.defaultEngine = engine;
             Services.obs.notifyObservers(null, "default-search-engine-migrated");
           }
         });
       }
     }
@@ -4129,67 +4129,65 @@ Tab.prototype = {
           tabID: this.id
         };
         GlobalEventDispatcher.sendRequest(cache);
       } catch (err) {
         Log.e("Browser", "error when makeManifestMessage:" + err);
       }
   },
 
-  sendOpenSearchMessage: function(eventTarget) {
+  sendOpenSearchMessage: async function(eventTarget) {
     let type = eventTarget.type && eventTarget.type.toLowerCase();
     // Replace all starting or trailing spaces or spaces before "*;" globally w/ "".
     type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
 
     // Check that type matches opensearch.
     let isOpenSearch = (type == "application/opensearchdescription+xml");
     if (isOpenSearch && eventTarget.title && /^(?:https?|ftp):/i.test(eventTarget.href)) {
-      Services.search.init(() => {
-        let visibleEngines = Services.search.getVisibleEngines();
-        // NOTE: Engines are currently identified by name, but this can be changed
-        // when Engines are identified by URL (see bug 335102).
-        if (visibleEngines.some(function(e) {
-          return e.name == eventTarget.title;
+      let visibleEngines = await Services.search.getVisibleEngines();
+      // NOTE: Engines are currently identified by name, but this can be changed
+      // when Engines are identified by URL (see bug 335102).
+      if (visibleEngines.some(function(e) {
+        return e.name == eventTarget.title;
+      })) {
+        // This engine is already present, do nothing.
+        return null;
+      }
+
+      if (this.browser.engines) {
+        // This engine has already been handled, do nothing.
+        if (this.browser.engines.some(function(e) {
+          return e.url == eventTarget.href;
         })) {
-          // This engine is already present, do nothing.
           return null;
         }
-
-        if (this.browser.engines) {
-          // This engine has already been handled, do nothing.
-          if (this.browser.engines.some(function(e) {
-            return e.url == eventTarget.href;
-          })) {
-            return null;
-          }
-        } else {
-            this.browser.engines = [];
-        }
-
-        // Get favicon.
-        let iconURL = eventTarget.ownerDocument.documentURIObject.prePath + "/favicon.ico";
-
-        let newEngine = {
-          title: eventTarget.title,
-          url: eventTarget.href,
-          iconURL: iconURL
-        };
-
-        this.browser.engines.push(newEngine);
-
-        // Don't send a message to display engines if we've already handled an engine.
-        if (this.browser.engines.length > 1)
-          return null;
-
-        // Broadcast message that this tab contains search engines that should be visible.
-        GlobalEventDispatcher.sendRequest({
-          type: "Link:OpenSearch",
-          tabID: this.id,
-          visible: true
-        });
+      } else {
+          this.browser.engines = [];
+      }
+
+      // Get favicon.
+      let iconURL = eventTarget.ownerDocument.documentURIObject.prePath + "/favicon.ico";
+
+      let newEngine = {
+        title: eventTarget.title,
+        url: eventTarget.href,
+        iconURL: iconURL
+      };
+
+      this.browser.engines.push(newEngine);
+
+      // Don't send a message to display engines if we've already handled an engine.
+      if (this.browser.engines.length > 1)
+        return null;
+
+      // Broadcast message that this tab contains search engines that should be visible.
+      GlobalEventDispatcher.sendRequest({
+        type: "Link:OpenSearch",
+        tabID: this.id,
+        visible: true
       });
     }
   },
 
   get parentId() {
     return this._parentId;
   },
 
@@ -5942,28 +5940,28 @@ var SearchEngines = {
       "SearchEngines:Remove",
       "SearchEngines:RestoreDefaults",
       "SearchEngines:SetDefault",
     ]);
     Services.obs.addObserver(this, "browser-search-engine-modified");
   },
 
   // Fetch list of search engines. all ? All engines : Visible engines only.
-  _handleSearchEnginesGetVisible: function _handleSearchEnginesGetVisible(rv, all) {
+  _handleSearchEnginesGetVisible: async function _handleSearchEnginesGetVisible(rv, all) {
     if (!Components.isSuccessCode(rv)) {
       Cu.reportError("Could not initialize search service, bailing out.");
       return;
     }
 
-    let engineData = Services.search.getVisibleEngines({});
+    let engineData = await Services.search.getVisibleEngines({});
 
     // Our Java UI assumes that the default engine is the first item in the array,
     // so we need to make sure that's the case.
-    if (engineData[0] !== Services.search.defaultEngine) {
-      engineData = engineData.filter(engine => engine !== Services.search.defaultEngine);
+    if (engineData[0].name !== Services.search.defaultEngine.name) {
+      engineData = engineData.filter(engine => engine.name !== Services.search.defaultEngine.name);
       engineData.unshift(Services.search.defaultEngine);
     }
 
     let searchEngines = engineData.map(function (engine) {
       return {
         name: engine.name,
         identifier: engine.identifier,
         iconURI: (engine.iconURI ? engine.iconURI.spec : null),
@@ -6006,34 +6004,33 @@ var SearchEngines = {
 
   onEvent: function (event, data, callback) {
     let engine;
     switch (event) {
       case "SearchEngines:Add":
         this.displaySearchEnginesList(data);
         break;
       case "SearchEngines:GetVisible":
-        Services.search.init(this._handleSearchEnginesGetVisible.bind(this));
+        Services.search.init().then(this._handleSearchEnginesGetVisible.bind(this));
         break;
       case "SearchEngines:Remove":
         // Make sure the engine isn't hidden before removing it, to make sure it's
         // visible if the user later re-adds it (works around bug 341833)
         engine = this._extractEngineFromJSON(data);
         engine.hidden = false;
         Services.search.removeEngine(engine);
         break;
       case "SearchEngines:RestoreDefaults":
         // Un-hides all default engines.
         Services.search.restoreDefaultEngines();
         break;
       case "SearchEngines:SetDefault":
         engine = this._extractEngineFromJSON(data);
         // Move the new default search engine to the top of the search engine list.
-        Services.search.moveEngine(engine, 0);
-        Services.search.defaultEngine = engine;
+        Services.search.moveEngine(engine, 0).then(() => Services.search.defaultEngine = engine);
         break;
       default:
         dump("Unexpected message type observed: " + event);
         break;
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
@@ -6045,17 +6042,17 @@ var SearchEngines = {
         break;
       default:
         dump("Unexpected message type observed: " + aTopic);
         break;
     }
   },
 
   migrateSearchActivityDefaultPref: function migrateSearchActivityDefaultPref() {
-    Services.search.init(() => this._setSearchActivityDefaultPref(Services.search.defaultEngine));
+    Services.search.init().then(() => this._setSearchActivityDefaultPref(Services.search.defaultEngine));
   },
 
   // Updates the search activity pref when the default engine changes.
   _setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
     SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, engine.name);
   },
 
   // Display context menu listing names of the search engines available to be added.
@@ -6087,37 +6084,34 @@ var SearchEngines = {
           visible: false
         };
 
         GlobalEventDispatcher.sendRequest(newEngineMessage);
       }
     });
   },
 
-  addOpenSearchEngine: function addOpenSearchEngine(engine) {
-    Services.search.addEngine(engine.url, engine.iconURL, false, {
-      onSuccess: function() {
-        // Display a toast confirming addition of new search engine.
-        Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [engine.title], 1), Snackbars.LENGTH_LONG);
-      },
-
-      onError: function(aCode) {
-        let errorMessage;
-        if (aCode == 2) {
-          // Engine is a duplicate.
-          errorMessage = "alertSearchEngineDuplicateToast";
-
-        } else {
-          // Unknown failure. Display general error message.
-          errorMessage = "alertSearchEngineErrorToast";
-        }
-
-        Snackbars.show(Strings.browser.formatStringFromName(errorMessage, [engine.title], 1), Snackbars.LENGTH_LONG);
-      }
-    });
+  addOpenSearchEngine: async function addOpenSearchEngine(engine) {
+    try {
+      await Services.search.addEngine(engine.url, engine.iconURL, false);
+      // Display a toast confirming addition of new search engine.
+      Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [engine.title], 1), Snackbars.LENGTH_LONG);
+    } catch (ex) {
+      let code = ex.result;
+      let errorMessage;
+      if (code == 2) {
+        // Engine is a duplicate.
+        errorMessage = "alertSearchEngineDuplicateToast";
+      } else {
+        // Unknown failure. Display general error message.
+        errorMessage = "alertSearchEngineErrorToast";
+      }
+
+      Snackbars.show(Strings.browser.formatStringFromName(errorMessage, [engine.title], 1), Snackbars.LENGTH_LONG);
+    }
   },
 
   /**
    * Build and return an array of sorted form data / Query Parameters
    * for an element in a submission form.
    *
    * @param element
    *        A valid submission element of a form.
@@ -6157,17 +6151,17 @@ var SearchEngines = {
             }
           }
         }
       }
     };
 
     // Return valid, pre-sorted queryParams.
     return formData.filter(a => a.name && a.value).sort((a, b) => {
-      // nsIBrowserSearchService.hasEngineWithURL() ensures sort, but this helps.
+      // nsISearchService.hasEngineWithURL() ensures sort, but this helps.
       if (a.name > b.name) {
         return 1;
       }
       if (b.name > a.name) {
         return -1;
       }
 
       if (a.value > b.value) {
@@ -6194,17 +6188,17 @@ var SearchEngines = {
     let charset = element.ownerDocument.characterSet;
     let docURI = Services.io.newURI(element.ownerDocument.URL, charset);
     let formURL = Services.io.newURI(form.getAttribute("action"), charset, docURI).spec;
 
     return Services.search.hasEngineWithURL(method, formURL, formData);
   },
 
   /**
-   * Adds a new search engine to the BrowserSearchService, based on its provided element. Prompts for an engine
+   * Adds a new search engine to the SearchService, based on its provided element. Prompts for an engine
    * name, and appends a simple version-number in case of collision with an existing name.
    *
    * @return callback to handle success value. Currently used for ActionBarHandler.js and UI updates.
    */
   addEngine: function addEngine(aElement, resultCallback) {
     let form = aElement.form;
     let charset = aElement.ownerDocument.characterSet;
     let docURI = Services.io.newURI(aElement.ownerDocument.URL, charset);
@@ -6217,38 +6211,38 @@ var SearchEngines = {
     let title = { value: (aElement.ownerDocument.title || docURI.displayHost) };
     if (!Services.prompt.prompt(null, promptTitle, null, title, null, {})) {
       if (resultCallback) {
         resultCallback(false);
       };
       return;
     }
 
-    Services.search.init(function addEngine_cb(rv) {
+    Services.search.init().then(function addEngine_cb(rv) {
       if (!Components.isSuccessCode(rv)) {
         Cu.reportError("Could not initialize search service, bailing out.");
         if (resultCallback) {
           resultCallback(false);
         };
         return;
       }
 
       GlobalEventDispatcher.sendRequestForResult({
         type: 'Favicon:Request',
         url: docURI.spec,
         skipNetwork: false
-      }).then(data => {
+      }).then(async data => {
         // if there's already an engine with this name, add a number to
         // make the name unique (e.g., "Google" becomes "Google 2")
         let name = title.value;
         for (let i = 2; Services.search.getEngineByName(name); i++) {
             name = title.value + " " + i;
         }
 
-        Services.search.addEngineWithDetails(name, data, null, null, method, formURL);
+        await Services.search.addEngineWithDetails(name, data, null, null, method, formURL);
         Snackbars.show(Strings.browser.formatStringFromName("alertSearchEngineAddedToast", [name], 1), Snackbars.LENGTH_LONG);
 
         let engine = Services.search.getEngineByName(name);
         engine.wrappedJSObject._queryCharset = charset;
         formData.forEach(param => { engine.addParam(param.name, param.value, null); });
 
         if (resultCallback) {
             return resultCallback(true);
@@ -6538,17 +6532,17 @@ var Distribution = {
         let array = new TextEncoder().encode(JSON.stringify(data));
         OS.File.writeAtomic(this._file.path, array, { tmpPath: this._file.path + ".tmp" });
         break;
       }
 
       case "Distribution:Changed":
         // Re-init the search service.
         try {
-          Services.search._asyncReInit();
+          Services.search._reInit();
         } catch (e) {
           console.log("Unable to reinit search service.");
         }
         // Fall through.
 
       case "Distribution:Set":
         if (data) {
           try {
--- a/mobile/android/tests/browser/robocop/testBrowserDiscovery.js
+++ b/mobile/android/tests/browser/robocop/testBrowserDiscovery.js
@@ -19,17 +19,18 @@ function setHandlerFunc(handler, test) {
 add_test(function setup_browser() {
   let BrowserApp = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
   do_register_cleanup(function cleanup() {
     BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
   });
 
   let url = "http://mochi.test:8888/tests/robocop/link_discovery.html";
   browser = BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }).browser;
-  browser.addEventListener("load", function(event) {
+  browser.addEventListener("load", async function(event) {
+    await Services.search.init();
     Services.tm.dispatchToMainThread(run_next_test);
   }, {capture: true, once: true});
 });
 
 var searchDiscoveryTests = [
   { text: "rel search discovered" },
   { rel: "SEARCH", text: "rel is case insensitive" },
   { rel: "-search-", pass: false, text: "rel -search- not discovered" },
@@ -53,19 +54,16 @@ function execute_search_test(test) {
     browser.engines = null;
   } else {
     ok(!test.pass, test.text);
   }
   run_next_test();
 }
 
 function prep_search_test(test) {
-  // Syncrhonously load the search service.
-  Services.search.getVisibleEngines();
-
   setHandlerFunc(execute_search_test, test);
 
   let rel = test.rel || "search";
   let href = test.href || "http://so.not.here.mozilla.com/search.xml";
   let type = test.type || "application/opensearchdescription+xml";
   let title = test.title;
   if (!("pass" in test)) {
     test.pass = true;
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -124,21 +124,16 @@ XPIDL_SOURCES += [
     'nsIURI.idl',
     'nsIURIMutator.idl',
     'nsIURIWithSpecialOrigin.idl',
     'nsIURL.idl',
     'nsIURLParser.idl',
     'nsPISocketTransportService.idl',
 ]
 
-if CONFIG['MOZ_TOOLKIT_SEARCH']:
-    XPIDL_SOURCES += [
-        'nsIBrowserSearchService.idl',
-    ]
-
 XPIDL_MODULE = 'necko'
 
 EXPORTS += [
     'netCore.h',
     'nsASocketHandler.h',
     'nsAsyncRedirectVerifyHelper.h',
     'nsFileStreams.h',
     'nsInputStreamPump.h',
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -284,16 +284,23 @@ var UninstallObserver = {
       // Clear the entry in the UUID map
       UUIDMap.remove(addon.id);
     }
   },
 };
 
 UninstallObserver.init();
 
+const manifestTypes = new Map([
+  ["theme", "manifest.ThemeManifest"],
+  ["langpack", "manifest.WebExtensionLangpackManifest"],
+  ["dictionary", "manifest.WebExtensionDictionaryManifest"],
+  ["extension", "manifest.WebExtensionManifest"],
+]);
+
 /**
  * Represents the data contained in an extension, contained either
  * in a directory or a zip file, which may or may not be installed.
  * This class implements the functionality of the Extension class,
  * primarily related to manifest parsing and localization, which is
  * useful prior to extension installation or initialization.
  *
  * No functionality of this class is guaranteed to work before
@@ -553,16 +560,66 @@ class ExtensionData {
       permissions: newPermissions.permissions.filter(perm => !oldPermissions.permissions.includes(perm)),
     };
   }
 
   canUseExperiment(manifest) {
     return this.experimentsAllowed && manifest.experiment_apis;
   }
 
+  /**
+   * Load a locale and return a localized manifest.  The extension must
+   * be initialized, and manifest parsed prior to calling.
+   *
+   * @param {string} locale to load, if necessary.
+   * @returns {object} normalized manifest.
+   */
+  async getLocalizedManifest(locale) {
+    if (!this.type || !this.localeData) {
+      throw new Error("The extension has not been initialized.");
+    }
+    if (!this.localeData.has(locale)) {
+      // Locales are not avialable until some additional
+      // initialization is done.  We could just call initAllLocales,
+      // but that is heavy handed, especially when we likely only
+      // need one out of 20.
+      let locales = await this.promiseLocales();
+      if (locales.get(locale)) {
+        await this.initLocale(locale);
+      }
+      if (!this.localeData.has(locale)) {
+        throw new Error(`The extension does not contain the locale ${locale}`);
+      }
+    }
+    let normalized = await this._getNormalizedManifest(locale);
+    if (normalized.error) {
+      throw new Error(normalized.error);
+    }
+    return normalized.value;
+  }
+
+  async _getNormalizedManifest(locale) {
+    let manifestType = manifestTypes.get(this.type);
+
+    let context = {
+      url: this.baseURI && this.baseURI.spec,
+      principal: this.principal,
+      logError: error => {
+        this.manifestWarning(error);
+      },
+      preprocessors: {},
+    };
+
+    if (this.localeData) {
+      context.preprocessors.localize = (value, context) => this.localize(value, locale);
+    }
+
+    return Schemas.normalize(this.rawManifest, manifestType, context);
+  }
+
   // eslint-disable-next-line complexity
   async parseManifest() {
     let [manifest] = await Promise.all([
       this.readJSON("manifest.json"),
       Management.lazyInit(),
     ]);
 
     this.manifest = manifest;
@@ -572,47 +629,27 @@ class ExtensionData {
         "incognito" in manifest && manifest.incognito == "not_allowed") {
       throw new Error(`manifest.incognito set to "not_allowed" is currently unvailable for use.`);
     }
 
     if (manifest && manifest.default_locale) {
       await this.initLocale();
     }
 
-    let context = {
-      url: this.baseURI && this.baseURI.spec,
-
-      principal: this.principal,
-
-      logError: error => {
-        this.manifestWarning(error);
-      },
-
-      preprocessors: {},
-    };
-
-    let manifestType = "manifest.WebExtensionManifest";
     if (this.manifest.theme) {
       this.type = "theme";
-      manifestType = "manifest.ThemeManifest";
     } else if (this.manifest.langpack_id) {
       this.type = "langpack";
-      manifestType = "manifest.WebExtensionLangpackManifest";
     } else if (this.manifest.dictionaries) {
       this.type = "dictionary";
-      manifestType = "manifest.WebExtensionDictionaryManifest";
     } else {
       this.type = "extension";
     }
 
-    if (this.localeData) {
-      context.preprocessors.localize = (value, context) => this.localize(value);
-    }
-
-    let normalized = Schemas.normalize(this.manifest, manifestType, context);
+    let normalized = await this._getNormalizedManifest();
     if (normalized.error) {
       this.manifestError(normalized.error);
       return null;
     }
 
     manifest = normalized.value;
 
     let id;
--- a/toolkit/components/extensions/test/xpcshell/test_locale_data.js
+++ b/toolkit/components/extensions/test/xpcshell/test_locale_data.js
@@ -123,8 +123,32 @@ add_task(async function testInvalidSynta
 
   equal(extension.errors.length, 2, "One error reported");
 
   info(`Got error: ${extension.errors[1]}`);
 
   ok(extension.errors[1].includes("Loading locale file _locales\/en_US\/messages\.json: SyntaxError"),
      "Got syntax error");
 });
+
+add_task(async function testExtractLocalizedManifest() {
+  let extension = await generateAddon({
+    "manifest": {
+      "name": "__MSG_extensionName__",
+      "default_locale": "en_US",
+    },
+
+    "files": {
+      "_locales/en_US/messages.json": '{"extensionName": {"message": "foo"}}',
+      "_locales/de_DE/messages.json": '{"extensionName": {"message": "bar"}}',
+    },
+  });
+
+  await extension.loadManifest();
+  equal(extension.manifest.name, "foo", "name localized");
+
+  let manifest = await extension.getLocalizedManifest("de-DE");
+  ok(extension.localeData.has("de-DE"), "has de_DE locale");
+  equal(manifest.name, "bar", "name localized");
+
+  await Assert.rejects(extension.getLocalizedManifest("xx-XX"),
+                       /does not contain the locale xx-XX/, "xx-XX does not exist");
+});
--- a/toolkit/components/normandy/actions/ShowHeartbeatAction.jsm
+++ b/toolkit/components/normandy/actions/ShowHeartbeatAction.jsm
@@ -160,24 +160,17 @@ class ShowHeartbeatAction extends BaseAc
     const { postAnswerUrl, message, includeTelemetryUUID } = recipe.arguments;
 
     // Don`t bother with empty URLs.
     if (!postAnswerUrl) {
       return postAnswerUrl;
     }
 
     const userId = ClientEnvironment.userId;
-    const searchEngine = await new Promise(resolve => {
-      Services.search.init(rv => {
-        if (Components.isSuccessCode(rv)) {
-          resolve(Services.search.defaultEngine.identifier);
-        }
-      });
-    });
-
+    const searchEngine = (await Services.search.getDefault()).identifier;
     const args = {
       fxVersion: Services.appinfo.version,
       isDefaultBrowser: ShellService.isDefaultBrowser() ? 1 : 0,
       searchEngine,
       source: "heartbeat",
       // `surveyversion` used to be the version of the heartbeat action when it
       // was hosted on a server. Keeping it around for compatibility.
       surveyversion: Services.appinfo.version,
--- a/toolkit/components/normandy/lib/NormandyDriver.jsm
+++ b/toolkit/components/normandy/lib/NormandyDriver.jsm
@@ -67,22 +67,19 @@ var NormandyDriver = function(sandboxMan
         syncTotalDevices: null,
         plugins: {},
         doNotTrack: Preferences.get("privacy.donottrackheader.enabled", false),
         distribution: Preferences.get("distribution.id", "default"),
       };
       appinfo.syncTotalDevices = appinfo.syncDesktopDevices + appinfo.syncMobileDevices;
 
       const searchEnginePromise = new Promise(resolve => {
-        Services.search.init(rv => {
-          if (Components.isSuccessCode(rv)) {
-            appinfo.searchEngine = Services.search.defaultEngine.identifier;
-          }
-          resolve();
-        });
+        Services.search.init().then(() => {
+          appinfo.searchEngine = Services.search.defaultEngine.identifier;
+        }).finally(resolve);
       });
 
       const pluginsPromise = (async () => {
         let plugins = await AddonManager.getAddonsByTypes(["plugin"]);
         plugins.forEach(plugin => appinfo.plugins[plugin.name] = {
           name: plugin.name,
           description: plugin.description,
           version: plugin.version,
--- a/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
+++ b/toolkit/components/places/PlacesSearchAutocompleteProvider.jsm
@@ -33,58 +33,51 @@ const SearchAutocompleteProviderInternal
   enginesByAlias: new Map(),
 
   /**
    * {array<{ {nsISearchEngine} engine, {array<string>} tokenAliases }>} Array
    * of engines that have "@" aliases.
    */
   tokenAliasEngines: [],
 
-  initialize() {
-    return new Promise((resolve, reject) => {
-      Services.search.init(status => {
-        if (!Components.isSuccessCode(status)) {
-          reject(new Error("Unable to initialize search service."));
-        }
+  async initialize() {
+    try {
+      await Services.search.init();
+    } catch (errorCode) {
+      throw new Error("Unable to initialize search service.");
+    }
 
-        try {
-          // The initial loading of the search engines must succeed.
-          this._refresh();
-
-          Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
+    // The initial loading of the search engines must succeed.
+    await this._refresh();
 
-          this.initialized = true;
-          resolve();
-        } catch (ex) {
-          reject(ex);
-        }
-      });
-    });
+    Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
+
+    this.initialized = true;
   },
 
   initialized: false,
 
   observe(subject, topic, data) {
     switch (data) {
       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 +248,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/places/tests/unifiedcomplete/head_autocomplete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
@@ -515,28 +515,24 @@ add_task(async function ensure_search_en
 
   // Initialize the search service, but first set this geo IP pref to a dummy
   // string.  When the search service is initialized, it contacts the URI named
   // in this pref, which breaks the test since outside connections aren't
   // allowed.
   let geoPref = "browser.search.geoip.url";
   Services.prefs.setCharPref(geoPref, "");
   registerCleanupFunction(() => Services.prefs.clearUserPref(geoPref));
-  await new Promise(resolve => {
-    Services.search.init(resolve);
-  });
-
   // Remove any existing engines before adding ours.
-  for (let engine of Services.search.getEngines()) {
-    Services.search.removeEngine(engine);
+  for (let engine of await Services.search.getEngines()) {
+    await Services.search.removeEngine(engine);
   }
-  Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
-                                       "http://s.example.com/search");
+  await Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+                                             "http://s.example.com/search");
   let engine = Services.search.getEngineByName("MozSearch");
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
 });
 
 /**
  * Add a adaptive result for a given (url, string) tuple.
  * @param {string} aUrl
  *        The url to add an adaptive result for.
  * @param {string} aSearch
  *        The string to add an adaptive result for.
--- a/toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_PlacesSearchAutocompleteProvider.js
@@ -2,62 +2,62 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {PlacesSearchAutocompleteProvider} = ChromeUtils.import("resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
 const {updateAppInfo} = ChromeUtils.import("resource://testing-common/AppInfo.jsm");
 updateAppInfo();
 
 add_task(async function() {
+  await Services.search.init();
     // Tell the search service we are running in the US.  This also has the
     // desired side-effect of preventing our geoip lookup.
    Services.prefs.setCharPref("browser.search.region", "US");
    Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
 
    Services.search.restoreDefaultEngines();
    Services.search.resetToOriginalDefaultEngine();
 });
 
 add_task(async function search_engine_match() {
-  let engine = await promiseDefaultSearchEngine();
+  let engine = await Services.search.getDefault();
   let domain = engine.getResultDomain();
   let token = domain.substr(0, 1);
   let matchedEngine =
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix(token);
   Assert.equal(matchedEngine, engine);
 });
 
 add_task(async function no_match() {
   Assert.equal(
     null,
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix("test")
   );
 });
 
 add_task(async function hide_search_engine_nomatch() {
-  let engine = await promiseDefaultSearchEngine();
+  let engine = await Services.search.getDefault();
   let domain = engine.getResultDomain();
   let token = domain.substr(0, 1);
   let promiseTopic = promiseSearchTopic("engine-changed");
-  Services.search.removeEngine(engine);
-  await promiseTopic;
+  await Promise.all([Services.search.removeEngine(engine), promiseTopic]);
   Assert.ok(engine.hidden);
   let matchedEngine =
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix(token);
   Assert.ok(!matchedEngine || matchedEngine.getResultDomain() != domain);
 });
 
 add_task(async function add_search_engine_match() {
   let promiseTopic = promiseSearchTopic("engine-added");
   Assert.equal(
     null,
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix("bacon")
   );
-  Services.search.addEngineWithDetails("bacon", "", "pork", "Search Bacon",
-                                       "GET", "http://www.bacon.moz/?search={searchTerms}");
+  await Promise.all([Services.search.addEngineWithDetails("bacon", "", "pork", "Search Bacon",
+    "GET", "http://www.bacon.moz/?search={searchTerms}"), promiseTopic]);
   await promiseTopic;
   let matchedEngine =
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix("bacon");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.searchForm, "http://www.bacon.moz");
   Assert.equal(matchedEngine.name, "bacon");
   Assert.equal(matchedEngine.iconURI, null);
 });
@@ -89,19 +89,18 @@ add_task(async function test_aliased_sea
 });
 
 add_task(async function test_aliased_search_engine_match_upper_case_alias() {
   let promiseTopic = promiseSearchTopic("engine-added");
   Assert.equal(
     null,
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix("patch")
   );
-  Services.search.addEngineWithDetails("patch", "", "PR", "Search Patch",
-                                       "GET", "http://www.patch.moz/?search={searchTerms}");
-  await promiseTopic;
+  await Promise.all([Services.search.addEngineWithDetails("patch", "", "PR", "Search Patch",
+    "GET", "http://www.patch.moz/?search={searchTerms}"), promiseTopic]);
   // lower case
   let matchedEngine =
     await PlacesSearchAutocompleteProvider.engineForAlias("pr");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "patch");
   Assert.equal(matchedEngine.alias, "PR");
   Assert.equal(matchedEngine.iconURI, null);
   // Upper case
@@ -116,28 +115,27 @@ add_task(async function test_aliased_sea
   Assert.equal(matchedEngine.name, "patch");
   Assert.equal(matchedEngine.alias, "PR");
   Assert.equal(matchedEngine.iconURI, null);
 });
 
 add_task(async function remove_search_engine_nomatch() {
   let engine = Services.search.getEngineByName("bacon");
   let promiseTopic = promiseSearchTopic("engine-removed");
-  Services.search.removeEngine(engine);
-  await promiseTopic;
+  await Promise.all([Services.search.removeEngine(engine), promiseTopic]);
   Assert.equal(
     null,
     await PlacesSearchAutocompleteProvider.engineForDomainPrefix("bacon")
   );
 });
 
 add_task(async function test_parseSubmissionURL_basic() {
   // Most of the logic of parseSubmissionURL is tested in the search service
   // itself, thus we only do a sanity check of the wrapper here.
-  let engine = await promiseDefaultSearchEngine();
+  let engine = await Services.search.getDefault();
   let submissionURL = engine.getSubmission("terms").uri.spec;
 
   let result = PlacesSearchAutocompleteProvider.parseSubmissionURL(submissionURL);
   Assert.equal(result.engineName, engine.name);
   Assert.equal(result.terms, "terms");
 
   result = PlacesSearchAutocompleteProvider.parseSubmissionURL("http://example.org/");
   Assert.equal(result, null);
@@ -145,24 +143,16 @@ add_task(async function test_parseSubmis
 
 add_task(async function test_builtin_aliased_search_engine_match() {
   let matchedEngine =
     await PlacesSearchAutocompleteProvider.engineForAlias("@google");
   Assert.ok(matchedEngine);
   Assert.equal(matchedEngine.name, "Google");
 });
 
-function promiseDefaultSearchEngine() {
-  return new Promise(resolve => {
-    Services.search.init( () => {
-      resolve(Services.search.defaultEngine);
-    });
-  });
-}
-
 function promiseSearchTopic(expectedVerb) {
   return new Promise(resolve => {
     Services.obs.addObserver( function observe(subject, topic, verb) {
       info("browser-search-engine-modified: " + verb);
       if (verb == expectedVerb) {
         Services.obs.removeObserver(observe, "browser-search-engine-modified");
         resolve();
       }
--- a/toolkit/components/places/tests/unifiedcomplete/test_autofill_search_engine_aliases.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_autofill_search_engine_aliases.js
@@ -6,24 +6,24 @@
 
 "use strict";
 
 const TEST_ENGINE_NAME = "test autofill aliases";
 const TEST_ENGINE_ALIAS = "@autofilltest";
 
 add_task(async function init() {
   // Add an engine with an "@" alias.
-  Services.search.addEngineWithDetails(TEST_ENGINE_NAME, {
+  await Services.search.addEngineWithDetails(TEST_ENGINE_NAME, {
     alias: TEST_ENGINE_ALIAS,
     template: "http://example.com/?search={searchTerms}",
   });
-  registerCleanupFunction(() => {
+  registerCleanupFunction(async () => {
     let engine = Services.search.getEngineByName(TEST_ENGINE_NAME);
     Assert.ok(engine);
-    Services.search.removeEngine(engine);
+    await Services.search.removeEngine(engine);
   });
 });
 
 // Searching for @autofi should autofill to @autofilltest.
 add_task(async function basic() {
   // Add a history visit that should normally match but for the fact that the
   // search uses an @ alias.  When an @ alias is autofilled, there should be no
   // other matches except the autofill heuristic match.
--- a/toolkit/components/places/tests/unifiedcomplete/test_autofill_search_engines.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_autofill_search_engines.js
@@ -10,18 +10,18 @@
 "use strict";
 
 add_task(async function searchEngines() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
 
   let schemes = ["http", "https"];
   for (let i = 0; i < schemes.length; i++) {
     let scheme = schemes[i];
-    Services.search.addEngineWithDetails("TestEngine", "", "", "", "GET",
-                                         scheme + "://www.example.com/");
+    await Services.search.addEngineWithDetails("TestEngine", "", "", "", "GET",
+                                               scheme + "://www.example.com/");
     let engine = Services.search.getEngineByName("TestEngine");
     engine.addParam("q", "{searchTerms}", null);
 
     await check_autocomplete({
       search: "ex",
       autofilled: "example.com/",
       completed: scheme + "://www.example.com/",
       matches: [{
@@ -164,13 +164,13 @@ add_task(async function searchEngines() 
 
     await check_autocomplete({
       search: "example/",
       autofilled: "example/",
       completed: "example/",
       matches: [],
     });
 
-    Services.search.removeEngine(engine);
+    await Services.search.removeEngine(engine);
   }
 
   await cleanup();
 });
--- a/toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js
@@ -31,111 +31,111 @@ add_task(async function test_trailing_sp
     completed: "mo ",
   });
 
   await cleanup();
 });
 
 add_task(async function test_searchEngine_autofill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("CakeSearch", "", "", "",
-                                       "GET", "http://cake.search/");
+  await Services.search.addEngineWithDetails("CakeSearch", "", "", "",
+                                             "GET", "http://cake.search/");
   let engine = Services.search.getEngineByName("CakeSearch");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   info("Should autoFill search engine if search string does not contains a space");
   await check_autocomplete({
     search: "ca",
     autofilled: "cake.search/",
     completed: "http://cake.search/",
   });
 
   await cleanup();
 });
 
 add_task(async function test_searchEngine_prefix_space_noautofill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("CupcakeSearch", "", "", "",
-                                       "GET", "http://cupcake.search/");
+  await Services.search.addEngineWithDetails("CupcakeSearch", "", "", "",
+                                             "GET", "http://cupcake.search/");
   let engine = Services.search.getEngineByName("CupcakeSearch");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   info("Should not try to autoFill search engine if search string contains a space");
   await check_autocomplete({
     search: " cu",
     autofilled: " cu",
     completed: " cu",
   });
 
   await cleanup();
 });
 
 add_task(async function test_searchEngine_trailing_space_noautofill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("BaconSearch", "", "", "",
-                                       "GET", "http://bacon.search/");
+  await Services.search.addEngineWithDetails("BaconSearch", "", "", "",
+                                             "GET", "http://bacon.search/");
   let engine = Services.search.getEngineByName("BaconSearch");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   info("Should not try to autoFill search engine if search string contains a space");
   await check_autocomplete({
     search: "ba ",
     autofilled: "ba ",
     completed: "ba ",
   });
 
   await cleanup();
 });
 
 add_task(async function test_searchEngine_www_noautofill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("HamSearch", "", "", "",
-                                       "GET", "http://ham.search/");
+  await Services.search.addEngineWithDetails("HamSearch", "", "", "",
+                                             "GET", "http://ham.search/");
   let engine = Services.search.getEngineByName("HamSearch");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   info("Should not autoFill search engine if search string contains www. but engine doesn't");
   await check_autocomplete({
     search: "www.ham",
     autofilled: "www.ham",
     completed: "www.ham",
   });
 
   await cleanup();
 });
 
 add_task(async function test_searchEngine_different_scheme_noautofill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("PieSearch", "", "", "",
-                                       "GET", "https://pie.search/");
+  await Services.search.addEngineWithDetails("PieSearch", "", "", "",
+                                             "GET", "https://pie.search/");
   let engine = Services.search.getEngineByName("PieSearch");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   info("Should not autoFill search engine if search string has a different scheme.");
   await check_autocomplete({
     search: "http://pie",
     autofilled: "http://pie",
     completed: "http://pie",
   });
 
   await cleanup();
 });
 
 add_task(async function test_searchEngine_matching_prefix_autofill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("BeanSearch", "", "", "",
-                                       "GET", "http://www.bean.search/");
+  await Services.search.addEngineWithDetails("BeanSearch", "", "", "",
+                                             "GET", "http://www.bean.search/");
   let engine = Services.search.getEngineByName("BeanSearch");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
 
   info("Should autoFill search engine if search string has matching prefix.");
   await check_autocomplete({
     search: "http://www.be",
     autofilled: "http://www.bean.search/",
     completed: "http://www.bean.search/",
   });
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_engine_alias.js
@@ -3,20 +3,20 @@
 
 const SUGGESTIONS_ENGINE_NAME = "engine-suggestions.xml";
 
 // Basic test that uses two engines, a GET engine and a POST engine, neither
 // providing search suggestions.
 add_task(async function basicGetAndPost() {
   // Note that head_autocomplete.js has already added a MozSearch engine.
   // Here we add another engine with a search alias.
-  Services.search.addEngineWithDetails("AliasedGETMozSearch", "", "get", "",
-                                       "GET", "http://s.example.com/search");
-  Services.search.addEngineWithDetails("AliasedPOSTMozSearch", "", "post", "",
-                                       "POST", "http://s.example.com/search");
+  await Services.search.addEngineWithDetails("AliasedGETMozSearch", "", "get", "",
+                                             "GET", "http://s.example.com/search");
+  await Services.search.addEngineWithDetails("AliasedPOSTMozSearch", "", "post", "",
+                                             "POST", "http://s.example.com/search");
 
   await PlacesTestUtils.addVisits("http://s.example.com/search?q=firefox");
   let historyMatch = {
     value: "http://s.example.com/search?q=firefox",
     comment: "test visit for http://s.example.com/search?q=firefox",
   };
 
   for (let alias of ["get", "post"]) {
@@ -205,17 +205,17 @@ add_task(async function engineWithSugges
   await cleanup();
 });
 
 
 // When the search is simply "@", the results should be a list of all the "@"
 // alias engines.
 add_task(async function tokenAliasEngines() {
   let tokenEngines = [];
-  for (let engine of Services.search.getEngines()) {
+  for (let engine of await Services.search.getEngines()) {
     let aliases = [];
     if (engine.alias) {
       aliases.push(engine.alias);
     }
     aliases.push(...engine.wrappedJSObject._internalAliases);
     let tokenAliases = aliases.filter(a => a.startsWith("@"));
     if (tokenAliases.length) {
       tokenEngines.push({ engine, tokenAliases });
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_engine_current.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_engine_current.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 
 add_task(async function() {
   // Note that head_autocomplete.js has already added a MozSearch engine.
   // Here we add another engine with a search alias.
-  Services.search.addEngineWithDetails("AliasedMozSearch", "", "doit", "",
-                                       "GET", "http://s.example.com/search");
+  await Services.search.addEngineWithDetails("AliasedMozSearch", "", "doit", "",
+                                             "GET", "http://s.example.com/search");
 
   info("search engine");
   await check_autocomplete({
     search: "mozilla",
     searchParam: "enable-actions",
     matches: [ makeSearchMatch("mozilla", { heuristic: true }) ],
   });
 
@@ -25,21 +25,21 @@ add_task(async function() {
   info("search engine, multiple words");
   await check_autocomplete({
     search: "mozzarella cheese",
     searchParam: "enable-actions",
     matches: [ makeSearchMatch("mozzarella cheese", { heuristic: true }) ],
   });
 
   info("search engine, after current engine has changed");
-  Services.search.addEngineWithDetails("MozSearch2", "", "", "", "GET",
-                                       "http://s.example.com/search2");
+  await Services.search.addEngineWithDetails("MozSearch2", "", "", "", "GET",
+                                             "http://s.example.com/search2");
   let engine = Services.search.getEngineByName("MozSearch2");
   notEqual(Services.search.defaultEngine, engine, "New engine shouldn't be the current engine yet");
-  Services.search.defaultEngine = engine;
+  await Services.search.setDefault(engine);
   await check_autocomplete({
     search: "mozilla",
     searchParam: "enable-actions",
     matches: [ makeSearchMatch("mozilla", { engineName: "MozSearch2", heuristic: true }) ],
   });
 
   await cleanup();
 });
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_engine_host.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_engine_host.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 add_task(async function test_searchEngine_autoFill() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", true);
-  Services.search.addEngineWithDetails("MySearchEngine", "", "", "",
-                                       "GET", "http://my.search.com/");
+  await Services.search.addEngineWithDetails("MySearchEngine", "", "", "",
+                                             "GET", "http://my.search.com/");
   let engine = Services.search.getEngineByName("MySearchEngine");
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   // Add an uri that matches the search string with high frecency.
   let uri = NetUtil.newURI("http://www.example.com/my/");
   let visits = [];
   for (let i = 0; i < 100; ++i) {
     visits.push({ uri, title: "Terms - SearchEngine Search" });
   }
   await PlacesTestUtils.addVisits(visits);
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_engine_restyle.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_engine_restyle.js
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 add_task(async function test_searchEngine() {
-  Services.search.addEngineWithDetails("SearchEngine", "", "", "",
-                                       "GET", "http://s.example.com/search");
+  await Services.search.addEngineWithDetails("SearchEngine", "", "", "",
+                                             "GET", "http://s.example.com/search");
   let engine = Services.search.getEngineByName("SearchEngine");
   engine.addParam("q", "{searchTerms}", null);
-  registerCleanupFunction(() => Services.search.removeEngine(engine));
+  registerCleanupFunction(async () => Services.search.removeEngine(engine));
 
   let uri1 = NetUtil.newURI("http://s.example.com/search?q=Terms&client=1");
   let uri2 = NetUtil.newURI("http://s.example.com/search?q=Terms&client=2");
   await PlacesTestUtils.addVisits({ uri: uri1, title: "Terms - SearchEngine Search" });
   await addBookmark({ uri: uri2, title: "Terms - SearchEngine Search" });
 
   info("Past search terms should be styled, unless bookmarked");
   Services.prefs.setBoolPref("browser.urlbar.restyleSearches", true);
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
@@ -30,19 +30,19 @@ add_task(async function setup() {
     return suggestionsFn(searchStr);
   });
   setSuggestionsFn(searchStr => {
     let suffixes = ["foo", "bar"];
     return [searchStr].concat(suffixes.map(s => searchStr + " " + s));
   });
 
   // Install the test engine.
-  let oldCurrentEngine = Services.search.defaultEngine;
-  registerCleanupFunction(() => Services.search.defaultEngine = oldCurrentEngine);
-  Services.search.defaultEngine = engine;
+  let oldDefaultEngine = await Services.search.getDefault();
+  registerCleanupFunction(async () => Services.search.setDefault(oldDefaultEngine));
+  Services.search.setDefault(engine);
 
   // We must make sure the FormHistoryStartup component is initialized.
   Cc["@mozilla.org/satchel/form-history-startup;1"]
     .getService(Ci.nsIObserver)
     .observe(null, "profile-after-change", null);
   await updateSearchHistory("bump", "hello Fred!");
   await updateSearchHistory("bump", "hello Barney!");
 });
--- 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/moz.build
+++ b/toolkit/components/search/moz.build
@@ -1,23 +1,30 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 
+if CONFIG['MOZ_TOOLKIT_SEARCH']:
+    XPIDL_SOURCES += [
+        'nsISearchService.idl',
+    ]
+
+XPIDL_MODULE = 'toolkit_search'
+
 EXTRA_COMPONENTS += [
     'nsSearchService.js',
     'nsSearchSuggestions.js',
 ]
 
-if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']:
-    DEFINES['HAVE_SIDEBAR'] = True
+if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']:
+    DEFINES['HAVE_SIDEBAR'] = True
     EXTRA_COMPONENTS += [
     'nsSidebar.js',
     ]
 
 EXTRA_JS_MODULES += [
     'SearchSuggestionController.jsm',
 ]
 
rename from netwerk/base/nsIBrowserSearchService.idl
rename to toolkit/components/search/nsISearchService.idl
--- a/netwerk/base/nsIBrowserSearchService.idl
+++ b/toolkit/components/search/nsISearchService.idl
@@ -231,29 +231,30 @@ 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(150ef720-bbe2-4169-b9f3-ef7ec0654ced)]
-interface nsIBrowserSearchService : nsISupports
+[scriptable, uuid(0301834b-2630-440e-8b98-db8dc55f34b9)]
+interface nsISearchService : nsISupports
 {
   /**
    * Start asynchronous initialization.
    *
-   * The callback is triggered once initialization is complete, which may be
+   * The promise is resolved 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.
+   * call to this method.
+   * This method should only be called when you need or want to wait for the
+   * full initialization of the search service, which may include waiting for
+   * outbound service requests.
    */
-  void init([optional] in nsIBrowserSearchInitObserver aObserver);
+  Promise init();
 
   /**
    * 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.
    *
@@ -262,17 +263,17 @@ interface nsIBrowserSearchService : nsIS
    * @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.
    */
-  void resetToOriginalDefaultEngine();
+  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".
@@ -301,30 +302,24 @@ interface nsIBrowserSearchService : nsIS
    *        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 callback
-   *        A nsISearchInstallCallback that will be notified when the
-   *        addition is complete, or if the addition fails. It will not be
-   *        called if addEngine throws an exception.
-   *
    * @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.
    */
-  void addEngine(in AString engineURL, in AString iconURL, in boolean confirm,
-                 [optional] in nsISearchInstallCallback callback,
-                 [optional] in AString extensionID);
+  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.
    *
@@ -371,25 +366,34 @@ interface nsIBrowserSearchService : nsIS
    *        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.
    *
    */
-  void 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 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);
 
-  void addEnginesFromExtension(in jsval extension);
+  /**
+   * Adds search providers to the search service.  If the search
+   * service is configured to load multiple locales for the extension,
+   * it may load more than one search engine.
+   *
+   * @param extension
+   *        The extension to load from.
+   * @returns Promise that resolves when finished.
+   */
+  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.
@@ -411,72 +415,63 @@ interface nsIBrowserSearchService : nsIS
    */
   nsISearchEngine getEngineByName(in AString aEngineName);
 
   /**
    * Returns an array of all installed search engines.
    *
    * @returns an array of nsISearchEngine objects.
    */
-  void getEngines(
-            [optional] out unsigned long engineCount,
-            [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+  Promise getEngines();
 
   /**
    * Returns an array of all installed search engines whose hidden attribute is
    * false.
    *
    * @returns an array of nsISearchEngine objects.
    */
-  void getVisibleEngines(
-            [optional] out unsigned long engineCount,
-            [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+  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.
    */
-  void getDefaultEngines(
-            [optional] out unsigned long engineCount,
-            [retval, array, size_is(engineCount)] out nsISearchEngine engines);
+  Promise getDefaultEngines();
 
   /**
    * Returns an array of search engines installed by a given extension.
    *
    * @returns an array of nsISearchEngine objects.
    */
-  void getEnginesByExtensionID(in AString extensionID,
-            [optional] out unsigned long engineCount,
-            [retval, array, size_is(engineCount)] out nsISearchEngine engines);
-
+  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.
    */
-  void moveEngine(in nsISearchEngine engine, in long newIndex);
+  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.
    */
-  void removeEngine(in nsISearchEngine engine);
+  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;
 
   /**
@@ -484,25 +479,27 @@ interface nsIBrowserSearchService : nsIS
    * 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).
    */
-  jsval getDefaultEngineInfo();
+  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.
@@ -511,18 +508,18 @@ interface nsIBrowserSearchService : nsIS
    *
    * @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 telemtry.
+   * 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);
 };
 
@@ -563,11 +560,9 @@ interface nsIBrowserSearchService : nsIS
  */
 #define SEARCH_ENGINE_CURRENT      "engine-current";
 
 /**
  * Sent when the "default" engine is changed.
  */
 #define SEARCH_ENGINE_DEFAULT      "engine-default";
 
-
-
 %}
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -62,29 +62,29 @@ const PERMS_FILE    = 0o644;
 const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 
 // We load plugins from APP_SEARCH_PREFIX, where a list.json
 // file needs to exist to list available engines.
 const APP_SEARCH_PREFIX = "resource://search-plugins/";
 const EXT_SEARCH_PREFIX = "resource://search-extensions/";
 
-// See documentation in nsIBrowserSearchService.idl.
+// See documentation in nsISearchService.idl.
 const SEARCH_ENGINE_TOPIC        = "browser-search-engine-modified";
 const TOPIC_LOCALES_CHANGE       = "intl:app-locales-changed";
 const QUIT_APPLICATION_TOPIC     = "quit-application";
 
 const SEARCH_ENGINE_REMOVED      = "engine-removed";
 const SEARCH_ENGINE_ADDED        = "engine-added";
 const SEARCH_ENGINE_CHANGED      = "engine-changed";
 const SEARCH_ENGINE_LOADED       = "engine-loaded";
 const SEARCH_ENGINE_CURRENT      = "engine-current";
 const SEARCH_ENGINE_DEFAULT      = "engine-default";
 
-// The following constants are left undocumented in nsIBrowserSearchService.idl
+// The following constants are left undocumented in nsISearchService.idl
 // For the moment, they are meant for testing/debugging purposes only.
 
 /**
  * Topic used for events involving the service itself.
  */
 const SEARCH_SERVICE_TOPIC       = "browser-search-service";
 
 /**
@@ -383,63 +383,63 @@ function isUSTimezone() {
   let UTCOffset = (new Date()).getTimezoneOffset();
   return UTCOffset >= 150 && UTCOffset <= 600;
 }
 
 // A method that tries to determine our region via an XHR geoip lookup.
 var ensureKnownRegion = async function(ss) {
   // If we have a region already stored in our prefs we trust it.
   let region = Services.prefs.getCharPref("browser.search.region", "");
-
-  if (!region) {
-    // We don't have it cached, so fetch it. fetchRegion() will call
-    // storeRegion if it gets a result (even if that happens after the
-    // promise resolves) and fetchRegionDefault.
-    await fetchRegion(ss);
-  } else {
-    // if nothing to do, return early.
-    if (!geoSpecificDefaultsEnabled())
-      return;
-
-    let expir = ss.getGlobalAttr("searchDefaultExpir") || 0;
-    if (expir > Date.now()) {
-      // The territory default we have already fetched hasn't expired yet.
+  try {
+    if (!region) {
+      // We don't have it cached, so fetch it. fetchRegion() will call
+      // storeRegion if it gets a result (even if that happens after the
+      // promise resolves) and fetchRegionDefault.
+      await fetchRegion(ss);
+    } else if (geoSpecificDefaultsEnabled()) {
+      // The territory default we have already fetched may have expired.
+      let expired = (ss.getGlobalAttr("searchDefaultExpir") || 0) <= Date.now();
       // If we have a default engine or a list of visible default engines
       // saved, the hashes should be valid, verify them now so that we can
       // refetch if they have been tampered with.
       let defaultEngine = ss.getVerifiedGlobalAttr("searchDefault");
       let visibleDefaultEngines = ss.getVerifiedGlobalAttr("visibleDefaultEngines");
-      if ((defaultEngine || defaultEngine === undefined) &&
-          (visibleDefaultEngines || visibleDefaultEngines === undefined)) {
-        // No geo defaults, or valid hashes; nothing to do.
-        return;
+      let hasValidHashes = (defaultEngine || defaultEngine === undefined) &&
+                           (visibleDefaultEngines || visibleDefaultEngines === undefined);
+      if (expired || !hasValidHashes) {
+        await new Promise(resolve => {
+          let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
+          let timerId = setTimeout(() => {
+            timerId = null;
+            resolve();
+          }, timeoutMS);
+
+          let callback = () => {
+            clearTimeout(timerId);
+            resolve();
+          };
+          fetchRegionDefault(ss).then(callback).catch(err => {
+            Cu.reportError(err);
+            callback();
+          });
+        });
       }
     }
 
-    await new Promise(resolve => {
-      let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
-      let timerId = setTimeout(() => {
-        timerId = null;
-        resolve();
-      }, timeoutMS);
-
-      let callback = () => {
-        clearTimeout(timerId);
-        resolve();
-      };
-      fetchRegionDefault(ss).then(callback).catch(err => {
-        Cu.reportError(err);
-        callback();
-      });
-    });
+    // If gInitialized is true then the search service was forced to perform
+    // a sync initialization during our XHRs - capture this via telemetry.
+    Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
+  } catch (ex) {
+    Cu.reportError(ex);
+  } finally {
+    // Since bug 1492475, we don't block our init flows on the region fetch as
+    // performed here. But we'd still like to unit-test its implementation, thus
+    // we fire this observer notification.
+    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "ensure-known-region-done");
   }
-
-  // If gInitialized is true then the search service was forced to perform
-  // a sync initialization during our XHRs - capture this via telemetry.
-  Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
 };
 
 // Store the result of the geoip request as well as any other values and
 // telemetry which depend on it.
 function storeRegion(region) {
   let isTimezoneUS = isUSTimezone();
   // If it's a US region, but not a US timezone, we don't store the value.
   // This works because no region defaults to ZZ (unknown) in nsURLFormatter
@@ -466,17 +466,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
@@ -683,17 +684,19 @@ var fetchRegionDefault = (ss) => new Pro
       LOG("fetchRegionDefault saved visibleDefaultEngines: " + string);
     }
 
     let interval = response.interval || SEARCH_GEO_DEFAULT_UPDATE_INTERVAL;
     let milliseconds = interval * 1000; // |interval| is in seconds.
     ss.setGlobalAttr("searchDefaultExpir", Date.now() + milliseconds);
 
     LOG("fetchRegionDefault got success response in " + took + "ms");
-    resolve();
+    // If we're doing this somewhere during the app's lifetime, reload the list
+    // of engines in order to pick up any geo-specific changes.
+    ss._maybeReloadEngines().finally(resolve);
   };
   request.ontimeout = function(event) {
     LOG("fetchRegionDefault: XHR finally timed-out");
     resolve();
   };
   request.onerror = function(event) {
     LOG("fetchRegionDefault: failed to retrieve territory default information");
     resolve();
@@ -849,19 +852,20 @@ function getMozParamPref(prefName) {
  * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
  * the state of the search service.
  *
  * @param aEngine
  *        The nsISearchEngine object to which the change applies.
  * @param aVerb
  *        A verb describing the change.
  *
- * @see nsIBrowserSearchService.idl
+ * @see nsISearchService.idl
  */
 var gInitialized = false;
+var gReinitializing = false;
 function notifyAction(aEngine, aVerb) {
   if (gInitialized) {
     LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
     Services.obs.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
   }
 }
 
 function parseJsonFromStream(aInputStream) {
@@ -1282,50 +1286,25 @@ Engine.prototype = {
   _iconUpdateURL: null,
   /* The extension ID if added by an extension. */
   _extensionID: null,
   // If the extension is builtin we treat it as a builtin search engine as well.
   // Both System and Distribution extensions are considered builtin for search engines.
   _isBuiltinExtension: false,
 
   /**
-   * Retrieves the data from the engine's file.
-   * The document element is placed in the engine's data field.
-   */
-  _initFromFile: function SRCH_ENG_initFromFile(file) {
-    if (!file || !file.exists())
-      FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
-
-    var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
-                       createInstance(Ci.nsIFileInputStream);
-
-    fileInStream.init(file, MODE_RDONLY, PERMS_FILE, false);
-
-    var domParser = new DOMParser();
-    var doc = domParser.parseFromStream(fileInStream, "UTF-8",
-                                        file.fileSize,
-                                        "text/xml");
-
-    this._data = doc.documentElement;
-    fileInStream.close();
-
-    // Now that the data is loaded, initialize the engine object
-    this._initFromData();
-  },
-
-  /**
    * Retrieves the data from the engine's file asynchronously.
    * The document element is placed in the engine's data field.
    *
    * @param file The file to load the search plugin from.
    *
    * @returns {Promise} A promise, resolved successfully if initializing from
    * data succeeds, rejected if it fails.
    */
-  async _asyncInitFromFile(file) {
+  async _initFromFile(file) {
     if (!file || !(await OS.File.exists(file.path)))
       FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
 
     let fileURI = Services.io.newFileURI(file);
     await this._retrieveSearchXMLData(fileURI.spec);
 
     // Now that the data is loaded, initialize the engine object
     this._initFromData();
@@ -1360,18 +1339,18 @@ Engine.prototype = {
   /**
    * Retrieves the engine data from a URI asynchronously and initializes it.
    *
    * @param uri The uri to load the search plugin from.
    *
    * @returns {Promise} A promise, resolved successfully if retrieveing data
    * succeeds.
    */
-  async _asyncInitFromURI(uri) {
-    LOG("_asyncInitFromURI: Loading engine from: \"" + uri.spec + "\".");
+  async _initFromURI(uri) {
+    LOG("_initFromURI: Loading engine from: \"" + uri.spec + "\".");
     await this._retrieveSearchXMLData(uri.spec);
     // Now that the data is loaded, initialize the engine object
     this._initFromData();
   },
 
   /**
    * Retrieves the engine data for a given URI asynchronously.
    *
@@ -1390,38 +1369,16 @@ Engine.prototype = {
       request.onerror = function(aEvent) {
         resolve();
       };
       request.open("GET", aURL, true);
       request.send();
     });
   },
 
-  _initFromURISync: function SRCH_ENG_initFromURISync(uri) {
-    ENSURE_WARN(uri instanceof Ci.nsIURI,
-                "Must have URI when calling _initFromURISync!",
-                Cr.NS_ERROR_UNEXPECTED);
-
-    ENSURE_WARN(uri.schemeIs("resource"), "_initFromURISync called for non-resource URI",
-                Cr.NS_ERROR_FAILURE);
-
-    LOG("_initFromURISync: Loading engine from: \"" + uri.spec + "\".");
-
-    var chan = makeChannel(uri);
-
-    var stream = chan.open2();
-    var parser = new DOMParser();
-    var doc = parser.parseFromStream(stream, "UTF-8", stream.available(), "text/xml");
-
-    this._data = doc.documentElement;
-
-    // Now that the data is loaded, initialize the engine object
-    this._initFromData();
-  },
-
   /**
    * Attempts to find an EngineURL object in the set of EngineURLs for
    * this Engine that has the given type string.  (This corresponds to the
    * "type" attribute in the "Url" node in the OpenSearch spec.)
    * This method will return the first matching URL object found, or null
    * if no matching URL is found.
    *
    * @param aType string to match the EngineURL's type attribute
@@ -1567,17 +1524,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;
         }
@@ -1907,17 +1864,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"));
@@ -1931,17 +1889,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);
   },
 
@@ -2631,55 +2590,33 @@ ParseSubmissionResult.prototype = {
     return this._termsLength;
   },
   QueryInterface: ChromeUtils.generateQI([Ci.nsISearchParseSubmissionResult]),
 };
 
 const gEmptyParseSubmissionResult =
       Object.freeze(new ParseSubmissionResult(null, "", -1, 0));
 
-function executeSoon(func) {
-  Services.tm.dispatchToMainThread(func);
-}
-
-/**
- * Check for sync initialization has completed or not.
- *
- * @param {aPromise} A promise.
- *
- * @returns the value returned by the invoked method.
- * @throws NS_ERROR_ALREADY_INITIALIZED if sync initialization has completed.
- */
-function checkForSyncCompletion(aPromise) {
-  return aPromise.then(function(aValue) {
-    if (gInitialized) {
-      throw Components.Exception("Synchronous fallback was called and has " +
-                                 "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
   // initialization is complete, only if an error has been encountered so far.
   _initRV: Cr.NS_OK,
 
   // The boolean indicates that the initialization has started or not.
   _initStarted: null,
 
+  _ensureKnownRegionPromise: null,
+
   // Reading the JSON cache file is the first thing done during initialization.
   // During the async init, we save it in a field so that if we have to do a
   // sync init before the async init finishes, we can avoid reading the cache
   // with sync disk I/O and handling lz4 decompression synchronously.
   // This is set back to null as soon as the initialization is finished.
   _cacheFileJSON: null,
 
   // If initialization has not been completed yet, perform synchronous
@@ -2689,99 +2626,70 @@ 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;
-    }
-  },
-
-  // 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;
-
-    let cache = this._readCacheFile();
-    if (cache.metaData)
-      this._metaData = cache.metaData;
-
-    try {
-      this._syncLoadEngines(cache);
-    } catch (ex) {
-      this._initRV = Cr.NS_ERROR_FAILURE;
-      LOG("_syncInit: failure loading engines: " + ex);
-    }
-    this._addObservers();
-
-    gInitialized = true;
-    this._cacheFileJSON = null;
-
-    this._initObservers.resolve(this._initRV);
-
-    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
-    Services.telemetry.getHistogramById("SEARCH_SERVICE_INIT_SYNC").add(true);
-
-    LOG("_syncInit end");
+    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;
   },
 
   /**
    * Asynchronous implementation of the initializer.
    *
+   * @param   [optional] skipRegionCheck
+   *          A boolean value indicating whether we should explicitly await the
+   *          the region check process to complete, which may be fetched remotely.
+   *          Pass in `false` if the caller needs to be absolutely certain of the
+   *          correct default engine and/ or ordering of visible engines.
    * @returns {Promise} A promise, resolved successfully if the initialization
    * succeeds.
    */
-  async _asyncInit() {
-    LOG("_asyncInit start");
+  async _init(skipRegionCheck) {
+    LOG("_init start");
 
     // See if we have a cache file so we don't have to parse a bunch of XML.
-    // Not using checkForSyncCompletion here because we want to ensure we
-    // fetch the region and geo specific defaults asynchronously even
-    // if a sync init has been forced.
-    let cache = await this._asyncReadCacheFile();
+    let cache = await this._readCacheFile();
+
+    // The init flow is not going to block on a fetch from an external service,
+    // but we're kicking it off as soon as possible to prevent UI flickering as
+    // much as possible.
+    this._ensureKnownRegionPromise = ensureKnownRegion(this)
+      .catch(ex => LOG("_init: failure determining region: " + ex))
+      .finally(() => this._ensureKnownRegionPromise = null);
+    if (!skipRegionCheck) {
+      await this._ensureKnownRegionPromise;
+    }
 
     try {
-      await checkForSyncCompletion(ensureKnownRegion(this));
+      await this._loadEngines(cache);
     } catch (ex) {
-      if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
-        throw ex;
-      }
-      LOG("_asyncInit: failure determining region: " + ex);
+      this._initRV = Cr.NS_ERROR_FAILURE;
+      LOG("_init: failure loading engines: " + ex);
     }
-    try {
-      await checkForSyncCompletion(this._asyncLoadEngines(cache));
-    } catch (ex) {
-      if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
-        throw ex;
-      }
-      this._initRV = Cr.NS_ERROR_FAILURE;
-      LOG("_asyncInit: failure loading engines: " + ex);
-    }
+    // Make sure the current list of engines is persisted, without the need to wait.
+    this._buildCache();
     this._addObservers();
     gInitialized = true;
-    this._cacheFileJSON = null;
-    this._initObservers.resolve(this._initRV);
+    if (Components.isSuccessCode(this._initRV)) {
+      this._initObservers.resolve(this._initRV);
+    } else {
+      this._initObservers.reject(this._initRV);
+    }
     Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
     Services.telemetry.getHistogramById("SEARCH_SERVICE_INIT_SYNC").add(false);
 
-    LOG("_asyncInit: Completed _asyncInit");
+    LOG("_init: Completed _init");
+    return this._initRV;
   },
 
   _metaData: { },
   setGlobalAttr(name, val) {
     this._metaData[name] = val;
     this.batchTask.disarm();
     this.batchTask.arm();
   },
@@ -2841,20 +2749,20 @@ 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() {
+  async _buildCache() {
     if (this._batchTask)
       this._batchTask.disarm();
 
     let cache = {};
     let locale = getLocale();
     let buildID = Services.appinfo.platformBuildID;
     let appVersion = Services.appinfo.version;
 
@@ -2880,50 +2788,59 @@ SearchService.prototype = {
 
     try {
       if (!cache.engines.length)
         throw "cannot write without any engine.";
 
       LOG("_buildCache: Writing to cache file.");
       let path = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
       let data = gEncoder.encode(JSON.stringify(cache));
-      let promise = OS.File.writeAtomic(path, data, {compression: "lz4",
-                                                     tmpPath: path + ".tmp"});
-
-      promise.then(
-        function onSuccess() {
-          Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, SEARCH_SERVICE_CACHE_WRITTEN);
-        },
-        function onError(e) {
-          LOG("_buildCache: failure during writeAtomic: " + e);
-        }
-      );
+      await OS.File.writeAtomic(path, data, {compression: "lz4",
+                                             tmpPath: path + ".tmp"});
+      LOG("_buildCache: cache file written to disk.");
+      Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, SEARCH_SERVICE_CACHE_WRITTEN);
     } catch (ex) {
       LOG("_buildCache: Could not write to cache file: " + ex);
     }
   },
 
-  _syncLoadEngines: function SRCH_SVC__syncLoadEngines(cache) {
-    LOG("_syncLoadEngines: start");
-    // See if we have a cache file so we don't have to parse a bunch of XML.
-    let chromeURIs = this._findJAREngines();
-
+  /**
+   * Loads engines asynchronously.
+   *
+   * @returns {Promise} A promise, resolved successfully if loading data
+   * succeeds.
+   */
+  async _loadEngines(cache) {
+    LOG("_loadEngines: start");
+    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
+    let chromeURIs = await this._findJAREngines();
+
+    // Get the non-empty distribution directories into distDirs...
     let distDirs = [];
     let locations;
     try {
       locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
                          Ci.nsISimpleEnumerator);
     } catch (e) {
       // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
       // so this throws during unit tests (but not xpcshell tests).
       locations = [];
     }
     for (let dir of locations) {
-      if (dir.directoryEntries.nextFile)
-        distDirs.push(dir);
+      let iterator = new OS.File.DirectoryIterator(dir.path,
+                                                   { winPattern: "*.xml" });
+      try {
+        // Add dir to distDirs if it contains any files.
+        let {done} = await iterator.next();
+        if (!done) {
+          distDirs.push(dir);
+        }
+      } finally {
+        iterator.close();
+      }
     }
 
     function notInCacheVisibleEngines(aEngineName) {
       return !cache.visibleDefaultEngines.includes(aEngineName);
     }
 
     let buildID = Services.appinfo.platformBuildID;
     let rebuildCache = !cache.engines ||
@@ -2939,240 +2856,171 @@ SearchService.prototype = {
       if (Object.keys(this._engines).length) {
         LOG("_loadEngines: done using existing cache");
         return;
       }
       LOG("_loadEngines: No valid engines found in cache. Loading engines from disk.");
     }
 
     LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
-    distDirs.forEach(this._loadEnginesFromDir, this);
-
-    this._loadFromChromeURLs(chromeURIs);
-
-    LOG("_loadEngines: load user-installed engines from the obsolete cache");
+    for (let loadDir of distDirs) {
+      let enginesFromDir = await this._loadEnginesFromDir(loadDir);
+      enginesFromDir.forEach(this._addEngineToStore, this);
+    }
+    let enginesFromURLs = await this._loadFromChromeURLs(chromeURIs);
+    enginesFromURLs.forEach(this._addEngineToStore, this);
+
+    LOG("_loadEngines: loading user-installed engines from the obsolete cache");
     this._loadEnginesFromCache(cache, true);
 
     this._loadEnginesMetadataFromCache(cache);
-    this._buildCache();
 
     LOG("_loadEngines: done using rebuilt cache");
   },
 
   /**
-   * Loads engines asynchronously.
+   * Reloads engines asynchronously, but only when the service has already been
+   * initialized.
    *
-   * @returns {Promise} A promise, resolved successfully if loading data
-   * succeeds.
+   * @return {Promise} A promise, resolved successfully if loading data succeeds.
    */
-  async _asyncLoadEngines(cache) {
-    LOG("_asyncLoadEngines: start");
-    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
-    let chromeURIs =
-      await checkForSyncCompletion(this._asyncFindJAREngines());
-
-    // Get the non-empty distribution directories into distDirs...
-    let distDirs = [];
-    let locations;
-    try {
-      locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
-                         Ci.nsISimpleEnumerator);
-    } catch (e) {
-      // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
-      // so this throws during unit tests (but not xpcshell tests).
-      locations = [];
+  async _maybeReloadEngines() {
+    // There's no point in already reloading the list of engines, when the service
+    // hasn't even initialized yet.
+    if (!gInitialized) {
+      return;
     }
-    for (let dir of locations) {
-      let iterator = new OS.File.DirectoryIterator(dir.path,
-                                                   { winPattern: "*.xml" });
-      try {
-        // Add dir to distDirs if it contains any files.
-        let {done} = await checkForSyncCompletion(iterator.next());
-        if (!done) {
-          distDirs.push(dir);
-        }
-      } finally {
-        iterator.close();
-      }
-    }
-
-    function notInCacheVisibleEngines(aEngineName) {
-      return !cache.visibleDefaultEngines.includes(aEngineName);
+
+    // Before we read the cache file, first make sure all pending tasks are clear.
+    if (this._batchTask) {
+      let task = this._batchTask;
+      this._batchTask = null;
+      await task.finalize();
     }
-
-    let buildID = Services.appinfo.platformBuildID;
-    let rebuildCache = !cache.engines ||
-                       cache.version != CACHE_VERSION ||
-                       cache.locale != getLocale() ||
-                       cache.buildID != buildID ||
-                       cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
-                       this._visibleDefaultEngines.some(notInCacheVisibleEngines);
-
-    if (!rebuildCache) {
-      LOG("_asyncLoadEngines: loading from cache directories");
-      this._loadEnginesFromCache(cache);
-      if (Object.keys(this._engines).length) {
-        LOG("_asyncLoadEngines: done using existing cache");
-        return;
-      }
-      LOG("_asyncLoadEngines: No valid engines found in cache. Loading engines from disk.");
+    // Capture the current engine state, in case we need to notify below.
+    let prevCurrentEngine = this._currentEngine;
+    this._currentEngine = null;
+
+    await this._loadEngines(await this._readCacheFile());
+    // Make sure the current list of engines is persisted.
+    await this._buildCache();
+
+    // If the defaultEngine has changed between the previous load and this one,
+    // dispatch the appropriate notifications.
+    if (prevCurrentEngine && this.defaultEngine !== prevCurrentEngine) {
+      notifyAction(this._currentEngine, SEARCH_ENGINE_DEFAULT);
+      notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
     }
-
-    LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
-    for (let loadDir of distDirs) {
-      let enginesFromDir =
-        await checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
-      enginesFromDir.forEach(this._addEngineToStore, this);
+    Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "engines-reloaded");
+  },
+
+  _reInit(origin) {
+    LOG("_reInit");
+    // Re-entrance guard, because we're using an async lambda below.
+    if (gReinitializing) {
+      LOG("_reInit: already re-initializing, bailing out.");
+      return;
     }
-    let enginesFromURLs =
-      await checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
-    enginesFromURLs.forEach(this._addEngineToStore, this);
-
-    LOG("_asyncLoadEngines: loading user-installed engines from the obsolete cache");
-    this._loadEnginesFromCache(cache, true);
-
-    this._loadEnginesMetadataFromCache(cache);
-    this._buildCache();
-
-    LOG("_asyncLoadEngines: done using rebuilt cache");
-  },
-
-  _asyncReInit() {
-    LOG("_asyncReInit");
+    gReinitializing = true;
+
     // Start by clearing the initialized state, so we don't abort early.
     gInitialized = false;
 
     (async () => {
       try {
+        this._initObservers = PromiseUtils.defer();
         if (this._batchTask) {
           LOG("finalizing batch task");
           let task = this._batchTask;
           this._batchTask = null;
-          await task.finalize();
+          // Tests manipulate the cache directly, so let's not double-write with
+          // stale cache data here.
+          if (origin == "test") {
+            task.disarm();
+          } else {
+            await task.finalize();
+          }
         }
 
         // Clear the engines, too, so we don't stick with the stale ones.
         this._engines = {};
         this.__sortedEngines = null;
         this._currentEngine = null;
         this._visibleDefaultEngines = [];
         this._searchDefault = null;
         this._searchOrder = [];
         this._metaData = {};
-        this._cacheFileJSON = null;
 
         // Tests that want to force a synchronous re-initialization need to
         // be notified when we are done uninitializing.
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC,
                                      "uninit-complete");
 
-        let cache = await this._asyncReadCacheFile();
-
-        await ensureKnownRegion(this);
-        // Due to the HTTP requests done by ensureKnownRegion, it's possible that
-        // at this point a synchronous init has been forced by other code.
-        if (!gInitialized)
-          await this._asyncLoadEngines(cache);
+        let cache = await this._readCacheFile();
+        // The init flow is not going to block on a fetch from an external service,
+        // but we're kicking it off as soon as possible to prevent UI flickering as
+        // much as possible.
+        this._ensureKnownRegionPromise = ensureKnownRegion(this)
+          .catch(ex => LOG("_reInit: failure determining region: " + ex))
+          .finally(() => this._ensureKnownRegionPromise = null);
+        await this._loadEngines(cache);
+        // Make sure the current list of engines is persisted.
+        await this._buildCache();
 
         // Typically we'll re-init as a result of a pref observer,
         // so signal to 'callers' that we're done.
         gInitialized = true;
+        this._initObservers.resolve();
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
       } catch (err) {
         LOG("Reinit failed: " + err);
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-failed");
       } finally {
+        gReinitializing = false;
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-complete");
       }
     })();
   },
 
   /**
-   * Read the cache file synchronously.
-   *
-   * @returns A JS object containing the cached data.
-   */
-  _readCacheFile: function SRCH_SVC__readCacheFile() {
-    if (this._cacheFileJSON) {
-      return this._cacheFileJSON;
-    }
-
-    let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
-    cacheFile.append(CACHE_FILENAME);
-
-    let stream;
-    try {
-      stream = Cc["@mozilla.org/network/file-input-stream;1"].
-                 createInstance(Ci.nsIFileInputStream);
-      stream.init(cacheFile, MODE_RDONLY, PERMS_FILE, 0);
-
-      let bis = Cc["@mozilla.org/binaryinputstream;1"]
-                  .createInstance(Ci.nsIBinaryInputStream);
-      bis.setInputStream(stream);
-
-      let count = stream.available();
-      let array = new Uint8Array(count);
-      bis.readArrayBuffer(count, array.buffer);
-
-      let bytes = Lz4.decompressFileContent(array);
-      let json = JSON.parse(new TextDecoder().decode(bytes));
-      if (!json.engines || !json.engines.length)
-        throw "no engine in the file";
-      // Reset search default expiration on major releases
-      if (json.appVersion != Services.appinfo.version &&
-          geoSpecificDefaultsEnabled() &&
-          json.metaData) {
-        json.metaData.searchDefaultExpir = 0;
-      }
-      return json;
-    } catch (ex) {
-      LOG("_readCacheFile: Error reading cache file: " + ex);
-      return {};
-    } finally {
-      stream.close();
-    }
-  },
-
-  /**
    * Read the cache file asynchronously.
    *
    * @returns {Promise} A promise, resolved successfully if retrieveing data
    * succeeds.
    */
-  async _asyncReadCacheFile() {
+  async _readCacheFile() {
     let json;
     try {
       let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
       let bytes = await OS.File.read(cacheFilePath, {compression: "lz4"});
       json = JSON.parse(new TextDecoder().decode(bytes));
       if (!json.engines || !json.engines.length)
         throw "no engine in the file";