test_UrlbarSearchUtils.js (13501B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const { UrlbarSearchUtils } = ChromeUtils.importESModule( 6 "moz-src:///browser/components/urlbar/UrlbarSearchUtils.sys.mjs" 7 ); 8 const { updateAppInfo } = ChromeUtils.importESModule( 9 "resource://testing-common/AppInfo.sys.mjs" 10 ); 11 12 const SEARCH_CONFIG = [ 13 { 14 recordType: "engine", 15 identifier: "testdefault", 16 base: { 17 name: "Default Engine", 18 urls: { 19 search: { 20 base: "https://www.example.com/search", 21 searchTermParamName: "q", 22 }, 23 }, 24 }, 25 }, 26 { 27 // For testing add/remove app provided engine. 28 recordType: "engine", 29 identifier: "aliased", 30 base: { 31 name: "Aliased Engine", 32 urls: { 33 search: { 34 base: "https://aliased.example.com/search", 35 searchTermParamName: "q", 36 }, 37 }, 38 aliases: ["aliased"], 39 }, 40 }, 41 { 42 recordType: "defaultEngines", 43 globalDefault: "testdefault", 44 specificDefaults: [], 45 }, 46 ]; 47 48 let baconEngineExtension; 49 50 add_setup(async function () { 51 updateAppInfo({ 52 name: "firefox", 53 }); 54 55 // Tell the search service we are running in the US. This also has the 56 // desired side-effect of preventing our geoip lookup. 57 Services.prefs.setCharPref("browser.search.region", "US"); 58 59 SearchTestUtils.setRemoteSettingsConfig(SEARCH_CONFIG); 60 await Services.search.init(); 61 62 await UrlbarSearchUtils.init(); 63 }); 64 65 add_task(async function search_engine_match() { 66 let engine = await Services.search.getDefault(); 67 Assert.equal(engine.name, "Default Engine", "Default engine is correct."); 68 let domain = engine.searchUrlDomain; 69 let token = domain.substr(0, 1); 70 let matchedEngine = ( 71 await UrlbarSearchUtils.enginesForDomainPrefix(token) 72 )[0]; 73 Assert.equal(matchedEngine, engine); 74 }); 75 76 add_task(async function no_match() { 77 Assert.equal( 78 0, 79 (await UrlbarSearchUtils.enginesForDomainPrefix("nomatchprefix")).length 80 ); 81 }); 82 83 add_task(async function hide_search_engine_nomatch() { 84 let engine = await Services.search.getDefault(); 85 let domain = engine.searchUrlDomain; 86 let token = domain.substr(0, 1); 87 let promiseTopic = promiseSearchTopic("engine-changed"); 88 await Promise.all([Services.search.removeEngine(engine), promiseTopic]); 89 Assert.ok(engine.hidden); 90 let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token); 91 Assert.ok( 92 !matchedEngines.length || matchedEngines[0].searchUrlDomain != domain 93 ); 94 engine.hidden = false; 95 await TestUtils.waitForCondition( 96 async () => (await UrlbarSearchUtils.enginesForDomainPrefix(token)).length 97 ); 98 let matchedEngine2 = ( 99 await UrlbarSearchUtils.enginesForDomainPrefix(token) 100 )[0]; 101 Assert.ok(matchedEngine2); 102 await Services.search.setDefault( 103 engine, 104 Ci.nsISearchService.CHANGE_REASON_UNKNOWN 105 ); 106 }); 107 108 add_task(async function onlyEnabled_option_nomatch() { 109 let defaultEngine = await Services.search.getDefault(); 110 let domain = defaultEngine.searchUrlDomain; 111 let token = domain.substr(0, 1); 112 defaultEngine.hideOneOffButton = true; 113 114 let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token); 115 Assert.notEqual(matchedEngines[0], defaultEngine); 116 117 defaultEngine.hideOneOffButton = false; 118 matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token); 119 Assert.equal(matchedEngines[0].searchUrlDomain, domain); 120 Assert.equal(matchedEngines[0], defaultEngine); 121 }); 122 123 add_task(async function add_search_engine_match() { 124 Assert.equal( 125 0, 126 (await UrlbarSearchUtils.enginesForDomainPrefix("bacon")).length 127 ); 128 baconEngineExtension = await SearchTestUtils.installSearchExtension( 129 { 130 name: "bacon", 131 keyword: "pork", 132 search_url: "https://www.bacon.moz/", 133 }, 134 { skipUnload: true } 135 ); 136 let matchedEngine = ( 137 await UrlbarSearchUtils.enginesForDomainPrefix("bacon") 138 )[0]; 139 Assert.ok(matchedEngine); 140 Assert.equal(matchedEngine.name, "bacon"); 141 Assert.equal(await matchedEngine.getIconURL(), null); 142 info("also type part of the public suffix"); 143 matchedEngine = ( 144 await UrlbarSearchUtils.enginesForDomainPrefix("bacon.m") 145 )[0]; 146 Assert.ok(matchedEngine); 147 Assert.equal(matchedEngine.name, "bacon"); 148 Assert.equal(await matchedEngine.getIconURL(), null); 149 }); 150 151 add_task(async function match_multiple_search_engines() { 152 Assert.equal( 153 0, 154 (await UrlbarSearchUtils.enginesForDomainPrefix("baseball")).length 155 ); 156 await SearchTestUtils.installSearchExtension({ 157 name: "baseball", 158 search_url: "https://www.baseball.moz/", 159 }); 160 let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix("ba"); 161 Assert.equal( 162 matchedEngines.length, 163 2, 164 "enginesForDomainPrefix returned two engines." 165 ); 166 Assert.equal(matchedEngines[0].name, "bacon"); 167 Assert.equal(matchedEngines[1].name, "baseball"); 168 }); 169 170 add_task(async function test_aliased_search_engine_match() { 171 Assert.equal(null, await UrlbarSearchUtils.engineForAlias("sober")); 172 // Lower case 173 let matchedEngine = await UrlbarSearchUtils.engineForAlias("pork"); 174 Assert.ok(matchedEngine); 175 Assert.equal(matchedEngine.name, "bacon"); 176 Assert.ok(matchedEngine.aliases.includes("pork")); 177 Assert.equal(await matchedEngine.getIconURL(), null); 178 // Upper case 179 matchedEngine = await UrlbarSearchUtils.engineForAlias("PORK"); 180 Assert.ok(matchedEngine); 181 Assert.equal(matchedEngine.name, "bacon"); 182 Assert.ok(matchedEngine.aliases.includes("pork")); 183 Assert.equal(await matchedEngine.getIconURL(), null); 184 // Cap case 185 matchedEngine = await UrlbarSearchUtils.engineForAlias("Pork"); 186 Assert.ok(matchedEngine); 187 Assert.equal(matchedEngine.name, "bacon"); 188 Assert.ok(matchedEngine.aliases.includes("pork")); 189 Assert.equal(await matchedEngine.getIconURL(), null); 190 }); 191 192 add_task(async function test_aliased_search_engine_match_upper_case_alias() { 193 Assert.equal( 194 0, 195 (await UrlbarSearchUtils.enginesForDomainPrefix("patch")).length 196 ); 197 await SearchTestUtils.installSearchExtension({ 198 name: "patch", 199 keyword: "PR", 200 search_url: "https://www.patch.moz/", 201 }); 202 // lower case 203 let matchedEngine = await UrlbarSearchUtils.engineForAlias("pr"); 204 Assert.ok(matchedEngine); 205 Assert.equal(matchedEngine.name, "patch"); 206 Assert.ok(matchedEngine.aliases.includes("PR")); 207 Assert.equal(await matchedEngine.getIconURL(), null); 208 // Upper case 209 matchedEngine = await UrlbarSearchUtils.engineForAlias("PR"); 210 Assert.ok(matchedEngine); 211 Assert.equal(matchedEngine.name, "patch"); 212 Assert.ok(matchedEngine.aliases.includes("PR")); 213 Assert.equal(await matchedEngine.getIconURL(), null); 214 // Cap case 215 matchedEngine = await UrlbarSearchUtils.engineForAlias("Pr"); 216 Assert.ok(matchedEngine); 217 Assert.equal(matchedEngine.name, "patch"); 218 Assert.ok(matchedEngine.aliases.includes("PR")); 219 Assert.equal(await matchedEngine.getIconURL(), null); 220 }); 221 222 add_task(async function remove_search_engine_nomatch() { 223 let promiseTopic = promiseSearchTopic("engine-removed"); 224 await Promise.all([baconEngineExtension.unload(), promiseTopic]); 225 Assert.equal( 226 0, 227 (await UrlbarSearchUtils.enginesForDomainPrefix("bacon")).length 228 ); 229 }); 230 231 add_task(async function test_app_provided_aliased_search_engine_match() { 232 let engine = await UrlbarSearchUtils.engineForAlias("@aliased"); 233 Assert.ok(engine); 234 Assert.equal(engine.name, "Aliased Engine"); 235 let promiseTopic = promiseSearchTopic("engine-changed"); 236 await Promise.all([Services.search.removeEngine(engine), promiseTopic]); 237 let matchedEngine = await UrlbarSearchUtils.engineForAlias("@aliased"); 238 Assert.ok(!matchedEngine); 239 engine.hidden = false; 240 await TestUtils.waitForCondition(() => 241 UrlbarSearchUtils.engineForAlias("@aliased") 242 ); 243 engine = await UrlbarSearchUtils.engineForAlias("@aliased"); 244 Assert.ok(engine); 245 }); 246 247 add_task(async function test_serps_are_equivalent() { 248 info("Subset URL has extraneous parameters."); 249 let url1 = "https://example.com/search?q=test&type=images"; 250 let url2 = "https://example.com/search?q=test"; 251 Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); 252 info("Superset URL has extraneous parameters."); 253 Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url2, url1)); 254 255 info("Same keys, different values."); 256 url1 = "https://example.com/search?q=test&type=images"; 257 url2 = "https://example.com/search?q=test123&type=maps"; 258 Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); 259 Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url2, url1)); 260 261 info("Subset matching isn't strict (URL is subset of itself)."); 262 Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url1, url1)); 263 264 info("Origin and pathname are ignored."); 265 url1 = "https://example.com/search?q=test"; 266 url2 = "https://example-1.com/maps?q=test"; 267 Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); 268 Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url2, url1)); 269 270 info("Params can be optionally ignored"); 271 url1 = "https://example.com/search?q=test&abc=123&foo=bar"; 272 url2 = "https://example.com/search?q=test"; 273 Assert.ok(!UrlbarSearchUtils.serpsAreEquivalent(url1, url2)); 274 Assert.ok(UrlbarSearchUtils.serpsAreEquivalent(url1, url2, ["abc", "foo"])); 275 }); 276 277 add_task(async function test_get_root_domain_from_engine() { 278 let extension = await SearchTestUtils.installSearchExtension( 279 { 280 name: "TestEngine2", 281 search_url: "https://example.com/", 282 }, 283 { skipUnload: true } 284 ); 285 let engine = Services.search.getEngineByName("TestEngine2"); 286 Assert.equal(UrlbarSearchUtils.getRootDomainFromEngine(engine), "example"); 287 await extension.unload(); 288 289 extension = await SearchTestUtils.installSearchExtension( 290 { 291 name: "TestEngine", 292 search_url: "https://www.subdomain.othersubdomain.example.com", 293 }, 294 { skipUnload: true } 295 ); 296 engine = Services.search.getEngineByName("TestEngine"); 297 Assert.equal(UrlbarSearchUtils.getRootDomainFromEngine(engine), "example"); 298 await extension.unload(); 299 300 // We let engines with URL ending in .test through even though its not a valid 301 // TLD. 302 extension = await SearchTestUtils.installSearchExtension( 303 { 304 name: "TestMalformed", 305 search_url: "https://mochi.test/", 306 search_url_get_params: "search={searchTerms}", 307 }, 308 { skipUnload: true } 309 ); 310 engine = Services.search.getEngineByName("TestMalformed"); 311 Assert.equal(UrlbarSearchUtils.getRootDomainFromEngine(engine), "mochi"); 312 await extension.unload(); 313 314 // We return the domain for engines with a malformed URL. 315 extension = await SearchTestUtils.installSearchExtension( 316 { 317 name: "TestMalformed", 318 search_url: "https://subdomain.foobar/", 319 search_url_get_params: "search={searchTerms}", 320 }, 321 { skipUnload: true } 322 ); 323 engine = Services.search.getEngineByName("TestMalformed"); 324 Assert.equal( 325 UrlbarSearchUtils.getRootDomainFromEngine(engine), 326 "subdomain.foobar" 327 ); 328 await extension.unload(); 329 }); 330 331 add_task(async function matchAllDomainLevels() { 332 let baseHostname = "matchalldomainlevels"; 333 Assert.equal( 334 (await UrlbarSearchUtils.enginesForDomainPrefix(baseHostname)).length, 335 0, 336 `Sanity check: No engines initially match ${baseHostname}` 337 ); 338 339 // Install engines with the following domains. When we match engines below, 340 // perfectly matching domains should come before partially matching domains. 341 let baseDomain = `${baseHostname}.com`; 342 let perfectDomains = [baseDomain, `www.${baseDomain}`]; 343 let partialDomains = [`foo.${baseDomain}`, `foo.bar.${baseDomain}`]; 344 345 // Install engines with partially matching domains first so that the test 346 // isn't incidentally passing because engines are installed in the order it 347 // ultimately expects them in. Wait for each engine to finish installing 348 // before starting the next one to avoid intermittent out-of-order failures. 349 let extensions = []; 350 for (let list of [partialDomains, perfectDomains]) { 351 for (let domain of list) { 352 let ext = await SearchTestUtils.installSearchExtension( 353 { 354 name: domain, 355 search_url: `https://${domain}/`, 356 }, 357 { skipUnload: true } 358 ); 359 extensions.push(ext); 360 } 361 } 362 363 // Perfect matches come before partial matches. 364 let expectedDomains = [...perfectDomains, ...partialDomains]; 365 366 // Do searches for the following strings. Each should match all the engines 367 // installed above. 368 let searchStrings = [baseHostname, baseHostname + "."]; 369 for (let searchString of searchStrings) { 370 info(`Searching for "${searchString}"`); 371 let engines = await UrlbarSearchUtils.enginesForDomainPrefix(searchString, { 372 matchAllDomainLevels: true, 373 }); 374 // Domain names are saved in engine names. 375 let actualDomains = engines.map(e => e.name); 376 Assert.deepEqual( 377 actualDomains, 378 expectedDomains, 379 "Expected matching engine names/domains in the expected order" 380 ); 381 } 382 383 await Promise.all(extensions.map(e => e.unload())); 384 }); 385 386 function promiseSearchTopic(expectedVerb) { 387 return new Promise(resolve => { 388 Services.obs.addObserver(function observe(subject, topic, verb) { 389 info("browser-search-engine-modified: " + verb); 390 if (verb == expectedVerb) { 391 Services.obs.removeObserver(observe, "browser-search-engine-modified"); 392 resolve(); 393 } 394 }, "browser-search-engine-modified"); 395 }); 396 }