/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { UrlbarSearchUtils } = ChromeUtils.importESModule( "resource:///modules/UrlbarSearchUtils.sys.mjs" ); let baconEngineExtension; add_task(async function () { await UrlbarSearchUtils.init(); // Tell the search service we are running in the US. This also has the // desired side-effect of preventing our geoip lookup. Services.prefs.setCharPref("browser.search.region", "US"); Services.search.restoreDefaultEngines(); Services.search.resetToAppDefaultEngine(); }); add_task(async function search_engine_match() { let engine = await Services.search.getDefault(); let domain = engine.searchUrlDomain; let token = domain.substr(0, 1); let matchedEngine = ( await UrlbarSearchUtils.enginesForDomainPrefix(token) )[0]; Assert.equal(matchedEngine, engine); }); add_task(async function no_match() { Assert.equal( 0, (await UrlbarSearchUtils.enginesForDomainPrefix("test")).length ); }); add_task(async function hide_search_engine_nomatch() { let engine = await Services.search.getDefault(); let domain = engine.searchUrlDomain; let token = domain.substr(0, 1); let promiseTopic = promiseSearchTopic("engine-changed"); await Promise.all([Services.search.removeEngine(engine), promiseTopic]); Assert.ok(engine.hidden); let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token); Assert.ok( !matchedEngines.length || matchedEngines[0].searchUrlDomain != domain ); engine.hidden = false; await TestUtils.waitForCondition( async () => (await UrlbarSearchUtils.enginesForDomainPrefix(token)).length ); let matchedEngine2 = ( await UrlbarSearchUtils.enginesForDomainPrefix(token) )[0]; Assert.ok(matchedEngine2); await Services.search.setDefault( engine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN ); }); add_task(async function onlyEnabled_option_nomatch() { let engine = await Services.search.getDefault(); let domain = engine.searchUrlDomain; let token = domain.substr(0, 1); Services.prefs.setCharPref("browser.search.hiddenOneOffs", engine.name); let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token, { onlyEnabled: true, }); Assert.notEqual(matchedEngines[0].searchUrlDomain, domain); Services.prefs.clearUserPref("browser.search.hiddenOneOffs"); matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token, { onlyEnabled: true, }); Assert.equal(matchedEngines[0].searchUrlDomain, domain); }); add_task(async function add_search_engine_match() { Assert.equal( 0, (await UrlbarSearchUtils.enginesForDomainPrefix("bacon")).length ); baconEngineExtension = await SearchTestUtils.installSearchExtension( { name: "bacon", keyword: "pork", search_url: "https://www.bacon.moz/", }, { skipUnload: true } ); let matchedEngine = ( await UrlbarSearchUtils.enginesForDomainPrefix("bacon") )[0]; Assert.ok(matchedEngine); Assert.equal(matchedEngine.searchForm, "https://www.bacon.moz"); Assert.equal(matchedEngine.name, "bacon"); Assert.equal(matchedEngine.iconURI, null); info("also type part of the public suffix"); matchedEngine = ( await UrlbarSearchUtils.enginesForDomainPrefix("bacon.m") )[0]; Assert.ok(matchedEngine); Assert.equal(matchedEngine.searchForm, "https://www.bacon.moz"); Assert.equal(matchedEngine.name, "bacon"); Assert.equal(matchedEngine.iconURI, null); }); add_task(async function match_multiple_search_engines() { Assert.equal( 0, (await UrlbarSearchUtils.enginesForDomainPrefix("baseball")).length ); await SearchTestUtils.installSearchExtension({ name: "baseball", search_url: "https://www.baseball.moz/", }); let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix("ba"); Assert.equal( matchedEngines.length, 2, "enginesForDomainPrefix returned two engines." ); Assert.equal(matchedEngines[0].searchForm, "https://www.bacon.moz"); Assert.equal(matchedEngines[0].name, "bacon"); Assert.equal(matchedEngines[1].searchForm, "https://www.baseball.moz"); Assert.equal(matchedEngines[1].name, "baseball"); }); add_task(async function test_aliased_search_engine_match() { Assert.equal(null, await UrlbarSearchUtils.engineForAlias("sober")); // Lower case let matchedEngine = await UrlbarSearchUtils.engineForAlias("pork"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "bacon"); Assert.ok(matchedEngine.aliases.includes("pork")); Assert.equal(matchedEngine.iconURI, null); // Upper case matchedEngine = await UrlbarSearchUtils.engineForAlias("PORK"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "bacon"); Assert.ok(matchedEngine.aliases.includes("pork")); Assert.equal(matchedEngine.iconURI, null); // Cap case matchedEngine = await UrlbarSearchUtils.engineForAlias("Pork"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "bacon"); Assert.ok(matchedEngine.aliases.includes("pork")); Assert.equal(matchedEngine.iconURI, null); }); add_task(async function test_aliased_search_engine_match_upper_case_alias() { Assert.equal( 0, (await UrlbarSearchUtils.enginesForDomainPrefix("patch")).length ); await SearchTestUtils.installSearchExtension({ name: "patch", keyword: "PR", search_url: "https://www.patch.moz/", }); // lower case let matchedEngine = await UrlbarSearchUtils.engineForAlias("pr"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "patch"); Assert.ok(matchedEngine.aliases.includes("PR")); Assert.equal(matchedEngine.iconURI, null); // Upper case matchedEngine = await UrlbarSearchUtils.engineForAlias("PR"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "patch"); Assert.ok(matchedEngine.aliases.includes("PR")); Assert.equal(matchedEngine.iconURI, null); // Cap case matchedEngine = await UrlbarSearchUtils.engineForAlias("Pr"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "patch"); Assert.ok(matchedEngine.aliases.includes("PR")); Assert.equal(matchedEngine.iconURI, null); }); add_task(async function remove_search_engine_nomatch() { let promiseTopic = promiseSearchTopic("engine-removed"); await Promise.all([baconEngineExtension.unload(), promiseTopic]); Assert.equal( 0, (await UrlbarSearchUtils.enginesForDomainPrefix("bacon")).length ); }); add_task(async function test_builtin_aliased_search_engine_match() { let engine = await UrlbarSearchUtils.engineForAlias("@google"); Assert.ok(engine); Assert.equal(engine.name, "Google"); let promiseTopic = promiseSearchTopic("engine-changed"); await Promise.all([Services.search.removeEngine(engine), promiseTopic]); let matchedEngine = await UrlbarSearchUtils.engineForAlias("@google"); Assert.ok(!matchedEngine); engine.hidden = false; await TestUtils.waitForCondition(() => UrlbarSearchUtils.engineForAlias("@google") ); engine = await UrlbarSearchUtils.engineForAlias("@google"); Assert.ok(engine); }); add_task(async function test_serps_are_equivalent() { info("Subset URL has extraneous parameters."); let url1 = "https://example.com/search?q=test&type=images"; let url2 = "https://example.com/search?q=test"; Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); info("Superset URL has extraneous parameters."); Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url2, url1)); info("Same keys, different values."); url1 = "https://example.com/search?q=test&type=images"; url2 = "https://example.com/search?q=test123&type=maps"; Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url2, url1)); info("Subset matching isn't strict (URL is subset of itself)."); Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url1, url1)); info("Origin and pathname are ignored."); url1 = "https://example.com/search?q=test"; url2 = "https://example-1.com/maps?q=test"; Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url2, url1)); info("Params can be optionally ignored"); url1 = "https://example.com/search?q=test&abc=123&foo=bar"; url2 = "https://example.com/search?q=test"; Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url1, url2, ["abc", "foo"])); }); add_task(async function test_get_root_domain_from_engine() { let extension = await SearchTestUtils.installSearchExtension( { name: "TestEngine2", search_url: "https://example.com/", }, { skipUnload: true } ); let engine = Services.search.getEngineByName("TestEngine2"); Assert.equal(UrlbarSearchUtils.getRootDomainFromEngine(engine), "example"); await extension.unload(); extension = await SearchTestUtils.installSearchExtension( { name: "TestEngine", search_url: "https://www.subdomain.othersubdomain.example.com", }, { skipUnload: true } ); engine = Services.search.getEngineByName("TestEngine"); Assert.equal(UrlbarSearchUtils.getRootDomainFromEngine(engine), "example"); await extension.unload(); // We let engines with URL ending in .test through even though its not a valid // TLD. extension = await SearchTestUtils.installSearchExtension( { name: "TestMalformed", search_url: "https://mochi.test/", search_url_get_params: "search={searchTerms}", }, { skipUnload: true } ); engine = Services.search.getEngineByName("TestMalformed"); Assert.equal(UrlbarSearchUtils.getRootDomainFromEngine(engine), "mochi"); await extension.unload(); // We return the domain for engines with a malformed URL. extension = await SearchTestUtils.installSearchExtension( { name: "TestMalformed", search_url: "https://subdomain.foobar/", search_url_get_params: "search={searchTerms}", }, { skipUnload: true } ); engine = Services.search.getEngineByName("TestMalformed"); Assert.equal( UrlbarSearchUtils.getRootDomainFromEngine(engine), "subdomain.foobar" ); await extension.unload(); }); // Tests getSearchTermIfDefaultSerpUri() by using a variety of // input strings and nsIURI's. // Should not throw an error if the consumer passes an input // that when accessed, could cause an error. add_task(async function get_search_term_if_default_serp_uri() { let testCases = [ { url: null, skipUriTest: true, }, { url: "", skipUriTest: true, }, { url: "about:blank", }, { url: "about:home", }, { url: "about:newtab", }, { url: "not://a/supported/protocol", }, { url: "view-source:http://www.example.com/", }, { // Not a default engine. url: "http://mochi.test:8888/?q=chocolate&pc=sample_code", }, { // Not the correct protocol. url: "http://example.com/?q=chocolate&pc=sample_code", }, { // Not the same query param values. url: "https://example.com/?q=chocolate&pc=sample_code2", }, { // Not the same query param values. url: "https://example.com/?q=chocolate&pc=sample_code&pc2=sample_code_2", }, { url: "https://example.com/?q=chocolate&pc=sample_code", expectedString: "chocolate", }, { url: "https://example.com/?q=chocolate+cakes&pc=sample_code", expectedString: "chocolate cakes", }, ]; // Create a specific engine so that the tests are matched // exactly against the query params used. let extension = await SearchTestUtils.installSearchExtension( { name: "TestEngine", search_url: "https://example.com/", search_url_get_params: "?q={searchTerms}&pc=sample_code", }, { skipUnload: true } ); let engine = Services.search.getEngineByName("TestEngine"); let originalDefaultEngine = Services.search.defaultEngine; Services.search.defaultEngine = engine; for (let testCase of testCases) { let expectedString = testCase.expectedString ?? ""; Assert.equal( UrlbarSearchUtils.getSearchTermIfDefaultSerpUri(testCase.url), expectedString, `Should return ${ expectedString == "" ? "an empty string" : "a matching search string" }` ); // Convert the string into a nsIURI and then // try the test case with it. if (!testCase.skipUriTest) { Assert.equal( UrlbarSearchUtils.getSearchTermIfDefaultSerpUri( Services.io.newURI(testCase.url) ), expectedString, `Should return ${ expectedString == "" ? "an empty string" : "a matching search string" }` ); } } Services.search.defaultEngine = originalDefaultEngine; await extension.unload(); }); add_task(async function matchAllDomainLevels() { let baseHostname = "matchalldomainlevels"; Assert.equal( (await UrlbarSearchUtils.enginesForDomainPrefix(baseHostname)).length, 0, `Sanity check: No engines initially match ${baseHostname}` ); // Install engines with the following domains. When we match engines below, // perfectly matching domains should come before partially matching domains. let baseDomain = `${baseHostname}.com`; let perfectDomains = [baseDomain, `www.${baseDomain}`]; let partialDomains = [`foo.${baseDomain}`, `foo.bar.${baseDomain}`]; // Install engines with partially matching domains first so that the test // isn't incidentally passing because engines are installed in the order it // ultimately expects them in. Wait for each engine to finish installing // before starting the next one to avoid intermittent out-of-order failures. let extensions = []; for (let list of [partialDomains, perfectDomains]) { for (let domain of list) { let ext = await SearchTestUtils.installSearchExtension( { name: domain, search_url: `https://${domain}/`, }, { skipUnload: true } ); extensions.push(ext); } } // Perfect matches come before partial matches. let expectedDomains = [...perfectDomains, ...partialDomains]; // Do searches for the following strings. Each should match all the engines // installed above. let searchStrings = [baseHostname, baseHostname + "."]; for (let searchString of searchStrings) { info(`Searching for "${searchString}"`); let engines = await UrlbarSearchUtils.enginesForDomainPrefix(searchString, { matchAllDomainLevels: true, }); let engineData = engines.map(e => ({ name: e.name, searchForm: e.searchForm, })); info("Matching engines: " + JSON.stringify(engineData)); Assert.equal( engines.length, expectedDomains.length, "Expected number of matching engines" ); Assert.deepEqual( engineData.map(d => d.name), expectedDomains, "Expected matching engine names/domains in the expected order" ); } await Promise.all(extensions.map(e => e.unload())); }); function promiseSearchTopic(expectedVerb) { return new Promise(resolve => { Services.obs.addObserver(function observe(subject, topic, verb) { info("browser-search-engine-modified: " + verb); if (verb == expectedVerb) { Services.obs.removeObserver(observe, "browser-search-engine-modified"); resolve(); } }, "browser-search-engine-modified"); }); }