Bug 1635235 - Implement reading the allow override default search engine allowlist from remote settings. r=daleharvey
☠☠ backed out by e7641c1071ea ☠ ☠
authorMark Banner <standard8@mozilla.com>
Sun, 24 May 2020 21:45:41 +0000
changeset 531816 c9f80397bbecbaa3b026d20fe8fc58c9e44fbed2
parent 531815 1334652d0ec42759562bd39be74e165971037889
child 531817 97ecda13df1858b93690d09e5cf3f8d82831c03c
push id37446
push userabutkovits@mozilla.com
push dateMon, 25 May 2020 09:34:40 +0000
treeherdermozilla-central@9155018d4978 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaleharvey
bugs1635235
milestone78.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 1635235 - Implement reading the allow override default search engine allowlist from remote settings. r=daleharvey This implements reading the list from remote settings. We only read it at startup if necessary, or on add-on installation. We do not check for updates - if something is removed, we'll wait until next startup before processing it. Also adds lots of tests for canOverride as this seems a critical part to get right. Differential Revision: https://phabricator.services.mozilla.com/D76473
browser/components/extensions/parent/ext-chrome-settings-overrides.js
browser/components/extensions/test/xpcshell/test_ext_settings_overrides_defaults.js
browser/installer/allowed-dupes.mn
services/settings/dumps/main/moz.build
services/settings/dumps/main/search-default-override-allowlist.json
toolkit/components/search/SearchService.jsm
toolkit/components/search/SearchUtils.jsm
toolkit/components/search/tests/xpcshell/test_override_allowlist.js
toolkit/components/search/tests/xpcshell/xpcshell-common.ini
--- a/browser/components/extensions/parent/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/parent/ext-chrome-settings-overrides.js
@@ -262,17 +262,17 @@ this.chrome_settings_overrides = class e
 
   static async onEnabling(id) {
     chrome_settings_overrides.processDefaultSearchSetting("enable", id);
   }
 
   static async onUninstall(id) {
     let searchStartupPromise = pendingSearchSetupTasks.get(id);
     if (searchStartupPromise) {
-      await searchStartupPromise;
+      await searchStartupPromise.catch(Cu.reportError);
     }
     // Note: We do not have to deal with homepage here as it is managed by
     // the ExtensionPreferencesManager.
     return Promise.all([
       this.removeSearchSettings(id),
       homepagePopup.clearConfirmation(id),
     ]);
   }
@@ -418,17 +418,17 @@ this.chrome_settings_overrides = class e
           Services.obs.notifyObservers(
             subject,
             "webextension-defaultsearch-prompt"
           );
         }
       } else {
         // Needs to be called every time to handle reenabling, but
         // only sets default for install or enable.
-        this.setDefault(engineName);
+        await this.setDefault(engineName);
       }
     }
   }
 
   async setDefault(engineName) {
     let { extension } = this;
     if (extension.startupReason === "ADDON_INSTALL") {
       let defaultEngine = await Services.search.getDefault();
--- a/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_defaults.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_settings_overrides_defaults.js
@@ -6,16 +6,26 @@
 const { AddonTestUtils } = ChromeUtils.import(
   "resource://testing-common/AddonTestUtils.jsm"
 );
 
 const { SearchTestUtils } = ChromeUtils.import(
   "resource://testing-common/SearchTestUtils.jsm"
 );
 
+const { SearchUtils } = ChromeUtils.import(
+  "resource://gre/modules/SearchUtils.jsm"
+);
+
+const { RemoteSettings } = ChromeUtils.import(
+  "resource://services-settings/remote-settings.js"
+);
+
+const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+
 const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
 
 AddonTestUtils.init(this);
 AddonTestUtils.createAppInfo(
   "xpcshell@tests.mozilla.org",
   "XPCShell",
   "42",
   "42"
@@ -113,27 +123,28 @@ add_task(async function test_extension_c
   Assert.equal(
     Services.search.defaultEngine.name,
     "MozParamsTest",
     "Should have reverted to the original default engine."
   );
 });
 
 add_task(async function test_extension_overriding_app_provided_default() {
-  Services.search.wrappedJSObject._getSearchDefaultOverrideAllowlist = () => [
+  const settings = await RemoteSettings(SearchUtils.SETTINGS_ALLOWLIST_KEY);
+  sinon.stub(settings, "get").returns([
     {
       thirdPartyId: "test@thirdparty.example.com",
       overridesId: "test2@search.mozilla.org",
       urls: [
         {
           search_url: "https://example.com/?q={searchTerms}&foo=myparams",
         },
       ],
     },
-  ];
+  ]);
 
   let ext1 = ExtensionTestUtils.loadExtension({
     manifest: {
       applications: {
         gecko: {
           id: "test@thirdparty.example.com",
         },
       },
--- a/browser/installer/allowed-dupes.mn
+++ b/browser/installer/allowed-dupes.mn
@@ -57,16 +57,17 @@ browser/chrome/browser/res/payments/form
 browser/features/formautofill@mozilla.org/chrome/content/editCreditCard.xhtml
 browser/chrome/browser/res/payments/formautofill/editCreditCard.xhtml
 browser/features/formautofill@mozilla.org/chrome/content/autofillEditForms.js
 browser/chrome/browser/res/payments/formautofill/autofillEditForms.js
 
 # Bug 1451050 - Remote settings empty dumps (will be populated with data eventually)
 browser/defaults/settings/pinning/pins.json
 browser/defaults/settings/main/example.json
+browser/defaults/settings/main/search-default-override-allowlist.json
 
 #ifdef MOZ_EME_WIN32_ARTIFACT
 gmp-clearkey/0.1/manifest.json
 i686/gmp-clearkey/0.1/manifest.json
 #endif
 
 # Bug 1496075 - Switch searchplugins to Web Extensions
 browser/chrome/browser/search-extensions/amazon/favicon.ico
--- a/services/settings/dumps/main/moz.build
+++ b/services/settings/dumps/main/moz.build
@@ -4,14 +4,15 @@
 
 FINAL_TARGET_FILES.defaults.settings.main += [
     'anti-tracking-url-decoration.json',
     'example.json',
     'hijack-blocklists.json',
     'language-dictionaries.json',
     'onboarding.json',
     'search-config.json',
+    'search-default-override-allowlist.json',
     'sites-classification.json',
     'url-classifier-skip-urls.json',
 ]
 
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
     DIST_SUBDIR = 'browser'
new file mode 100644
--- /dev/null
+++ b/services/settings/dumps/main/search-default-override-allowlist.json
@@ -0,0 +1,1 @@
+{"data":[]}
\ No newline at end of file
--- a/toolkit/components/search/SearchService.jsm
+++ b/toolkit/components/search/SearchService.jsm
@@ -16,16 +16,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   clearTimeout: "resource://gre/modules/Timer.jsm",
   DeferredTask: "resource://gre/modules/DeferredTask.jsm",
   ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
   getVerificationHash: "resource://gre/modules/SearchEngine.jsm",
   IgnoreLists: "resource://gre/modules/IgnoreLists.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   Region: "resource://gre/modules/Region.jsm",
+  RemoteSettings: "resource://services-settings/remote-settings.js",
   SearchEngine: "resource://gre/modules/SearchEngine.jsm",
   SearchEngineSelector: "resource://gre/modules/SearchEngineSelector.jsm",
   SearchStaticData: "resource://gre/modules/SearchStaticData.jsm",
   SearchUtils: "resource://gre/modules/SearchUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
 });
 
@@ -46,16 +47,23 @@ XPCOMUtils.defineLazyPreferenceGetter(
   SearchUtils.BROWSER_SEARCH_PREF + "modernConfig",
   false,
   () => {
     // We'll re-init the service, regardless of which way the pref-flip went.
     Services.search.reInit();
   }
 );
 
+XPCOMUtils.defineLazyGetter(this, "logConsole", () => {
+  return console.createInstance({
+    prefix: "SearchService",
+    maxLogLevel: SearchUtils.loggingEnabled ? "Debug" : "Warn",
+  });
+});
+
 // A text encoder to UTF8, used whenever we commit the cache to disk.
 XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
   return new TextEncoder();
 });
 
 // Directory service keys
 const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
 
@@ -593,16 +601,19 @@ SearchService.prototype = {
    *   - visibleDefaultEngines
    *       The list of visible default engines supplied by the region server.
    *
    * All of the above except `searchDefaultExpir` have associated hash fields
    * to validate the value is set by the application.
    */
   _metaData: {},
 
+  // A reference to the handler for the default override allow list.
+  _defaultOverrideAllowlist: null,
+
   // This reflects the combined values of the prefs for enabling the separate
   // private default UI, and for the user choosing a separate private engine.
   // If either one is disabled, then we don't enable the separate private default.
   get _separatePrivateDefault() {
     return (
       this._separatePrivateDefaultPrefValue &&
       this._separatePrivateDefaultEnabledPrefValue
     );
@@ -837,80 +848,78 @@ SearchService.prototype = {
         url.includes(code.toLowerCase())
       )
     ) {
       return true;
     }
     return false;
   },
 
-  async _getSearchDefaultOverrideAllowlist() {
-    return [];
-  },
-
-  async _canOverrideDefault(extension, appProvidedExtensionId) {
-    const overrideTable = await this._getSearchDefaultOverrideAllowlist();
-
-    let entry = overrideTable.find(e => e.thirdPartyId == extension.id);
-    if (!entry) {
-      return false;
-    }
-
-    if (appProvidedExtensionId != entry.overridesId) {
-      return false;
-    }
-    let searchProvider =
-      extension.manifest.chrome_settings_overrides.search_provider;
-    return entry.urls.some(
-      e =>
-        searchProvider.search_url == e.search_url &&
-        searchProvider.search_form == e.search_form &&
-        searchProvider.search_url_get_params == e.search_url_get_params &&
-        searchProvider.search_url_post_params == e.search_url_post_params
-    );
-  },
-
   async maybeSetAndOverrideDefault(extension) {
     let searchProvider =
       extension.manifest.chrome_settings_overrides.search_provider;
     let engine = this._engines.get(searchProvider.name);
     if (!engine || !engine.isAppProvided) {
       return false;
     }
     let params = this.getEngineParams(
       extension,
       extension.manifest,
       SearchUtils.DEFAULT_TAG
     );
 
+    if (!this._defaultOverrideAllowlist) {
+      this._defaultOverrideAllowlist = new SearchDefaultOverrideAllowlistHandler();
+    }
+
     if (
       extension.startupReason === "ADDON_INSTALL" ||
       extension.startupReason === "ADDON_ENABLE"
     ) {
       // Don't allow an extension to set the default if it is already the default.
       if (this.defaultEngine.name == searchProvider.name) {
         return false;
       }
-      if (!(await this._canOverrideDefault(extension, engine._extensionID))) {
+      if (
+        !(await this._defaultOverrideAllowlist.canOverride(
+          extension,
+          engine._extensionID
+        ))
+      ) {
+        logConsole.debug(
+          "Allowing default engine to be set to app-provided.",
+          extension.id
+        );
         // We don't allow overriding the engine in this case, but we can allow
         // the extension to change the default engine.
         return true;
       }
       if (extension.startupReason === "ADDON_INSTALL") {
         // We're ok to override.
         engine.overrideWithExtension(params);
+        logConsole.debug(
+          "Allowing default engine to be set to app-provided and overridden.",
+          extension.id
+        );
         return true;
       }
     }
 
     if (
       engine.getAttr("overriddenBy") == extension.id &&
-      (await this._canOverrideDefault(extension, engine._extensionID))
+      (await this._defaultOverrideAllowlist.canOverride(
+        extension,
+        engine._extensionID
+      ))
     ) {
       engine.overrideWithExtension(params);
+      logConsole.debug(
+        "Re-enabling overriding of core extension by",
+        extension.id
+      );
       return true;
     }
 
     return false;
   },
 
   /**
    * Handles the search configuration being - adds a wait on the user
@@ -3878,9 +3887,86 @@ var engineUpdateService = {
 
 XPCOMUtils.defineLazyServiceGetter(
   SearchService.prototype,
   "idleService",
   "@mozilla.org/widget/idleservice;1",
   "nsIIdleService"
 );
 
+/**
+ * Handles getting and checking extensions against the allow list.
+ */
+class SearchDefaultOverrideAllowlistHandler {
+  /**
+   * @param {function} listener
+   *   A listener for configuration update changes.
+   */
+  constructor(listener) {
+    this._remoteConfig = RemoteSettings(SearchUtils.SETTINGS_ALLOWLIST_KEY);
+  }
+
+  /**
+   * Determines if a search engine extension can override a default one
+   * according to the allow list.
+   *
+   * @param {object} extension
+   *   The extension object (from add-on manager) that will override the
+   *   app provided search engine.
+   * @param {string} appProvidedExtensionId
+   *   The id of the search engine that will be overriden.
+   * @returns {boolean}
+   *   Returns true if the search engine extension may override the app provided
+   *   instance.
+   */
+  async canOverride(extension, appProvidedExtensionId) {
+    const overrideTable = await this._getAllowlist();
+
+    let entry = overrideTable.find(e => e.thirdPartyId == extension.id);
+    if (!entry) {
+      return false;
+    }
+
+    if (appProvidedExtensionId != entry.overridesId) {
+      return false;
+    }
+
+    let searchProvider =
+      extension.manifest.chrome_settings_overrides.search_provider;
+
+    return entry.urls.some(
+      e =>
+        searchProvider.search_url == e.search_url &&
+        searchProvider.search_form == e.search_form &&
+        searchProvider.search_url_get_params == e.search_url_get_params &&
+        searchProvider.search_url_post_params == e.search_url_post_params
+    );
+  }
+
+  /**
+   * Obtains the configuration from remote settings. This includes
+   * verifying the signature of the record within the database.
+   *
+   * If the signature in the database is invalid, the database will be wiped
+   * and the stored dump will be used, until the settings next update.
+   *
+   * Note that this may cause a network check of the certificate, but that
+   * should generally be quick.
+   *
+   * @returns {array}
+   *   An array of objects in the database, or an empty array if none
+   *   could be obtained.
+   */
+  async _getAllowlist() {
+    let result = [];
+    try {
+      result = await this._remoteConfig.get();
+    } catch (ex) {
+      // Don't throw an error just log it, just continue with no data, and hopefully
+      // a sync will fix things later on.
+      Cu.reportError(ex);
+    }
+    logConsole.debug("Allow list is:", result);
+    return result;
+  }
+}
+
 var EXPORTED_SYMBOLS = ["SearchService"];
--- a/toolkit/components/search/SearchUtils.jsm
+++ b/toolkit/components/search/SearchUtils.jsm
@@ -19,16 +19,28 @@ XPCOMUtils.defineLazyModuleGetters(this,
 const BROWSER_SEARCH_PREF = "browser.search.";
 
 var SearchUtils = {
   BROWSER_SEARCH_PREF,
 
   SETTINGS_KEY: "search-config",
 
   /**
+   * This is the Remote Settings key that we use to get the ignore lists for
+   * engines.
+   */
+  SETTINGS_IGNORELIST_KEY: "hijack-blocklists",
+
+  /**
+   * This is the Remote Settings key that we use to get the allow lists for
+   * overriding the default engines.
+   */
+  SETTINGS_ALLOWLIST_KEY: "search-default-override-allowlist",
+
+  /**
    * Topic used for events involving the service itself.
    */
   TOPIC_SEARCH_SERVICE: "browser-search-service",
 
   // See documentation in nsISearchService.idl.
   TOPIC_ENGINE_MODIFIED: "browser-search-engine-modified",
   MODIFIED_TYPE: {
     CHANGED: "engine-changed",
@@ -63,22 +75,16 @@ var SearchUtils = {
   // cause big delays when loading them at startup.
   MAX_ICON_SIZE: 20000,
 
   // Default charset to use for sending search parameters. ISO-8859-1 is used to
   // match previous nsInternetSearchService behavior as a URL parameter. Label
   // resolution causes windows-1252 to be actually used.
   DEFAULT_QUERY_CHARSET: "ISO-8859-1",
 
-  /**
-   * This is the Remote Settings key that we use to get the ignore lists for
-   * engines.
-   */
-  SETTINGS_IGNORELIST_KEY: "hijack-blocklists",
-
   // A tag to denote when we are using the "default_locale" of an engine.
   DEFAULT_TAG: "default",
 
   /**
    * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
    * the state of the search service.
    *
    * @param {nsISearchEngine} engine
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_override_allowlist.js
@@ -0,0 +1,314 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kBaseURL = "https://example.com/";
+const kSearchEngineURL = `${kBaseURL}?q={searchTerms}&foo=myparams`;
+const kOverriddenEngineName = "Simple Engine";
+
+SearchTestUtils.initXPCShellAddonManager(this);
+
+const whitelist = [
+  {
+    thirdPartyId: "test@thirdparty.example.com",
+    overridesId: "simple@search.mozilla.org",
+    urls: [],
+  },
+];
+
+const tests = [
+  {
+    title: "test_not_changing_anything",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: "MozParamsTest2",
+      keyword: "MozSearch",
+      search_url: kSearchEngineURL,
+    },
+    expected: {
+      switchToDefaultAllowed: false,
+      overridesEngine: false,
+    },
+  },
+  {
+    title: "test_changing_default_engine",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kSearchEngineURL,
+    },
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: false,
+    },
+  },
+  {
+    title: "test_overriding_default_engine",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kSearchEngineURL,
+    },
+    whitelistUrls: [
+      {
+        search_url: kSearchEngineURL,
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: true,
+      searchUrl: kSearchEngineURL,
+    },
+  },
+  {
+    title: "test_overriding_default_engine_different_url",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kSearchEngineURL + "a",
+    },
+    whitelistUrls: [
+      {
+        search_url: kSearchEngineURL,
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: false,
+    },
+  },
+  {
+    title: "test_overriding_default_engine_get_params",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kBaseURL,
+      search_url_get_params: "q={searchTerms}&enc=UTF-8",
+    },
+    whitelistUrls: [
+      {
+        search_url: kBaseURL,
+        search_url_get_params: "q={searchTerms}&enc=UTF-8",
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: true,
+      searchUrl: `${kBaseURL}?q={searchTerms}&enc=UTF-8`,
+    },
+  },
+  {
+    title: "test_overriding_default_engine_different_get_params",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kBaseURL,
+      search_url_get_params: "q={searchTerms}&enc=UTF-8a",
+    },
+    whitelistUrls: [
+      {
+        search_url: kBaseURL,
+        search_url_get_params: "q={searchTerms}&enc=UTF-8",
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: false,
+    },
+  },
+  {
+    title: "test_overriding_default_engine_post_params",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kBaseURL,
+      search_url_post_params: "q={searchTerms}&enc=UTF-8",
+    },
+    whitelistUrls: [
+      {
+        search_url: kBaseURL,
+        search_url_post_params: "q={searchTerms}&enc=UTF-8",
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: true,
+      searchUrl: `${kBaseURL}`,
+      postData: "q={searchTerms}&enc=UTF-8",
+    },
+  },
+  {
+    title: "test_overriding_default_engine_different_post_params",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kBaseURL,
+      search_url_post_params: "q={searchTerms}&enc=UTF-8a",
+    },
+    whitelistUrls: [
+      {
+        search_url: kBaseURL,
+        search_url_post_params: "q={searchTerms}&enc=UTF-8",
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: false,
+    },
+  },
+  {
+    title: "test_overriding_default_engine_search_form",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kBaseURL,
+      search_form: "https://example.com/form",
+    },
+    whitelistUrls: [
+      {
+        search_url: kBaseURL,
+        search_form: "https://example.com/form",
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: true,
+      searchUrl: `${kBaseURL}`,
+      searchForm: "https://example.com/form",
+    },
+  },
+  {
+    title: "test_overriding_default_engine_different_search_form",
+    startupReason: "ADDON_INSTALL",
+    search_provider: {
+      is_default: true,
+      name: kOverriddenEngineName,
+      keyword: "MozSearch",
+      search_url: kBaseURL,
+      search_form: "https://example.com/forma",
+    },
+    whitelistUrls: [
+      {
+        search_url: kBaseURL,
+        search_form: "https://example.com/form",
+      },
+    ],
+    expected: {
+      switchToDefaultAllowed: true,
+      overridesEngine: false,
+    },
+  },
+];
+
+let baseExtension;
+let remoteSettingsStub;
+
+add_task(async function setup() {
+  await SearchTestUtils.useTestEngines("simple-engines");
+  await AddonTestUtils.promiseStartupManager();
+  await Services.search.init();
+
+  baseExtension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      applications: {
+        gecko: {
+          id: "test@thirdparty.example.com",
+        },
+      },
+    },
+    useAddonManager: "permanent",
+  });
+  await baseExtension.startup();
+
+  const settings = await RemoteSettings(SearchUtils.SETTINGS_ALLOWLIST_KEY);
+  remoteSettingsStub = sinon.stub(settings, "get").returns([]);
+
+  registerCleanupFunction(async () => {
+    await baseExtension.unload();
+  });
+});
+
+for (const test of tests) {
+  add_task(async () => {
+    info(test.title);
+
+    let extension = {
+      ...baseExtension,
+      startupReason: test.startupReason,
+      manifest: {
+        chrome_settings_overrides: {
+          search_provider: test.search_provider,
+        },
+      },
+    };
+
+    if (test.expected.overridesEngine) {
+      remoteSettingsStub.returns([
+        { ...whitelist[0], urls: test.whitelistUrls },
+      ]);
+    }
+
+    Assert.equal(
+      await Services.search.maybeSetAndOverrideDefault(extension),
+      test.expected.switchToDefaultAllowed,
+      "Should have returned the correct value for allowing switch to default or not."
+    );
+
+    let engine = await Services.search.getEngineByName(kOverriddenEngineName);
+    Assert.equal(
+      !!engine.wrappedJSObject.getAttr("overriddenBy"),
+      test.expected.overridesEngine,
+      "Should have correctly overridden or not."
+    );
+
+    if (test.expected.overridesEngine) {
+      let submission = engine.getSubmission("{searchTerms}");
+      Assert.equal(
+        decodeURI(submission.uri.spec),
+        test.expected.searchUrl,
+        "Should have set the correct url on an overriden engine"
+      );
+
+      if (test.expected.search_form) {
+        Assert.equal(
+          engine.wrappedJSObject._searchForm,
+          test.expected.searchForm,
+          "Should have overridden the search form."
+        );
+      }
+
+      if (test.expected.postData) {
+        let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+          Ci.nsIScriptableInputStream
+        );
+        sis.init(submission.postData);
+        let data = sis.read(submission.postData.available());
+        Assert.equal(
+          decodeURIComponent(data),
+          test.expected.postData,
+          "Should have overridden the postData"
+        );
+      }
+
+      engine.wrappedJSObject.removeExtensionOverride();
+    }
+  });
+}
--- a/toolkit/components/search/tests/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell-common.ini
@@ -47,16 +47,17 @@ support-files =
 [test_location_malformed_json.js]
 [test_location.js]
 [test_migrateWebExtensionEngine.js]
 [test_multipleIcons.js]
 [test_nocache.js]
 [test_nodb_pluschanges.js]
 [test_notifications.js]
 [test_originalDefaultEngine.js]
+[test_override_allowlist.js]
 [test_paramSubstitution.js]
 [test_parseSubmissionURL.js]
 [test_pref.js]
 [test_purpose.js]
 [test_rel_searchform.js]
 [test_require_engines_in_cache.js]
 [test_remove_profile_engine.js]
 [test_resultDomain.js]