Bug 1378882 - Support is_default for built-in engines only. r=mixedpuppy draft
authorMichael Kaply <mozilla@kaply.com>
Tue, 18 Jul 2017 11:28:33 -0500
changeset 652264 b29b61ffdddcb30ee5d3a9456c0fd9b285d4f03b
parent 649871 c7c96eebbcb91e5e0c8ef0dbbb5324812fa1e476
child 728051 a75e3ce4116a9ae795a0912c60430312be760046
push id76016
push usermozilla@kaply.com
push dateThu, 24 Aug 2017 17:47:17 +0000
reviewersmixedpuppy
bugs1378882
milestone57.0a1
Bug 1378882 - Support is_default for built-in engines only. r=mixedpuppy MozReview-Commit-ID: C4iM2boQhK3
browser/components/extensions/ext-chrome-settings-overrides.js
browser/components/extensions/schemas/chrome_settings_overrides.json
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
--- a/browser/components/extensions/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -1,16 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
                                   "resource://gre/modules/ExtensionPreferencesManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
+                                  "resource://gre/modules/ExtensionSettingsStore.jsm");
+
+const DEFAULT_SEARCH_STORE_TYPE = "default_search";
+const DEFAULT_SEARCH_SETTING_NAME = "defaultSearch";
 
 const searchInitialized = () => {
   return new Promise(resolve => {
     if (Services.search.isInitialized) {
       resolve();
     }
     const SEARCH_SERVICE_TOPIC = "browser-search-service";
     Services.obs.addObserver(function observer(subject, topic, data) {
@@ -20,27 +27,89 @@ const searchInitialized = () => {
 
       Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
       resolve();
     }, SEARCH_SERVICE_TOPIC);
   });
 };
 
 this.chrome_settings_overrides = class extends ExtensionAPI {
+  processDefaultSearchSetting(action) {
+    let {extension} = this;
+    let item = ExtensionSettingsStore.getSetting(DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
+    if (!item) {
+      return;
+    }
+    if (Services.search.currentEngine.name != item.value &&
+        Services.search.currentEngine.name != item.initialValue) {
+      // The current engine is not the same as the value that the ExtensionSettingsStore has.
+      // This means that the user changed the engine, so we shouldn't control it anymore.
+      // Do nothing and remove our entry from the ExtensionSettingsStore.
+      ExtensionSettingsStore.removeSetting(extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
+      return;
+    }
+    item = ExtensionSettingsStore[action](extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME);
+    if (item) {
+      try {
+        let engine = Services.search.getEngineByName(item.value || item.initialValue);
+        if (engine) {
+          Services.search.currentEngine = engine;
+        }
+      } catch (e) {
+        Components.utils.reportError(e);
+      }
+    }
+  }
+
   async onManifestEntry(entryName) {
     let {extension} = this;
     let {manifest} = extension;
 
+    await ExtensionSettingsStore.initialize();
     if (manifest.chrome_settings_overrides.homepage) {
       ExtensionPreferencesManager.setSetting(extension, "homepage_override",
                                              manifest.chrome_settings_overrides.homepage);
     }
     if (manifest.chrome_settings_overrides.search_provider) {
       await searchInitialized();
       let searchProvider = manifest.chrome_settings_overrides.search_provider;
+      if (searchProvider.is_default) {
+        let engineName = searchProvider.name.trim();
+        let engine = Services.search.getEngineByName(engineName);
+        if (engine && Services.search.getDefaultEngines().includes(engine)) {
+          // Only add onclose handlers if we would definitely
+          // be setting the default engine.
+          extension.callOnClose({
+            close: () => {
+              switch (extension.shutdownReason) {
+                case "ADDON_DISABLE":
+                  this.processDefaultSearchSetting("disable");
+                  break;
+
+                case "ADDON_UNINSTALL":
+                  this.processDefaultSearchSetting("removeSetting");
+                  break;
+              }
+            },
+          });
+          if (extension.startupReason === "ADDON_INSTALL") {
+            let item = await ExtensionSettingsStore.addSetting(
+              extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME, engineName, () => {
+                return Services.search.currentEngine.name;
+              });
+            Services.search.currentEngine = Services.search.getEngineByName(item.value);
+          } else if (extension.startupReason === "ADDON_ENABLE") {
+            this.processDefaultSearchSetting("enable");
+          }
+          // If we would have set the default engine,
+          // we don't allow a search provider to be added.
+          return;
+        }
+        Components.utils.reportError("is_default can only be used for built-in engines.");
+      }
       let isCurrent = false;
       let index = -1;
       if (extension.startupReason === "ADDON_UPGRADE") {
         let engines = Services.search.getEnginesByExtensionID(extension.id);
         if (engines.length > 0) {
           // There can be only one engine right now
           isCurrent = Services.search.currentEngine == engines[0];
           // Get position of engine and store it
@@ -65,16 +134,27 @@ this.chrome_settings_overrides = class e
           if (index != -1) {
             Services.search.moveEngine(engine, index);
           }
         }
       } catch (e) {
         Components.utils.reportError(e);
       }
     }
+    // If the setting exists for the extension, but is missing from the manifest,
+    // remove it. This can happen if the extension removes is_default.
+    // There's really no good place to put this, because the entire search section
+    // could be removed.
+    // We'll never get here in the normal case because we always return early
+    // if we have an is_default value that we use.
+    if (ExtensionSettingsStore.hasSetting(
+               extension, DEFAULT_SEARCH_STORE_TYPE, DEFAULT_SEARCH_SETTING_NAME)) {
+      await searchInitialized();
+      this.processDefaultSearchSetting("removeSetting");
+    }
   }
   async onShutdown(reason) {
     let {extension} = this;
     if (reason == "ADDON_DISABLE" ||
         reason == "ADDON_UNINSTALL") {
       if (extension.manifest.chrome_settings_overrides.search_provider) {
         await searchInitialized();
         let engines = Services.search.getEnginesByExtensionID(extension.id);
--- a/browser/components/extensions/schemas/chrome_settings_overrides.json
+++ b/browser/components/extensions/schemas/chrome_settings_overrides.json
@@ -93,17 +93,17 @@
                   "prepopulated_id": {
                     "type": "integer",
                     "optional": true,
                     "deprecated": "Unsupported on Firefox."
                   },
                   "is_default": {
                     "type": "boolean",
                     "optional": true,
-                    "deprecated": "Unsupported on Firefox at this time."
+                    "description": "Sets the default engine to a built-in engine only."
                   }
                 }
               }
             }
           }
         }
       }
     ]
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -102,16 +102,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
+[browser_ext_settings_overrides_default_search.js]
 [browser_ext_settings_overrides_search.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_browser_style.js]
 [browser_ext_sidebarAction_context.js]
 [browser_ext_sidebarAction_contextMenu.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_settings_overrides_default_search.js
@@ -0,0 +1,506 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+
+const EXTENSION1_ID = "extension1@mozilla.com";
+const EXTENSION2_ID = "extension2@mozilla.com";
+
+function awaitEvent(eventName, id) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      let extension = args[0];
+      if (_eventName === eventName &&
+          extension.id == id) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+/* This tests setting a default engine. */
+add_task(async function test_extension_setting_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests that using an invalid engine does nothing. */
+add_task(async function test_extension_setting_invalid_name_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "InvalidName",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.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 defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.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 defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});
+
+/* This tests adding an engine with one add-on and trying to make it the
+ *default with anoth. */
+add_task(async function test_extension_setting_invalid_default_engine() {
+  let defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "MozSearch",
+          "keyword": "MozSearch",
+          "search_url": "https://example.com/?q={searchTerms}",
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "MozSearch",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  let engine = Services.search.getEngineByName("MozSearch");
+  ok(engine, "Engine should exist.");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.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": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let engine = Services.search.getEngineByName("Twitter");
+  Services.search.currentEngine = engine;
+
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+});
+
+/* 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: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let engine = Services.search.getEngineByName("Twitter");
+  Services.search.currentEngine = engine;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
+  let addon = await AddonManager.getAddonByID(EXTENSION1_ID);
+  addon.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
+  addon.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext1.unload();
+});
+
+/* 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 defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION2_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
+  let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
+  addon1.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
+  addon1.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  await ext1.unload();
+
+  is(Services.search.currentEngine.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 defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION2_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION1_ID);
+  let addon1 = await AddonManager.getAddonByID(EXTENSION1_ID);
+  addon1.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION1_ID);
+  addon1.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  await ext1.unload();
+
+  is(Services.search.currentEngine.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 defaultEngineName = Services.search.currentEngine.name;
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION1_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "DuckDuckGo",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: EXTENSION2_ID,
+        },
+      },
+      "chrome_settings_overrides": {
+        "search_provider": {
+          "name": "Twitter",
+          "search_url": "https://example.com/?q={searchTerms}",
+          "is_default": true,
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  await ext2.startup();
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+
+  let disabledPromise = awaitEvent("shutdown", EXTENSION2_ID);
+  let addon2 = await AddonManager.getAddonByID(EXTENSION2_ID);
+  addon2.userDisabled = true;
+  await disabledPromise;
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+
+  let enabledPromise = awaitEvent("ready", EXTENSION2_ID);
+  addon2.userDisabled = false;
+  await enabledPromise;
+
+  is(Services.search.currentEngine.name, "Twitter", "Default engine is Twitter");
+  await ext2.unload();
+
+  is(Services.search.currentEngine.name, "DuckDuckGo", "Default engine is DuckDuckGo");
+  await ext1.unload();
+
+  is(Services.search.currentEngine.name, defaultEngineName, `Default engine is ${defaultEngineName}`);
+});