summaryrefslogtreecommitdiffstats
path: root/toolkit/components/search/tests/xpcshell/searchconfigs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/search/tests/xpcshell/searchconfigs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/search/tests/xpcshell/searchconfigs')
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js604
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js75
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js39
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js134
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_distributions.js348
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js35
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js276
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_ecosia.js35
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_google.js171
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_mailru.js36
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js36
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_rakuten.js35
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js209
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js20
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_selector_db_out_of_date.js93
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_wikipedia.js171
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_yahoojp.js36
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_yandex.js110
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml65
19 files changed, 2528 insertions, 0 deletions
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
new file mode 100644
index 0000000000..72c4d4f04f
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
@@ -0,0 +1,604 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ // Only needed when SearchUtils.newSearchConfigEnabled is false.
+ AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs",
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
+ Region: "resource://gre/modules/Region.sys.mjs",
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
+ SearchEngine: "resource://gre/modules/SearchEngine.sys.mjs",
+ SearchEngineSelector: "resource://gre/modules/SearchEngineSelector.sys.mjs",
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
+ SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
+ SearchEngineSelectorOld:
+ "resource://gre/modules/SearchEngineSelectorOld.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+ updateAppInfo: "resource://testing-common/AppInfo.sys.mjs",
+});
+
+const GLOBAL_SCOPE = this;
+const TEST_DEBUG = Services.env.get("TEST_DEBUG");
+
+const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
+const URLTYPE_SEARCH_HTML = "text/html";
+const SUBMISSION_PURPOSES = [
+ "searchbar",
+ "keyword",
+ "contextmenu",
+ "homepage",
+ "newtab",
+];
+
+let engineSelector;
+
+/**
+ * This function is used to override the remote settings configuration
+ * if the SEARCH_CONFIG environment variable is set. This allows testing
+ * against a remote server.
+ */
+async function maybeSetupConfig() {
+ const SEARCH_CONFIG = Services.env.get("SEARCH_CONFIG");
+ if (SEARCH_CONFIG) {
+ if (!(SEARCH_CONFIG in SearchUtils.ENGINES_URLS)) {
+ throw new Error(`Invalid value for SEARCH_CONFIG`);
+ }
+ const url = SearchUtils.ENGINES_URLS[SEARCH_CONFIG];
+ const response = await fetch(url);
+ const config = await response.json();
+ const settings = await RemoteSettings(SearchUtils.SETTINGS_KEY);
+ sinon.stub(settings, "get").returns(config.data);
+ }
+}
+
+/**
+ * This class implements the test harness for search configuration tests.
+ * These tests are designed to ensure that the correct search engines are
+ * loaded for the various region/locale configurations.
+ *
+ * The configuration for each test is represented by an object having the
+ * following properties:
+ *
+ * - identifier (string)
+ * The identifier for the search engine under test.
+ * - default (object)
+ * An inclusion/exclusion configuration (see below) to detail when this engine
+ * should be listed as default.
+ *
+ * The inclusion/exclusion configuration is represented as an object having the
+ * following properties:
+ *
+ * - included (array)
+ * An optional array of region/locale pairs.
+ * - excluded (array)
+ * An optional array of region/locale pairs.
+ *
+ * If the object is empty, the engine is assumed not to be part of any locale/region
+ * pair.
+ * If the object has `excluded` but not `included`, then the engine is assumed to
+ * be part of every locale/region pair except for where it matches the exclusions.
+ *
+ * The region/locale pairs are represented as an object having the following
+ * properties:
+ *
+ * - region (array)
+ * An array of two-letter region codes.
+ * - locale (object)
+ * A locale object which may consist of:
+ * - matches (array)
+ * An array of locale strings which should exactly match the locale.
+ * - startsWith (array)
+ * An array of locale strings which the locale should start with.
+ */
+class SearchConfigTest {
+ /**
+ * @param {object} config
+ * The initial configuration for this test, see above.
+ */
+ constructor(config = {}) {
+ this._config = config;
+ }
+
+ /**
+ * Sets up the test.
+ *
+ * @param {string} [version]
+ * The version to simulate for running the tests.
+ */
+ async setup(version = "42.0") {
+ if (SearchUtils.newSearchConfigEnabled) {
+ updateAppInfo({
+ name: "XPCShell",
+ ID: "xpcshell@tests.mozilla.org",
+ version,
+ platformVersion: version,
+ });
+ } else {
+ AddonTestUtils.init(GLOBAL_SCOPE);
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ version,
+ version
+ );
+ }
+
+ await maybeSetupConfig();
+
+ // Disable region checks.
+ Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
+
+ // Enable separatePrivateDefault testing. We test with this on, as we have
+ // separate tests for ensuring the normal = private when this is off.
+ Services.prefs.setBoolPref(
+ SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault.ui.enabled",
+ true
+ );
+ Services.prefs.setBoolPref(
+ SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
+ true
+ );
+
+ if (!SearchUtils.newSearchConfigEnabled) {
+ await AddonTestUtils.promiseStartupManager();
+ }
+ await Services.search.init();
+
+ // We must use the engine selector that the search service has created (if
+ // it has), as remote settings can only easily deal with us loading the
+ // configuration once - after that, it tries to access the network.
+ engineSelector =
+ Services.search.wrappedJSObject._engineSelector ||
+ SearchUtils.newSearchConfigEnabled
+ ? new SearchEngineSelector()
+ : new SearchEngineSelectorOld();
+
+ // Note: we don't use the helper function here, so that we have at least
+ // one message output per process.
+ Assert.ok(
+ Services.search.isInitialized,
+ "Should have correctly initialized the search service"
+ );
+ }
+
+ /**
+ * Runs the test.
+ */
+ async run() {
+ const locales = await this.getLocales();
+ const regions = this._regions;
+
+ // We loop on region and then locale, so that we always cause a re-init
+ // when updating the requested/available locales.
+ for (let region of regions) {
+ for (let locale of locales) {
+ const engines = await this._getEngines(region, locale);
+ this._assertEngineRules([engines[0]], region, locale, "default");
+ const isPresent = this._assertAvailableEngines(region, locale, engines);
+ if (isPresent) {
+ this._assertEngineDetails(region, locale, engines);
+ }
+ }
+ }
+ }
+
+ async _getEngines(region, locale) {
+ let configs = await engineSelector.fetchEngineConfiguration({
+ locale,
+ region: region || "default",
+ channel: SearchUtils.MODIFIED_APP_CHANNEL,
+ });
+
+ return SearchTestUtils.searchConfigToEngines(configs.engines);
+ }
+
+ /**
+ * @returns {Set} the list of regions for the tests to run with.
+ */
+ get _regions() {
+ // TODO: The legacy configuration worked with null as an unknown region,
+ // for the search engine selector, we expect "default" but apply the
+ // fallback in _getEngines. Once we remove the legacy configuration, we can
+ // simplify this.
+ if (TEST_DEBUG) {
+ return new Set(["by", "cn", "kz", "us", "ru", "tr", null]);
+ }
+ return [...Services.intl.getAvailableLocaleDisplayNames("region"), null];
+ }
+
+ /**
+ * @returns {Array} the list of locales for the tests to run with.
+ */
+ async getLocales() {
+ if (TEST_DEBUG) {
+ return ["be", "en-US", "kk", "tr", "ru", "zh-CN", "ach", "unknown"];
+ }
+ const data = await IOUtils.readUTF8(do_get_file("all-locales").path);
+ // "en-US" is not in all-locales as it is the default locale
+ // add it manually to ensure it is tested.
+ let locales = [...data.split("\n").filter(e => e != ""), "en-US"];
+ // BCP47 requires all variants are 5-8 characters long. Our
+ // build sytem uses the short `mac` variant, this is invalid, and inside
+ // the app we turn it into `ja-JP-macos`
+ locales = locales.map(l => (l == "ja-JP-mac" ? "ja-JP-macos" : l));
+ // The locale sometimes can be unknown or a strange name, e.g. if the updater
+ // is disabled, it may be "und", add one here so we know what happens if we
+ // hit it.
+ locales.push("unknown");
+ return locales;
+ }
+
+ /**
+ * Determines if a locale/region pair match a section of the configuration.
+ *
+ * @param {object} section
+ * The configuration section to match against.
+ * @param {string} region
+ * The two-letter region code.
+ * @param {string} locale
+ * The two-letter locale code.
+ * @returns {boolean}
+ * True if the locale/region pair matches the section.
+ */
+ _localeRegionInSection(section, region, locale) {
+ for (const { regions, locales } of section) {
+ // If we only specify a regions or locales section then
+ // it is always considered included in the other section.
+ const inRegions = !regions || regions.includes(region);
+ const inLocales = !locales || locales.includes(locale);
+ if (inRegions && inLocales) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Helper function to find an engine from within a list.
+ *
+ * @param {Array} engines
+ * The list of engines to check.
+ * @param {string} identifier
+ * The identifier to look for in the list.
+ * @param {boolean} exactMatch
+ * Whether to use an exactMatch for the identifier.
+ * @returns {Engine}
+ * Returns the engine if found, null otherwise.
+ */
+ _findEngine(engines, identifier, exactMatch) {
+ return engines.find(engine =>
+ exactMatch
+ ? engine.identifier == identifier
+ : engine.identifier.startsWith(identifier)
+ );
+ }
+
+ /**
+ * Asserts whether the engines rules defined in the configuration are met.
+ *
+ * @param {Array} engines
+ * The list of engines to check.
+ * @param {string} region
+ * The two-letter region code.
+ * @param {string} locale
+ * The two-letter locale code.
+ * @param {string} section
+ * The section of the configuration to check.
+ * @returns {boolean}
+ * Returns true if the engine is expected to be present, false otherwise.
+ */
+ _assertEngineRules(engines, region, locale, section) {
+ const infoString = `region: "${region}" locale: "${locale}"`;
+ const config = this._config[section];
+ const hasIncluded = "included" in config;
+ const hasExcluded = "excluded" in config;
+ const identifierIncluded = !!this._findEngine(
+ engines,
+ this._config.identifier,
+ this._config.identifierExactMatch ?? false
+ );
+
+ // If there's not included/excluded, then this shouldn't be the default anywhere.
+ if (section == "default" && !hasIncluded && !hasExcluded) {
+ this.assertOk(
+ !identifierIncluded,
+ `Should not be ${section} for any locale/region,
+ currently set for ${infoString}`
+ );
+ return false;
+ }
+
+ // If there's no included section, we assume the engine is default everywhere
+ // and we should apply the exclusions instead.
+ let included =
+ hasIncluded &&
+ this._localeRegionInSection(config.included, region, locale);
+
+ let excluded =
+ hasExcluded &&
+ this._localeRegionInSection(config.excluded, region, locale);
+ if (
+ (included && (!hasExcluded || !excluded)) ||
+ (!hasIncluded && hasExcluded && !excluded)
+ ) {
+ this.assertOk(
+ identifierIncluded,
+ `Should be ${section} for ${infoString}`
+ );
+ return true;
+ }
+ this.assertOk(
+ !identifierIncluded,
+ `Should not be ${section} for ${infoString}`
+ );
+ return false;
+ }
+
+ /**
+ * Asserts whether the engine is correctly set as default or not.
+ *
+ * @param {string} region
+ * The two-letter region code.
+ * @param {string} locale
+ * The two-letter locale code.
+ */
+ _assertDefaultEngines(region, locale) {
+ this._assertEngineRules(
+ [Services.search.appDefaultEngine],
+ region,
+ locale,
+ "default"
+ );
+ // At the moment, this uses the same section as the normal default, as
+ // we don't set this differently for any region/locale.
+ this._assertEngineRules(
+ [Services.search.appPrivateDefaultEngine],
+ region,
+ locale,
+ "default"
+ );
+ }
+
+ /**
+ * Asserts whether the engine is correctly available or not.
+ *
+ * @param {string} region
+ * The two-letter region code.
+ * @param {string} locale
+ * The two-letter locale code.
+ * @param {Array} engines
+ * The current visible engines.
+ * @returns {boolean}
+ * Returns true if the engine is expected to be present, false otherwise.
+ */
+ _assertAvailableEngines(region, locale, engines) {
+ return this._assertEngineRules(engines, region, locale, "available");
+ }
+
+ /**
+ * Asserts the engine follows various rules.
+ *
+ * @param {string} region
+ * The two-letter region code.
+ * @param {string} locale
+ * The two-letter locale code.
+ * @param {Array} engines
+ * The current visible engines.
+ */
+ _assertEngineDetails(region, locale, engines) {
+ const details = this._config.details.filter(value => {
+ const included = this._localeRegionInSection(
+ value.included,
+ region,
+ locale
+ );
+ const excluded =
+ value.excluded &&
+ this._localeRegionInSection(value.excluded, region, locale);
+ return included && !excluded;
+ });
+ this.assertEqual(
+ details.length,
+ 1,
+ `Should have just one details section for region: ${region} locale: ${locale}`
+ );
+
+ const engine = this._findEngine(
+ engines,
+ this._config.identifier,
+ this._config.identifierExactMatch ?? false
+ );
+ this.assertOk(engine, "Should have an engine present");
+
+ if (this._config.aliases) {
+ this.assertDeepEqual(
+ engine.aliases,
+ this._config.aliases,
+ "Should have the correct aliases for the engine"
+ );
+ }
+
+ const location = `in region:${region}, locale:${locale}`;
+
+ for (const rule of details) {
+ this._assertCorrectDomains(location, engine, rule);
+ if (rule.codes) {
+ this._assertCorrectCodes(location, engine, rule);
+ }
+ if (rule.searchUrlCode || rule.suggestUrlCode) {
+ this._assertCorrectUrlCode(location, engine, rule);
+ }
+ if (rule.aliases) {
+ this.assertDeepEqual(
+ engine.aliases,
+ rule.aliases,
+ "Should have the correct aliases for the engine"
+ );
+ }
+ if (rule.telemetryId) {
+ this.assertEqual(
+ engine.telemetryId,
+ rule.telemetryId,
+ `Should have the correct telemetryId ${location}.`
+ );
+ }
+ }
+ }
+
+ /**
+ * Asserts whether the engine is using the correct domains or not.
+ *
+ * @param {string} location
+ * Debug string with locale + region information.
+ * @param {object} engine
+ * The engine being tested.
+ * @param {object} rules
+ * Rules to test.
+ */
+ _assertCorrectDomains(location, engine, rules) {
+ this.assertOk(
+ rules.domain,
+ `Should have an expectedDomain for the engine ${location}`
+ );
+
+ const searchForm = new URL(engine.searchForm);
+ this.assertOk(
+ searchForm.host.endsWith(rules.domain),
+ `Should have the correct search form domain ${location}.
+ Got "${searchForm.host}", expected to end with "${rules.domain}".`
+ );
+
+ let submission = engine.getSubmission("test", URLTYPE_SEARCH_HTML);
+
+ this.assertOk(
+ submission.uri.host.endsWith(rules.domain),
+ `Should have the correct domain for type: ${URLTYPE_SEARCH_HTML} ${location}.
+ Got "${submission.uri.host}", expected to end with "${rules.domain}".`
+ );
+
+ submission = engine.getSubmission("test", URLTYPE_SUGGEST_JSON);
+ if (this._config.noSuggestionsURL || rules.noSuggestionsURL) {
+ this.assertOk(!submission, "Should not have a submission url");
+ } else if (this._config.suggestionUrlBase) {
+ this.assertEqual(
+ submission.uri.prePath + submission.uri.filePath,
+ this._config.suggestionUrlBase,
+ `Should have the correct domain for type: ${URLTYPE_SUGGEST_JSON} ${location}.`
+ );
+ this.assertOk(
+ submission.uri.query.includes(rules.suggestUrlCode),
+ `Should have the code in the uri`
+ );
+ }
+ }
+
+ /**
+ * Asserts whether the engine is using the correct codes or not.
+ *
+ * @param {string} location
+ * Debug string with locale + region information.
+ * @param {object} engine
+ * The engine being tested.
+ * @param {object} rules
+ * Rules to test.
+ */
+ _assertCorrectCodes(location, engine, rules) {
+ for (const purpose of SUBMISSION_PURPOSES) {
+ // Don't need to repeat the code if we use it for all purposes.
+ const code =
+ typeof rules.codes === "string" ? rules.codes : rules.codes[purpose];
+ const submission = engine.getSubmission("test", "text/html", purpose);
+ const submissionQueryParams = submission.uri.query.split("&");
+ this.assertOk(
+ submissionQueryParams.includes(code),
+ `Expected "${code}" in url "${submission.uri.spec}" from purpose "${purpose}" ${location}`
+ );
+
+ const paramName = code.split("=")[0];
+ this.assertOk(
+ submissionQueryParams.filter(param => param.startsWith(paramName))
+ .length == 1,
+ `Expected only one "${paramName}" parameter in "${submission.uri.spec}" from purpose "${purpose}" ${location}`
+ );
+ }
+ }
+
+ /**
+ * Asserts whether the engine is using the correct URL codes or not.
+ *
+ * @param {string} location
+ * Debug string with locale + region information.
+ * @param {object} engine
+ * The engine being tested.
+ * @param {object} rule
+ * Rules to test.
+ */
+ _assertCorrectUrlCode(location, engine, rule) {
+ if (rule.searchUrlCode) {
+ const submission = engine.getSubmission("test", URLTYPE_SEARCH_HTML);
+ this.assertOk(
+ submission.uri.query.split("&").includes(rule.searchUrlCode),
+ `Expected "${rule.searchUrlCode}" in search url "${submission.uri.spec}"`
+ );
+ let uri = engine.searchForm;
+ this.assertOk(
+ !uri.includes(rule.searchUrlCode),
+ `"${rule.searchUrlCode}" should not be in the search form URL.`
+ );
+ }
+ if (rule.searchUrlCodeNotInQuery) {
+ const submission = engine.getSubmission("test", URLTYPE_SEARCH_HTML);
+ this.assertOk(
+ submission.uri.includes(rule.searchUrlCodeNotInQuery),
+ `Expected "${rule.searchUrlCodeNotInQuery}" in search url "${submission.uri.spec}"`
+ );
+ }
+ if (rule.suggestUrlCode) {
+ const submission = engine.getSubmission("test", URLTYPE_SUGGEST_JSON);
+ this.assertOk(
+ submission.uri.query.split("&").includes(rule.suggestUrlCode),
+ `Expected "${rule.suggestUrlCode}" in suggestion url "${submission.uri.spec}"`
+ );
+ }
+ }
+
+ /**
+ * Helper functions which avoid outputting test results when there are no
+ * failures. These help the tests to run faster, and avoid clogging up the
+ * python test runner process.
+ */
+
+ assertOk(value, message) {
+ if (!value || TEST_DEBUG) {
+ Assert.ok(value, message);
+ }
+ }
+
+ assertEqual(actual, expected, message) {
+ if (actual != expected || TEST_DEBUG) {
+ Assert.equal(actual, expected, message);
+ }
+ }
+
+ assertDeepEqual(actual, expected, message) {
+ if (!ObjectUtils.deepEqual(actual, expected)) {
+ Assert.deepEqual(actual, expected, message);
+ }
+ }
+}
+
+async function checkUISchemaValid(configSchema, uiSchema) {
+ for (let key of Object.keys(configSchema.properties)) {
+ Assert.ok(
+ uiSchema["ui:order"].includes(key),
+ `Should have ${key} listed at the top-level of the ui schema`
+ );
+ }
+}
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js
new file mode 100644
index 0000000000..bfe05e1596
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "amazon",
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ included: [
+ {
+ // The main regions we ship Amazon to. Below this are special cases.
+ regions: ["us", "jp"],
+ },
+ ],
+ },
+ details: [
+ {
+ domain: "amazon.co.jp",
+ telemetryId: "amazon-jp",
+ aliases: ["@amazon"],
+ included: [
+ {
+ regions: ["jp"],
+ },
+ ],
+ searchUrlCode: "tag=mozillajapan-fx-22",
+ noSuggestionsURL: true,
+ },
+ {
+ domain: "amazon.com",
+ telemetryId: "amazondotcom-us-adm",
+ aliases: ["@amazon"],
+ included: [
+ {
+ regions: ["us"],
+ },
+ ],
+ noSuggestionsURL: true,
+ searchUrlCode: "tag=admarketus-20",
+ },
+ ],
+});
+
+add_setup(async function () {
+ // We only need to do setup on one of the tests.
+ await test.setup("89.0");
+});
+
+add_task(async function test_searchConfig_amazon() {
+ await test.run();
+});
+
+add_task(
+ { skip_if: () => SearchUtils.newSearchConfigEnabled },
+ async function test_searchConfig_amazon_pre89() {
+ const version = "88.0";
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ version,
+ version
+ );
+ // For pre-89, Amazon has a slightly different config.
+ let details = test._config.details.find(
+ d => d.telemetryId == "amazondotcom-us-adm"
+ );
+ details.telemetryId = "amazondotcom";
+ delete details.searchUrlCode;
+
+ await test.run();
+ }
+);
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js
new file mode 100644
index 0000000000..3c66708bdc
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "baidu",
+ aliases: ["@\u767E\u5EA6", "@baidu"],
+ default: {
+ included: [
+ {
+ regions: ["cn"],
+ locales: ["zh-CN"],
+ },
+ ],
+ },
+ available: {
+ included: [
+ {
+ locales: ["zh-CN"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "baidu.com",
+ telemetryId: "baidu",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_baidu() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js
new file mode 100644
index 0000000000..6dbddd8a08
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "bing",
+ aliases: ["@bing"],
+ default: {
+ // Not included anywhere.
+ },
+ available: {
+ included: [
+ {
+ // regions: [
+ // These arent currently enforced.
+ // "au", "at", "be", "br", "ca", "fi", "fr", "de",
+ // "in", "ie", "it", "jp", "my", "mx", "nl", "nz",
+ // "no", "sg", "es", "se", "ch", "gb", "us",
+ // ],
+ locales: [
+ "ach",
+ "af",
+ "an",
+ "ar",
+ "ast",
+ "az",
+ "bn",
+ "bs",
+ "ca",
+ "ca-valencia",
+ "cak",
+ "cs",
+ "cy",
+ "da",
+ "de",
+ "dsb",
+ "el",
+ "en-CA",
+ "en-GB",
+ "en-US",
+ "eo",
+ "es-CL",
+ "es-ES",
+ "es-MX",
+ "eu",
+ "fa",
+ "ff",
+ "fi",
+ "fr",
+ "fur",
+ "fy-NL",
+ "gd",
+ "gl",
+ "gn",
+ "gu-IN",
+ "he",
+ "hi-IN",
+ "hr",
+ "hsb",
+ "hy-AM",
+ "ia",
+ "id",
+ "is",
+ "it",
+ "ja-JP-macos",
+ "ja",
+ "ka",
+ "kab",
+ "km",
+ "kn",
+ "lij",
+ "lo",
+ "lt",
+ "meh",
+ "mk",
+ "ms",
+ "my",
+ "nb-NO",
+ "ne-NP",
+ "nl",
+ "nn-NO",
+ "oc",
+ "pa-IN",
+ "pt-BR",
+ "rm",
+ "ro",
+ "sc",
+ "sco",
+ "son",
+ "sq",
+ "sr",
+ "sv-SE",
+ "te",
+ "th",
+ "tl",
+ "tr",
+ "trs",
+ "uk",
+ "ur",
+ "uz",
+ "wo",
+ "xh",
+ "zh-CN",
+ ],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "bing.com",
+ telemetryId:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "bing-esr" : "bing",
+ codes: {
+ searchbar: "form=MOZSBR",
+ keyword: "form=MOZLBR",
+ contextmenu: "form=MOZCON",
+ homepage: "form=MOZSPG",
+ newtab: "form=MOZTSB",
+ },
+ searchUrlCode:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "pc=MOZR" : "pc=MOZI",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_bing() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_distributions.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_distributions.js
new file mode 100644
index 0000000000..513fdaafef
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_distributions.js
@@ -0,0 +1,348 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ SearchService: "resource://gre/modules/SearchService.sys.mjs",
+});
+
+const tests = [];
+
+for (let canonicalId of ["canonical", "canonical-001"]) {
+ tests.push({
+ locale: "en-US",
+ region: "US",
+ distribution: canonicalId,
+ test: engines =>
+ hasParams(engines, "Google", "searchbar", "client=ubuntu") &&
+ hasParams(engines, "Google", "searchbar", "channel=fs") &&
+ hasTelemetryId(engines, "Google", "google-canonical"),
+ });
+
+ tests.push({
+ locale: "en-US",
+ region: "GB",
+ distribution: canonicalId,
+ test: engines =>
+ hasParams(engines, "Google", "searchbar", "client=ubuntu") &&
+ hasParams(engines, "Google", "searchbar", "channel=fs") &&
+ hasTelemetryId(engines, "Google", "google-canonical"),
+ });
+}
+
+tests.push({
+ locale: "en-US",
+ region: "US",
+ distribution: "canonical-002",
+ test: engines =>
+ hasParams(engines, "Google", "searchbar", "client=ubuntu-sn") &&
+ hasParams(engines, "Google", "searchbar", "channel=fs") &&
+ hasTelemetryId(engines, "Google", "google-ubuntu-sn"),
+});
+
+tests.push({
+ locale: "en-US",
+ region: "GB",
+ distribution: "canonical-002",
+ test: engines =>
+ hasParams(engines, "Google", "searchbar", "client=ubuntu-sn") &&
+ hasParams(engines, "Google", "searchbar", "channel=fs") &&
+ hasTelemetryId(engines, "Google", "google-ubuntu-sn"),
+});
+
+tests.push({
+ locale: "zh-CN",
+ region: "CN",
+ distribution: "MozillaOnline",
+ test: engines =>
+ hasEnginesFirst(engines, ["百度", "Bing", "Google", "维基百科"]),
+});
+
+tests.push({
+ locale: "fr",
+ distribution: "qwant-001",
+ test: engines =>
+ hasParams(engines, "Qwant", "searchbar", "client=firefoxqwant") &&
+ hasDefault(engines, "Qwant") &&
+ hasEnginesFirst(engines, ["Qwant", "Qwant Junior"]),
+});
+
+tests.push({
+ locale: "fr",
+ distribution: "qwant-001",
+ test: engines =>
+ hasParams(engines, "Qwant Junior", "searchbar", "client=firefoxqwant"),
+});
+
+tests.push({
+ locale: "fr",
+ distribution: "qwant-002",
+ test: engines =>
+ hasParams(engines, "Qwant", "searchbar", "client=firefoxqwant") &&
+ hasDefault(engines, "Qwant") &&
+ hasEnginesFirst(engines, ["Qwant", "Qwant Junior"]),
+});
+
+tests.push({
+ locale: "fr",
+ distribution: "qwant-002",
+ test: engines =>
+ hasParams(engines, "Qwant Junior", "searchbar", "client=firefoxqwant"),
+});
+
+for (const locale of ["en-US", "de"]) {
+ tests.push({
+ locale,
+ distribution: "1und1",
+ test: engines =>
+ hasParams(engines, "1&1 Suche", "searchbar", "enc=UTF-8") &&
+ hasDefault(engines, "1&1 Suche") &&
+ hasEnginesFirst(engines, ["1&1 Suche"]),
+ });
+
+ tests.push({
+ locale,
+ distribution: "gmx",
+ test: engines =>
+ hasParams(engines, "GMX Suche", "searchbar", "enc=UTF-8") &&
+ hasDefault(engines, "GMX Suche") &&
+ hasEnginesFirst(engines, ["GMX Suche"]),
+ });
+
+ tests.push({
+ locale,
+ distribution: "gmx",
+ test: engines =>
+ hasParams(engines, "GMX Shopping", "searchbar", "origin=br_osd"),
+ });
+
+ tests.push({
+ locale,
+ distribution: "mail.com",
+ test: engines =>
+ hasParams(engines, "mail.com search", "searchbar", "enc=UTF-8") &&
+ hasDefault(engines, "mail.com search") &&
+ hasEnginesFirst(engines, ["mail.com search"]),
+ });
+
+ tests.push({
+ locale,
+ distribution: "webde",
+ test: engines =>
+ hasParams(engines, "WEB.DE Suche", "searchbar", "enc=UTF-8") &&
+ hasDefault(engines, "WEB.DE Suche") &&
+ hasEnginesFirst(engines, ["WEB.DE Suche"]),
+ });
+}
+
+tests.push({
+ locale: "ru",
+ region: "RU",
+ distribution: "gmx",
+ test: engines => hasDefault(engines, "GMX Suche"),
+});
+
+tests.push({
+ locale: "en-GB",
+ distribution: "gmxcouk",
+ test: engines =>
+ hasURLs(
+ engines,
+ "GMX Search",
+ "https://go.gmx.co.uk/br/moz_search_web/?enc=UTF-8&q=test",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://suggestplugin.gmx.co.uk/s?brand=gmxcouk&origin=moz_splugin_ff&enc=UTF-8&q=test"
+ : "https://suggestplugin.gmx.co.uk/s?q=test&brand=gmxcouk&origin=moz_splugin_ff&enc=UTF-8"
+ ) &&
+ hasDefault(engines, "GMX Search") &&
+ hasEnginesFirst(engines, ["GMX Search"]),
+});
+
+tests.push({
+ locale: "ru",
+ region: "RU",
+ distribution: "gmxcouk",
+ test: engines => hasDefault(engines, "GMX Search"),
+});
+
+tests.push({
+ locale: "es",
+ distribution: "gmxes",
+ test: engines =>
+ hasURLs(
+ engines,
+ "GMX - Búsqueda web",
+ "https://go.gmx.es/br/moz_search_web/?enc=UTF-8&q=test",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://suggestplugin.gmx.es/s?brand=gmxes&origin=moz_splugin_ff&enc=UTF-8&q=test"
+ : "https://suggestplugin.gmx.es/s?q=test&brand=gmxes&origin=moz_splugin_ff&enc=UTF-8"
+ ) &&
+ hasDefault(engines, "GMX Search") &&
+ hasEnginesFirst(engines, ["GMX Search"]),
+});
+
+tests.push({
+ locale: "ru",
+ region: "RU",
+ distribution: "gmxes",
+ test: engines => hasDefault(engines, "GMX - Búsqueda web"),
+});
+
+tests.push({
+ locale: "fr",
+ distribution: "gmxfr",
+ test: engines =>
+ hasURLs(
+ engines,
+ "GMX - Recherche web",
+ "https://go.gmx.fr/br/moz_search_web/?enc=UTF-8&q=test",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://suggestplugin.gmx.fr/s?brand=gmxfr&origin=moz_splugin_ff&enc=UTF-8&q=test"
+ : "https://suggestplugin.gmx.fr/s?q=test&brand=gmxfr&origin=moz_splugin_ff&enc=UTF-8"
+ ) &&
+ hasDefault(engines, "GMX Search") &&
+ hasEnginesFirst(engines, ["GMX Search"]),
+});
+
+tests.push({
+ locale: "ru",
+ region: "RU",
+ distribution: "gmxfr",
+ test: engines => hasDefault(engines, "GMX - Recherche web"),
+});
+
+tests.push({
+ locale: "en-US",
+ region: "US",
+ distribution: "mint-001",
+ test: engines =>
+ hasParams(engines, "DuckDuckGo", "searchbar", "t=lm") &&
+ hasParams(engines, "Google", "searchbar", "client=firefox-b-1-lm") &&
+ hasDefault(engines, "Google") &&
+ hasEnginesFirst(engines, ["Google"]) &&
+ hasTelemetryId(engines, "Google", "google-b-1-lm"),
+});
+
+tests.push({
+ locale: "en-GB",
+ region: "GB",
+ distribution: "mint-001",
+ test: engines =>
+ hasParams(engines, "DuckDuckGo", "searchbar", "t=lm") &&
+ hasParams(engines, "Google", "searchbar", "client=firefox-b-lm") &&
+ hasDefault(engines, "Google") &&
+ hasEnginesFirst(engines, ["Google"]) &&
+ hasTelemetryId(engines, "Google", "google-b-lm"),
+});
+
+function hasURLs(engines, engineName, url, suggestURL) {
+ let engine = engines.find(e => e._name === engineName);
+ Assert.ok(engine, `Should be able to find ${engineName}`);
+
+ let submission = engine.getSubmission("test", "text/html");
+ Assert.equal(
+ submission.uri.spec,
+ url,
+ `Should have the correct submission url for ${engineName}`
+ );
+
+ submission = engine.getSubmission("test", "application/x-suggestions+json");
+ Assert.equal(
+ submission.uri.spec,
+ suggestURL,
+ `Should have the correct suggestion url for ${engineName}`
+ );
+}
+
+function hasParams(engines, engineName, purpose, param) {
+ let engine = engines.find(e => e._name === engineName);
+ Assert.ok(engine, `Should be able to find ${engineName}`);
+
+ let submission = engine.getSubmission("test", "text/html", purpose);
+ let queries = submission.uri.query.split("&");
+
+ let paramNames = new Set();
+ for (let query of queries) {
+ let queryParam = query.split("=")[0];
+ Assert.ok(
+ !paramNames.has(queryParam),
+ `Should not have a duplicate ${queryParam} param`
+ );
+ paramNames.add(queryParam);
+ }
+
+ let result = queries.includes(param);
+ Assert.ok(result, `expect ${submission.uri.query} to include ${param}`);
+ return true;
+}
+
+function hasTelemetryId(engines, engineName, telemetryId) {
+ let engine = engines.find(e => e._name === engineName);
+ Assert.ok(engine, `Should be able to find ${engineName}`);
+
+ Assert.equal(
+ engine.telemetryId,
+ telemetryId,
+ "Should have the correct telemetryId"
+ );
+ return true;
+}
+
+function hasDefault(engines, expectedDefaultName) {
+ Assert.equal(
+ engines[0].name,
+ expectedDefaultName,
+ "Should have the expected engine set as default"
+ );
+ return true;
+}
+
+function hasEnginesFirst(engines, expectedEngines) {
+ for (let [i, expectedEngine] of expectedEngines.entries()) {
+ Assert.equal(
+ engines[i].name,
+ expectedEngine,
+ `Should have the expected engine in position ${i}`
+ );
+ }
+}
+
+engineSelector = SearchUtils.newSearchConfigEnabled
+ ? new SearchEngineSelector()
+ : new SearchEngineSelectorOld();
+
+add_setup(async function () {
+ if (!SearchUtils.newSearchConfigEnabled) {
+ AddonTestUtils.init(GLOBAL_SCOPE);
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "42",
+ "42"
+ );
+ await AddonTestUtils.promiseStartupManager();
+ }
+
+ await maybeSetupConfig();
+});
+
+add_task(async function test_expected_distribution_engines() {
+ let searchService = new SearchService();
+ for (const { distribution, locale = "en-US", region = "US", test } of tests) {
+ let config = await engineSelector.fetchEngineConfiguration({
+ locale,
+ region,
+ distroID: distribution,
+ });
+ let engines = await SearchTestUtils.searchConfigToEngines(config.engines);
+ searchService._engines = engines;
+ searchService._searchDefault = {
+ id: config.engines[0].webExtension.id,
+ locale:
+ config.engines[0]?.webExtension?.locale ?? SearchUtils.DEFAULT_TAG,
+ };
+ engines = searchService._sortEnginesByDefaults(engines);
+ test(engines);
+ }
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js
new file mode 100644
index 0000000000..ffbd3fb1ce
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "ddg",
+ aliases: ["@duckduckgo", "@ddg"],
+ default: {
+ // Not included anywhere.
+ },
+ available: {
+ excluded: [
+ // Should be available everywhere.
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "duckduckgo.com",
+ telemetryId:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "ddg-esr" : "ddg",
+ searchUrlCode:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "t=ftsa" : "t=ffab",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_duckduckgo() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js
new file mode 100644
index 0000000000..ed8e5147ee
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js
@@ -0,0 +1,276 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const DOMAIN_LOCALES = {
+ "ebay-ca": ["en-CA"],
+ "ebay-ch": ["rm"],
+ "ebay-de": ["de", "dsb", "hsb"],
+ "ebay-es": ["an", "ast", "ca", "ca-valencia", "es-ES", "eu", "gl"],
+ "ebay-ie": ["ga-IE", "ie"],
+ "ebay-it": ["fur", "it", "lij", "sc"],
+ "ebay-nl": ["fy-NL", "nl"],
+ "ebay-uk": ["cy", "en-GB", "gd"],
+};
+
+const test = new SearchConfigTest({
+ identifier: "ebay",
+ aliases: ["@ebay"],
+ default: {
+ // Not included anywhere.
+ },
+ available: {
+ included: [
+ {
+ // We don't currently enforce by region, but do locale instead.
+ // regions: [
+ // "us", "gb", "ca", "ie", "fr", "it", "de", "at", "es", "nl", "ch", "au"
+ // ],
+ locales: [
+ "an",
+ "ast",
+ "br",
+ "ca",
+ "ca-valencia",
+ "cy",
+ "de",
+ "dsb",
+ "en-CA",
+ "en-GB",
+ "es-ES",
+ "eu",
+ "fur",
+ "fr",
+ "fy-NL",
+ "ga-IE",
+ "gd",
+ "gl",
+ "hsb",
+ "it",
+ "lij",
+ "nl",
+ "rm",
+ "sc",
+ "wo",
+ ],
+ },
+ {
+ regions: ["au", "be", "ca", "ch", "gb", "ie", "nl", "us"],
+ locales: ["en-US"],
+ },
+ {
+ regions: ["gb"],
+ locales: ["sco"],
+ },
+ ],
+ },
+ suggestionUrlBase: "https://autosug.ebay.com/autosug",
+ details: [
+ {
+ // Note: These should be based on region, but we don't currently enforce that.
+ // Note: the order here is important. A region/locale match higher up in the
+ // list will override a region/locale match lower down.
+ domain: "www.befr.ebay.be",
+ telemetryId: "ebay-be",
+ included: [
+ {
+ regions: ["be"],
+ locales: ["br", "unknown", "en-US", "fr", "fy-NL", "nl", "wo"],
+ },
+ ],
+ searchUrlCode: "mkrid=1553-53471-19255-0",
+ suggestUrlCode: "sId=23",
+ },
+ {
+ domain: "www.ebay.at",
+ telemetryId: "ebay-at",
+ included: [
+ {
+ regions: ["at"],
+ locales: ["de", "dsb", "hsb"],
+ },
+ ],
+ searchUrlCode: "mkrid=5221-53469-19255-0",
+ suggestUrlCode: "sId=16",
+ },
+ {
+ domain: "www.ebay.ca",
+ telemetryId: "ebay-ca",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-ca"],
+ },
+ {
+ regions: ["ca"],
+ },
+ ],
+ excluded: [
+ {
+ locales: [
+ ...DOMAIN_LOCALES["ebay-ch"],
+ ...DOMAIN_LOCALES["ebay-de"],
+ ...DOMAIN_LOCALES["ebay-es"],
+ ...DOMAIN_LOCALES["ebay-ie"],
+ ...DOMAIN_LOCALES["ebay-it"],
+ ...DOMAIN_LOCALES["ebay-nl"],
+ ...DOMAIN_LOCALES["ebay-uk"],
+ ],
+ },
+ ],
+ searchUrlCode: "mkrid=706-53473-19255-0",
+ suggestUrlCode: "sId=2",
+ },
+ {
+ domain: "www.ebay.ch",
+ telemetryId: "ebay-ch",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-ch"],
+ },
+ {
+ regions: ["ch"],
+ },
+ ],
+ excluded: [
+ {
+ locales: [
+ ...DOMAIN_LOCALES["ebay-ca"],
+ ...DOMAIN_LOCALES["ebay-es"],
+ ...DOMAIN_LOCALES["ebay-ie"],
+ ...DOMAIN_LOCALES["ebay-it"],
+ ...DOMAIN_LOCALES["ebay-nl"],
+ ...DOMAIN_LOCALES["ebay-uk"],
+ ],
+ },
+ ],
+ searchUrlCode: "mkrid=5222-53480-19255-0",
+ suggestUrlCode: "sId=193",
+ },
+ {
+ domain: "www.ebay.com",
+ telemetryId: "ebay",
+ included: [
+ {
+ locales: ["unknown", "en-US"],
+ },
+ ],
+ excluded: [{ regions: ["au", "be", "ca", "ch", "gb", "ie", "nl"] }],
+ searchUrlCode: "mkrid=711-53200-19255-0",
+ suggestUrlCode: "sId=0",
+ },
+ {
+ domain: "www.ebay.com.au",
+ telemetryId: "ebay-au",
+ included: [
+ {
+ regions: ["au"],
+ locales: ["cy", "unknown", "en-GB", "en-US", "gd"],
+ },
+ ],
+ searchUrlCode: "mkrid=705-53470-19255-0",
+ suggestUrlCode: "sId=15",
+ },
+ {
+ domain: "www.ebay.ie",
+ telemetryId: "ebay-ie",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-ie"],
+ },
+ {
+ regions: ["ie"],
+ locales: ["cy", "unknown", "en-GB", "en-US", "gd"],
+ },
+ ],
+ searchUrlCode: "mkrid=5282-53468-19255-0",
+ suggestUrlCode: "sId=205",
+ },
+ {
+ domain: "www.ebay.co.uk",
+ telemetryId: "ebay-uk",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-uk"],
+ },
+ {
+ locales: ["unknown", "en-US", "sco"],
+ regions: ["gb"],
+ },
+ ],
+ excluded: [{ regions: ["au", "ie"] }],
+ searchUrlCode: "mkrid=710-53481-19255-0",
+ suggestUrlCode: "sId=3",
+ },
+ {
+ domain: "www.ebay.de",
+ telemetryId: "ebay-de",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-de"],
+ },
+ ],
+ excluded: [{ regions: ["at", "ch"] }],
+ searchUrlCode: "mkrid=707-53477-19255-0",
+ suggestUrlCode: "sId=77",
+ },
+ {
+ domain: "www.ebay.es",
+ telemetryId: "ebay-es",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-es"],
+ },
+ ],
+ searchUrlCode: "mkrid=1185-53479-19255-0",
+ suggestUrlCode: "sId=186",
+ },
+ {
+ domain: "www.ebay.fr",
+ telemetryId: "ebay-fr",
+ included: [
+ {
+ locales: ["br", "fr", "wo"],
+ },
+ ],
+ excluded: [{ regions: ["be", "ca", "ch"] }],
+ searchUrlCode: "mkrid=709-53476-19255-0",
+ suggestUrlCode: "sId=71",
+ },
+ {
+ domain: "www.ebay.it",
+ telemetryId: "ebay-it",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-it"],
+ },
+ ],
+ searchUrlCode: "mkrid=724-53478-19255-0",
+ suggestUrlCode: "sId=101",
+ },
+ {
+ domain: "www.ebay.nl",
+ telemetryId: "ebay-nl",
+ included: [
+ {
+ locales: DOMAIN_LOCALES["ebay-nl"],
+ },
+ {
+ locales: ["unknown", "en-US"],
+ regions: ["nl"],
+ },
+ ],
+ excluded: [{ regions: ["be"] }],
+ searchUrlCode: "mkrid=1346-53482-19255-0",
+ suggestUrlCode: "sId=146",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_ebay() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_ecosia.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_ecosia.js
new file mode 100644
index 0000000000..e9fc7241b1
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_ecosia.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "ecosia",
+ aliases: [],
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ included: [
+ {
+ locales: ["de"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "www.ecosia.org",
+ telemetryId: "ecosia",
+ searchUrlCode: "tt=mzl",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_ecosia() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_google.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_google.js
new file mode 100644
index 0000000000..c6b9d6a991
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_google.js
@@ -0,0 +1,171 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
+});
+
+const test = new SearchConfigTest({
+ identifier: "google",
+ aliases: ["@google"],
+ default: {
+ // Included everywhere apart from the exclusions below. These are basically
+ // just excluding what Yandex and Baidu include.
+ excluded: [
+ {
+ regions: ["cn"],
+ locales: ["zh-CN"],
+ },
+ ],
+ },
+ available: {
+ excluded: [
+ // Should be available everywhere.
+ ],
+ },
+ details: [
+ {
+ included: [{ regions: ["us"] }],
+ domain: "google.com",
+ telemetryId:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr"
+ ? "google-b-1-e"
+ : "google-b-1-d",
+ codes:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr"
+ ? "client=firefox-b-1-e"
+ : "client=firefox-b-1-d",
+ },
+ {
+ excluded: [{ regions: ["us", "by", "kz", "ru", "tr"] }],
+ included: [{}],
+ domain: "google.com",
+ telemetryId:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr" ? "google-b-e" : "google-b-d",
+ codes:
+ SearchUtils.MODIFIED_APP_CHANNEL == "esr"
+ ? "client=firefox-b-e"
+ : "client=firefox-b-d",
+ },
+ {
+ included: [{ regions: ["by", "kz", "ru", "tr"] }],
+ domain: "google.com",
+ telemetryId: "google-com-nocodes",
+ },
+ ],
+});
+
+add_setup(async function () {
+ sinon.spy(NimbusFeatures.search, "onUpdate");
+ sinon.stub(NimbusFeatures.search, "ready").resolves();
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_google() {
+ await test.run();
+});
+
+add_task(async function test_searchConfig_google_with_mozparam() {
+ // Test a couple of configurations with a MozParam set up.
+ const TEST_DATA = [
+ {
+ locale: "en-US",
+ region: "US",
+ pref: "google_channel_us",
+ expected: "us_param",
+ },
+ {
+ locale: "en-US",
+ region: "GB",
+ pref: "google_channel_row",
+ expected: "row_param",
+ },
+ ];
+
+ const defaultBranch = Services.prefs.getDefaultBranch(
+ SearchUtils.BROWSER_SEARCH_PREF
+ );
+ for (const testData of TEST_DATA) {
+ defaultBranch.setCharPref("param." + testData.pref, testData.expected);
+ }
+
+ for (const testData of TEST_DATA) {
+ info(`Checking region ${testData.region}, locale ${testData.locale}`);
+ const engines = await test._getEngines(testData.region, testData.locale);
+
+ Assert.ok(
+ engines[0].identifier.startsWith("google"),
+ "Should have the correct engine"
+ );
+ console.log(engines[0]);
+
+ const submission = engines[0].getSubmission("test", URLTYPE_SEARCH_HTML);
+ Assert.ok(
+ submission.uri.query.split("&").includes("channel=" + testData.expected),
+ "Should be including the correct MozParam parameter for the engine"
+ );
+ }
+
+ // Reset the pref values for next tests
+ for (const testData of TEST_DATA) {
+ defaultBranch.setCharPref("param." + testData.pref, "");
+ }
+});
+
+add_task(async function test_searchConfig_google_with_nimbus() {
+ let sandbox = sinon.createSandbox();
+ // Test a couple of configurations with a MozParam set up.
+ const TEST_DATA = [
+ {
+ locale: "en-US",
+ region: "US",
+ expected: "nimbus_us_param",
+ },
+ {
+ locale: "en-US",
+ region: "GB",
+ expected: "nimbus_row_param",
+ },
+ ];
+
+ Assert.ok(
+ NimbusFeatures.search.onUpdate.called,
+ "Should register an update listener for Nimbus experiments"
+ );
+ // Stub getVariable to populate the cache with our expected data
+ sandbox.stub(NimbusFeatures.search, "getVariable").returns([
+ { key: "google_channel_us", value: "nimbus_us_param" },
+ { key: "google_channel_row", value: "nimbus_row_param" },
+ ]);
+ // Set the pref cache with Nimbus values
+ NimbusFeatures.search.onUpdate.firstCall.args[0]();
+
+ for (const testData of TEST_DATA) {
+ info(`Checking region ${testData.region}, locale ${testData.locale}`);
+ const engines = await test._getEngines(testData.region, testData.locale);
+
+ Assert.ok(
+ engines[0].identifier.startsWith("google"),
+ "Should have the correct engine"
+ );
+ console.log(engines[0]);
+
+ const submission = engines[0].getSubmission("test", URLTYPE_SEARCH_HTML);
+ Assert.ok(
+ NimbusFeatures.search.ready.called,
+ "Should wait for Nimbus to get ready"
+ );
+ Assert.ok(
+ NimbusFeatures.search.getVariable,
+ "Should call NimbusFeatures.search.getVariable to populate the cache"
+ );
+ Assert.ok(
+ submission.uri.query.split("&").includes("channel=" + testData.expected),
+ "Should be including the correct MozParam parameter for the engine"
+ );
+ }
+
+ sandbox.restore();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_mailru.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_mailru.js
new file mode 100644
index 0000000000..e1a9c66b12
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_mailru.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "mailru",
+ aliases: [],
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ included: [
+ {
+ locales: ["ru"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "go.mail.ru",
+ telemetryId: "mailru",
+ codes: "gp=900200",
+ searchUrlCode: "frc=900200",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_mailru() {
+ await test.run();
+}).skip();
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js
new file mode 100644
index 0000000000..4024385729
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_qwant.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "qwant",
+ aliases: ["@qwant"],
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ included: [
+ {
+ locales: ["fr"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "www.qwant.com",
+ telemetryId: "qwant",
+ searchUrlCode: "client=brz-moz",
+ suggestUrlCode: "client=opensearch",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_qwant() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_rakuten.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_rakuten.js
new file mode 100644
index 0000000000..2c0010e2b3
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_rakuten.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "rakuten",
+ aliases: [],
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ included: [
+ {
+ locales: ["ja", "ja-JP-macos"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "rakuten.co.jp",
+ telemetryId: "rakuten",
+ searchUrlCodeNotInQuery: "013ca98b.cd7c5f0c",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_rakuten() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
new file mode 100644
index 0000000000..51e71ff573
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
+ SearchEngineSelectorOld:
+ "resource://gre/modules/SearchEngineSelectorOld.sys.mjs",
+});
+
+/**
+ * Checks to see if a value is an object or not.
+ *
+ * @param {*} value
+ * The value to check.
+ * @returns {boolean}
+ */
+function isObject(value) {
+ return value != null && typeof value == "object" && !Array.isArray(value);
+}
+
+/**
+ * This function modifies the schema to prevent allowing additional properties
+ * on objects. This is used to enforce that the schema contains everything that
+ * we deliver via the search configuration.
+ *
+ * These checks are not enabled in-product, as we want to allow older versions
+ * to keep working if we add new properties for whatever reason.
+ *
+ * @param {object} section
+ * The section to check to see if an additionalProperties flag should be added.
+ */
+function disallowAdditionalProperties(section) {
+ // It is generally acceptable for new properties to be added to the
+ // configuration as older builds will ignore them.
+ //
+ // As a result, we only check for new properties on nightly builds, and this
+ // avoids us having to uplift schema changes. This also helps preserve the
+ // schemas as documentation of "what was supported in this version".
+ if (!AppConstants.NIGHTLY_BUILD) {
+ return;
+ }
+
+ // If the section is a `oneOf` section, avoid the additionalProperties check.
+ // Otherwise, the validator expects all properties of any `oneOf` item to be
+ // present.
+ if (isObject(section)) {
+ if (section.properties && !("recordType" in section.properties)) {
+ section.additionalProperties = false;
+ }
+ if ("then" in section) {
+ section.then.additionalProperties = false;
+ }
+ }
+
+ for (let value of Object.values(section)) {
+ if (isObject(value)) {
+ disallowAdditionalProperties(value);
+ } else if (Array.isArray(value)) {
+ for (let item of value) {
+ disallowAdditionalProperties(item);
+ }
+ }
+ }
+}
+
+let searchConfigSchemaV1;
+let searchConfigSchema;
+
+add_setup(async function () {
+ searchConfigSchemaV1 = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-schema.json")
+ );
+ searchConfigSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-v2-schema.json")
+ );
+});
+
+async function checkSearchConfigValidates(schema, searchConfig) {
+ disallowAdditionalProperties(schema);
+ let validator = new JsonSchema.Validator(schema);
+
+ for (let entry of searchConfig) {
+ // Records in Remote Settings contain additional properties independent of
+ // the schema. Hence, we don't want to validate their presence.
+ delete entry.schema;
+ delete entry.id;
+ delete entry.last_modified;
+
+ let result = validator.validate(entry);
+ // entry.webExtension.id supports search-config v1.
+ let message = `Should validate ${
+ entry.identifier ?? entry.recordType ?? entry.webExtension.id
+ }`;
+ if (!result.valid) {
+ message += `:\n${JSON.stringify(result.errors, null, 2)}`;
+ }
+ Assert.ok(result.valid, message);
+
+ // All engine objects should have the base URL defined for each entry in
+ // entry.base.urls.
+ // Unfortunately this is difficult to enforce in the schema as it would
+ // need a `required` field that works across multiple levels.
+ if (entry.recordType == "engine") {
+ for (let urlEntry of Object.values(entry.base.urls)) {
+ Assert.ok(
+ urlEntry.base,
+ "Should have a base url for every URL defined on the top-level base object."
+ );
+ }
+ }
+ }
+}
+
+async function checkSearchConfigOverrideValidates(
+ schema,
+ searchConfigOverride
+) {
+ let validator = new JsonSchema.Validator(schema);
+
+ for (let entry of searchConfigOverride) {
+ // Records in Remote Settings contain additional properties independent of
+ // the schema. Hence, we don't want to validate their presence.
+ delete entry.schema;
+ delete entry.id;
+ delete entry.last_modified;
+
+ let result = validator.validate(entry);
+
+ let message = `Should validate ${entry.identifier ?? entry.telemetryId}`;
+ if (!result.valid) {
+ message += `:\n${JSON.stringify(result.errors, null, 2)}`;
+ }
+ Assert.ok(result.valid, message);
+ }
+}
+
+add_task(async function test_search_config_validates_to_schema_v1() {
+ let selector = new SearchEngineSelectorOld(() => {});
+ let searchConfig = await selector.getEngineConfiguration();
+
+ await checkSearchConfigValidates(searchConfigSchemaV1, searchConfig);
+});
+
+add_task(async function test_ui_schema_valid_v1() {
+ let uiSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-ui-schema.json")
+ );
+
+ await checkUISchemaValid(searchConfigSchemaV1, uiSchema);
+});
+
+add_task(async function test_search_config_override_validates_to_schema_v1() {
+ let selector = new SearchEngineSelectorOld(() => {});
+ let searchConfigOverrides = await selector.getEngineConfigurationOverrides();
+ let overrideSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-overrides-schema.json")
+ );
+
+ await checkSearchConfigOverrideValidates(
+ overrideSchema,
+ searchConfigOverrides
+ );
+});
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_search_config_validates_to_schema() {
+ delete SearchUtils.newSearchConfigEnabled;
+ SearchUtils.newSearchConfigEnabled = true;
+
+ let selector = new SearchEngineSelector(() => {});
+ let searchConfig = await selector.getEngineConfiguration();
+
+ await checkSearchConfigValidates(searchConfigSchema, searchConfig);
+ }
+);
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_ui_schema_valid() {
+ let uiSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-v2-ui-schema.json")
+ );
+
+ await checkUISchemaValid(searchConfigSchema, uiSchema);
+ }
+);
+
+add_task(
+ { skip_if: () => !SearchUtils.newSearchConfigEnabled },
+ async function test_search_config_override_validates_to_schema() {
+ let selector = new SearchEngineSelector(() => {});
+ let searchConfigOverrides =
+ await selector.getEngineConfigurationOverrides();
+ let overrideSchema = await IOUtils.readJSON(
+ PathUtils.join(
+ do_get_cwd().path,
+ "search-config-overrides-v2-schema.json"
+ )
+ );
+
+ await checkSearchConfigOverrideValidates(
+ overrideSchema,
+ searchConfigOverrides
+ );
+ }
+);
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js
new file mode 100644
index 0000000000..c830bb7ade
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let searchIconsSchema;
+
+add_setup(async function () {
+ searchIconsSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-icons-schema.json")
+ );
+});
+
+add_task(async function test_ui_schema_valid() {
+ let uiSchema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "search-config-icons-ui-schema.json")
+ );
+
+ await checkUISchemaValid(searchIconsSchema, uiSchema);
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_selector_db_out_of_date.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_selector_db_out_of_date.js
new file mode 100644
index 0000000000..9bd032c3b8
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_selector_db_out_of_date.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ RemoteSettingsWorker:
+ "resource://services-settings/RemoteSettingsWorker.sys.mjs",
+});
+
+do_get_profile();
+
+add_task(async function test_selector_db_out_of_date() {
+ let searchConfig = RemoteSettings(SearchUtils.SETTINGS_KEY);
+
+ // Do an initial get to pre-seed the database.
+ await searchConfig.get();
+
+ // Now clear the database and re-fill it.
+ let db = searchConfig.db;
+ await db.clear();
+ let databaseEntries = await db.list();
+ Assert.equal(databaseEntries.length, 0, "Should have cleared the database.");
+
+ // Add a dummy record with an out-of-date last modified.
+ if (SearchUtils.newSearchConfigEnabled) {
+ await RemoteSettingsWorker._execute("_test_only_import", [
+ "main",
+ SearchUtils.SETTINGS_KEY,
+ [
+ {
+ id: "b70edfdd-1c3f-4b7b-ab55-38cb048636c0",
+ identifier: "outofdate",
+ recordType: "engine",
+ base: {},
+ variants: [
+ {
+ environment: {
+ allRegionsAndLocales: true,
+ },
+ },
+ ],
+ last_modified: 1606227264000,
+ },
+ ],
+ 1606227264000,
+ ]);
+ } else {
+ await RemoteSettingsWorker._execute("_test_only_import", [
+ "main",
+ SearchUtils.SETTINGS_KEY,
+ [
+ {
+ id: "b70edfdd-1c3f-4b7b-ab55-38cb048636c0",
+ default: "yes",
+ webExtension: { id: "outofdate@search.mozilla.org" },
+ appliesTo: [{ included: { everywhere: true } }],
+ last_modified: 1606227264000,
+ },
+ ],
+ 1606227264000,
+ ]);
+ }
+
+ // Now load the configuration and check we get what we expect.
+ let engineSelector = SearchUtils.newSearchConfigEnabled
+ ? new SearchEngineSelector()
+ : new SearchEngineSelectorOld();
+
+ let result = await engineSelector.fetchEngineConfiguration({
+ // Use the fallback default locale/regions to get a simple list.
+ locale: "default",
+ region: "default",
+ });
+
+ if (SearchUtils.newSearchConfigEnabled) {
+ Assert.deepEqual(
+ result.engines.map(e => e.identifier),
+ ["google", "ddg", "wikipedia"],
+ "Should have returned the correct data."
+ );
+ } else {
+ Assert.deepEqual(
+ result.engines.map(e => e.webExtension.id),
+ [
+ "google@search.mozilla.org",
+ "wikipedia@search.mozilla.org",
+ "ddg@search.mozilla.org",
+ ],
+ "Should have returned the correct data."
+ );
+ }
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_wikipedia.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_wikipedia.js
new file mode 100644
index 0000000000..54cf764830
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_wikipedia.js
@@ -0,0 +1,171 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const testConfiguration = {
+ identifier: "wikipedia",
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ excluded: [
+ // Should be available everywhere.
+ ],
+ },
+ details: [
+ // Details generated below.
+ ],
+};
+
+/**
+ * Generates the expected details for the given locales and inserts
+ * them into the testConfiguration.
+ *
+ * @param {string[]} locales
+ * The locales for this details entry - which locales this variant of
+ * Wikipedia is expected to be deployed to.
+ * @param {string} [subDomainName]
+ * The expected sub domain name for this variant of Wikipedia. If not
+ * specified, defaults to the first item in the locales array.
+ * @param {string} [telemetrySuffix]
+ * The expected suffix used when this variant is reported via telemetry. If
+ * not specified, defaults to the first item in the array. If this is the
+ * empty string, then it "wikipedia" (i.e. no suffix) will be the expected
+ * value.
+ */
+function generateExpectedDetails(locales, subDomainName, telemetrySuffix) {
+ if (!subDomainName) {
+ subDomainName = locales[0];
+ }
+ if (telemetrySuffix == undefined) {
+ telemetrySuffix = locales[0];
+ }
+ testConfiguration.details.push({
+ domain: `${subDomainName}.wikipedia.org`,
+ telemetryId: telemetrySuffix ? `wikipedia-${telemetrySuffix}` : "wikipedia",
+ aliases: ["@wikipedia"],
+ included: [{ locales }],
+ });
+}
+
+// This is an array of an array of arguments to be passed to generateExpectedDetails().
+// These are the locale, sub domain name and telemetry id suffix expectations for
+// the test to check.
+// Note that the expectations for en.wikipedia.com are generated in add_setup.
+const LOCALES_INFO = [
+ [["af"]],
+ [["an"]],
+ [["ar"]],
+ [["ast"]],
+ [["az"]],
+ [["be"]],
+ [["bg"]],
+ [["bn"]],
+ [["br"]],
+ [["bs"]],
+ [["ca", "ca-valencia"], "ca", "ca"],
+ [["cs"], "cs", "cz"],
+ [["cy"]],
+ [["da"]],
+ [["de"]],
+ [["dsb"]],
+ [["el"]],
+ [["eo"]],
+ [["cak", "es-AR", "es-CL", "es-ES", "es-MX", "trs"], "es", "es"],
+ [["et"]],
+ [["eu"]],
+ [["fa"]],
+ [["fi"]],
+ [["fr", "ff", "son"], "fr", "fr"],
+ [["fy-NL"], "fy", "fy-NL"],
+ [["ga-IE"], "ga", "ga-IE"],
+ [["gd"]],
+ [["gl"]],
+ [["gn"]],
+ [["gu-IN"], "gu", "gu"],
+ [["hi-IN"], "hi", "hi"],
+ [["he"]],
+ [["hr"]],
+ [["hsb"]],
+ [["hu"]],
+ [["hy-AM"], "hy", "hy"],
+ [["ia"]],
+ [["id"]],
+ [["is"]],
+ [["ja", "ja-JP-macos"], "ja", "ja"],
+ [["ka"]],
+ [["kab"]],
+ [["kk"]],
+ [["km"]],
+ [["kn"]],
+ [["ko"], "ko", "kr"],
+ [["it", "fur", "sc"], "it", "it"],
+ [["lij"]],
+ [["lo"]],
+ [["lt"]],
+ [["ltg"]],
+ [["lv"]],
+ [["mk"]],
+ [["mr"]],
+ [["ms"]],
+ [["my"]],
+ [["nb-NO"], "no", "NO"],
+ [["ne-NP"], "ne", "ne"],
+ [["nl"]],
+ [["nn-NO"], "nn", "NN"],
+ [["oc"]],
+ [["pa-IN"], "pa", "pa"],
+ [["pl", "szl"], "pl", "pl"],
+ [["pt-BR", "pt-PT"], "pt", "pt"],
+ [["rm"]],
+ [["ro"]],
+ [["ru"]],
+ [["si"]],
+ [["sk"]],
+ [["sl"]],
+ [["sq"]],
+ [["sr"]],
+ [["sv-SE"], "sv", "sv-SE"],
+ [["ta"]],
+ [["te"]],
+ [["th"]],
+ [["tl"]],
+ [["tr"]],
+ [["uk"]],
+ [["ur"]],
+ [["uz"]],
+ [["vi"]],
+ [["wo"]],
+ [["zh-CN"], "zh", "zh-CN"],
+ [["zh-TW"], "zh", "zh-TW"],
+];
+
+const test = new SearchConfigTest(testConfiguration);
+
+add_setup(async function () {
+ const allLocales = await test.getLocales();
+
+ // For the "en" version of Wikipedia, we ship it to all locales where other
+ // Wikipedias are not shipped. We form the list based on all-locales to avoid
+ // needing to update the test whenever all-locales is updated.
+ let enLocales = [];
+ for (let locale of allLocales) {
+ if (!LOCALES_INFO.find(d => d[0].includes(locale))) {
+ enLocales.push(locale);
+ }
+ }
+
+ console.log("en.wikipedia.org expected locales are:", enLocales);
+ generateExpectedDetails(enLocales, "en", "");
+
+ for (let details of LOCALES_INFO) {
+ generateExpectedDetails(...details);
+ }
+
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_wikipedia() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_yahoojp.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_yahoojp.js
new file mode 100644
index 0000000000..2b80dffd17
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_yahoojp.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "yahoo-jp",
+ identifierExactMatch: true,
+ aliases: [],
+ default: {
+ // Not default anywhere.
+ },
+ available: {
+ included: [
+ {
+ locales: ["ja", "ja-JP-macos"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{}],
+ domain: "search.yahoo.co.jp",
+ telemetryId: "yahoo-jp",
+ searchUrlCode: "fr=mozff",
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_yahoojp() {
+ await test.run();
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_yandex.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_yandex.js
new file mode 100644
index 0000000000..6e2575b3c3
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_yandex.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const test = new SearchConfigTest({
+ identifier: "yandex",
+ aliases: ["@\u044F\u043D\u0434\u0435\u043A\u0441", "@yandex"],
+ default: {
+ included: [
+ {
+ regions: ["ru", "tr", "by", "kz"],
+ locales: ["ru", "tr", "be", "kk", "en-CA", "en-GB", "en-US"],
+ },
+ ],
+ },
+ available: {
+ included: [
+ {
+ locales: ["az", "ru", "be", "kk", "tr"],
+ },
+ {
+ regions: ["ru", "tr", "by", "kz"],
+ locales: ["en-CA", "en-GB", "en-US"],
+ },
+ ],
+ },
+ details: [
+ {
+ included: [{ locales: ["az"] }],
+ domain: "yandex.az",
+ telemetryId: "yandex-az",
+ codes: {
+ searchbar: "clid=2186618",
+ keyword: "clid=2186621",
+ contextmenu: "clid=2186623",
+ homepage: "clid=2186617",
+ newtab: "clid=2186620",
+ },
+ },
+ {
+ included: [{ locales: { startsWith: ["en"] } }],
+ domain: "yandex.com",
+ telemetryId: "yandex-en",
+ codes: {
+ searchbar: "clid=2186618",
+ keyword: "clid=2186621",
+ contextmenu: "clid=2186623",
+ homepage: "clid=2186617",
+ newtab: "clid=2186620",
+ },
+ },
+ {
+ included: [{ locales: ["ru"] }],
+ domain: "yandex.ru",
+ telemetryId: "yandex-ru",
+ codes: {
+ searchbar: "clid=2186618",
+ keyword: "clid=2186621",
+ contextmenu: "clid=2186623",
+ homepage: "clid=2186617",
+ newtab: "clid=2186620",
+ },
+ },
+ {
+ included: [{ locales: ["be"] }],
+ domain: "yandex.by",
+ telemetryId: "yandex-by",
+ codes: {
+ searchbar: "clid=2186618",
+ keyword: "clid=2186621",
+ contextmenu: "clid=2186623",
+ homepage: "clid=2186617",
+ newtab: "clid=2186620",
+ },
+ },
+ {
+ included: [{ locales: ["kk"] }],
+ domain: "yandex.kz",
+ telemetryId: "yandex-kk",
+ codes: {
+ searchbar: "clid=2186618",
+ keyword: "clid=2186621",
+ contextmenu: "clid=2186623",
+ homepage: "clid=2186617",
+ newtab: "clid=2186620",
+ },
+ },
+ {
+ included: [{ locales: ["tr"] }],
+ domain: "yandex.com.tr",
+ telemetryId: "yandex-tr",
+ codes: {
+ searchbar: "clid=2186618",
+ keyword: "clid=2186621",
+ contextmenu: "clid=2186623",
+ homepage: "clid=2186617",
+ newtab: "clid=2186620",
+ },
+ },
+ ],
+});
+
+add_setup(async function () {
+ await test.setup();
+});
+
+add_task(async function test_searchConfig_yandex() {
+ await test.run();
+}).skip();
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml
new file mode 100644
index 0000000000..8baff2a38d
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml
@@ -0,0 +1,65 @@
+[DEFAULT]
+firefox-appdir = "browser"
+head = "head_searchconfig.js"
+dupe-manifest = ""
+support-files = ["../../../../../../browser/locales/all-locales"]
+tags = "searchconfig remote-settings"
+# These are extensive tests, we don't need to run them on asan/tsan.
+# They are also skipped for mobile and Thunderbird as these are specifically
+# testing the Firefox config at the moment.
+skip-if = [
+ "os == 'android'",
+ "appname == 'thunderbird'",
+ "asan",
+ "tsan",
+ "debug",
+ "os == 'win' && ccov",
+]
+# These tests do take a little longer on Linux ccov, so allow that here.
+requesttimeoutfactor = 2
+
+["test_amazon.js"]
+
+["test_baidu.js"]
+
+["test_bing.js"]
+
+["test_distributions.js"]
+
+["test_duckduckgo.js"]
+
+["test_ebay.js"]
+
+["test_ecosia.js"]
+
+["test_google.js"]
+
+["test_mailru.js"]
+
+["test_qwant.js"]
+
+["test_rakuten.js"]
+
+["test_searchconfig_validates.js"]
+support-files = [
+ "../../../schema/search-config-overrides-schema.json",
+ "../../../schema/search-config-overrides-v2-schema.json",
+ "../../../schema/search-config-schema.json",
+ "../../../schema/search-config-ui-schema.json",
+ "../../../schema/search-config-v2-schema.json",
+ "../../../schema/search-config-v2-ui-schema.json",
+]
+
+["test_searchicons_validates.js"]
+support-files = [
+ "../../../schema/search-config-icons-schema.json",
+ "../../../schema/search-config-icons-ui-schema.json",
+]
+
+["test_selector_db_out_of_date.js"]
+
+["test_wikipedia.js"]
+
+["test_yahoojp.js"]
+
+["test_yandex.js"]