/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /* * Test initializing from the search settings. */ "use strict"; const legacyUseSavedOrderPrefName = SearchUtils.BROWSER_SEARCH_PREF + "useDBForOrder"; var settingsTemplate; /** * Test reading from search.json.mozlz4 */ add_task(async function setup() { Services.prefs .getDefaultBranch(SearchUtils.BROWSER_SEARCH_PREF + "param.") .setCharPref("test", "expected"); await SearchTestUtils.useTestEngines("data1"); await AddonTestUtils.promiseStartupManager(); await Services.search.init(); }); async function loadSettingsFile(settingsFile, setVersion, setHashes) { settingsTemplate = await readJSONFile(do_get_file(settingsFile)); if (setVersion) { settingsTemplate.version = SearchUtils.SETTINGS_VERSION; } if (setHashes) { settingsTemplate.metaData.hash = SearchUtils.getVerificationHash( settingsTemplate.metaData.current ); settingsTemplate.metaData.privateHash = SearchUtils.getVerificationHash( settingsTemplate.metaData.private ); } delete settingsTemplate.visibleDefaultEngines; await promiseSaveSettingsData(settingsTemplate); } /** * Start the search service and confirm the engine properties match the expected values. * * @param {string} settingsFile * The path to the settings file to use. * @param {boolean} setVersion * True if to set the version in the copied settings file. * @param {boolean} expectedUseDBValue * The value expected for the `useSavedOrder` metadata attribute. */ async function checkLoadSettingProperties( settingsFile, setVersion, expectedUseDBValue ) { info("init search service"); let ss = Services.search.wrappedJSObject; await loadSettingsFile(settingsFile, setVersion); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let engines = await ss.getEngines(); Assert.equal( engines[0].name, "engine1", "Should have loaded the correct first engine" ); Assert.equal(engines[0].alias, "testAlias", "Should have set the alias"); Assert.equal(engines[0].hidden, false, "Should have not hidden the engine"); Assert.equal(engines[0].id, "engine1@search.mozilla.orgdefault"); Assert.equal( engines[1].name, "engine2", "Should have loaded the correct second engine" ); Assert.equal(engines[1].alias, "", "Should have not set the alias"); Assert.equal(engines[1].hidden, true, "Should have hidden the engine"); Assert.equal(engines[1].id, "engine2@search.mozilla.orgdefault"); // The extra engine is the second in the list. isSubObjectOf(EXPECTED_ENGINE.engine, engines[2]); Assert.ok(engines[2].id, "test-addon-id@mozilla.orgdefault"); let engineFromSS = ss.getEngineByName(EXPECTED_ENGINE.engine.name); Assert.ok(!!engineFromSS); isSubObjectOf(EXPECTED_ENGINE.engine, engineFromSS); Assert.equal( engineFromSS.getSubmission("foo").uri.spec, "http://www.google.com/search?q=foo", "Should have the correct URL with no mozparams" ); Assert.equal( ss._settings.getMetaDataAttribute("useSavedOrder"), expectedUseDBValue, "Should have set the useSavedOrder metadata correctly." ); let migratedSettingsFile = await promiseSettingsData(); Assert.equal( migratedSettingsFile.engines[0].id, "engine1@search.mozilla.orgdefault" ); removeSettingsFile(); } add_task(async function test_legacy_setting_engine_properties() { Services.prefs.setBoolPref(legacyUseSavedOrderPrefName, true); let legacySettings = await readJSONFile( do_get_file("data/search-legacy.json") ); // Assert the engine ids have not been migrated yet for (let engine of legacySettings.engines) { Assert.ok(!("id" in engine)); } Assert.ok(!("defaultEngineId" in legacySettings.metaData)); Assert.ok(!("privateDefaultEngineId" in legacySettings.metaData)); await checkLoadSettingProperties("data/search-legacy.json", false, true); Assert.ok( !Services.prefs.prefHasUserValue(legacyUseSavedOrderPrefName), "Should have cleared the legacy pref." ); }); add_task( async function test_legacy_setting_migration_with_undefined_metaData_current_and_private() { let ss = Services.search.wrappedJSObject; await loadSettingsFile("data/search-legacy.json", false); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let migratedSettingsFile = await promiseSettingsData(); Assert.equal( migratedSettingsFile.metaData.defaultEngineId, "", "When there is no metaData.current attribute in settings file, the migration should set the defaultEngineId to an empty string." ); Assert.equal( migratedSettingsFile.metaData.privateDefaultEngineId, "", "When there is no metaData.private attribute in settings file, the migration should set the privateDefaultEngineId to an empty string." ); removeSettingsFile(); } ); add_task( async function test_legacy_setting_migration_with_correct_metaData_current_and_private_hashes() { let ss = Services.search.wrappedJSObject; await loadSettingsFile( "data/search-legacy-correct-default-engine-hashes.json", false, true ); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let migratedSettingsFile = await promiseSettingsData(); Assert.equal( migratedSettingsFile.metaData.defaultEngineId, "engine2@search.mozilla.orgdefault", "When the metaData.current and associated hash are correct, the migration should set the defaultEngineId to the engine id." ); Assert.equal( migratedSettingsFile.metaData.privateDefaultEngineId, "engine2@search.mozilla.orgdefault", "When the metaData.private and associated hash are correct, the migration should set the privateDefaultEngineId to the private engine id." ); removeSettingsFile(); } ); add_task( async function test_legacy_setting_migration_with_incorrect_metaData_current_and_private_hashes_app_provided() { let ss = Services.search.wrappedJSObject; // Here we are testing correct migration for the case that a user has set // their default engine to an application provided engine (but not the app // default). // // In this case we should ignore invalid hashes for the default engines, // and allow the select default to remain. This covers the case where // a user has copied a profile from a different directory. // See SearchService._getEngineDefault for more details. await loadSettingsFile( "data/search-legacy-wrong-default-engine-hashes.json", false, false ); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let migratedSettingsFile = await promiseSettingsData(); Assert.equal( migratedSettingsFile.metaData.defaultEngineId, "engine2@search.mozilla.orgdefault", "Should ignore invalid metaData.hash when the default engine is application provided." ); Assert.equal( Services.search.defaultEngine.name, "engine2", "Should have the correct engine set as default" ); Assert.equal( migratedSettingsFile.metaData.privateDefaultEngineId, "engine2@search.mozilla.orgdefault", "Should ignore invalid metaData.privateHash when the default private engine is application provided." ); Assert.equal( Services.search.defaultPrivateEngine.name, "engine2", "Should have the correct engine set as default private" ); removeSettingsFile(); } ); add_task( async function test_legacy_setting_migration_with_incorrect_metaData_current_and_private_hashes_third_party() { let ss = Services.search.wrappedJSObject; // This test is checking that if the user has set a third-party engine as // default, and the verification hash is invalid, then we do not copy // the default engine setting. await loadSettingsFile( "data/search-legacy-wrong-third-party-engine-hashes.json", false, false ); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let migratedSettingsFile = await promiseSettingsData(); Assert.equal( migratedSettingsFile.metaData.defaultEngineId, "", "Should reset the default engine when metaData.hash is invalid and the engine is not application provided." ); Assert.equal( Services.search.defaultEngine.name, "engine1", "Should have reset the default engine" ); Assert.equal( migratedSettingsFile.metaData.privateDefaultEngineId, "", "Should reset the default engine when metaData.privateHash is invalid and the engine is not application provided." ); Assert.equal( Services.search.defaultPrivateEngine.name, "engine1", "Should have reset the default private engine" ); removeSettingsFile(); } ); add_task(async function test_current_setting_engine_properties() { await checkLoadSettingProperties("data/search.json", true, false); }); add_task(async function test_settings_metadata_properties() { let ss = Services.search.wrappedJSObject; await loadSettingsFile("data/search.json"); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let metaDataProperties = [ "locale", "region", "channel", "experiment", "distroID", ]; for (let name of metaDataProperties) { Assert.notEqual( ss._settings.getMetaDataAttribute(`${name}`), undefined, `Search settings should have ${name} property defined.` ); } removeSettingsFile(); }); add_task(async function test_settings_write_when_settings_changed() { let ss = Services.search.wrappedJSObject; await loadSettingsFile("data/search.json"); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; Assert.ok( ss._settings.isCurrentAndCachedSettingsEqual(), "Settings and cached settings should be the same after search service initializaiton." ); const settingsFileWritten2 = promiseAfterSettings(); ss._settings.setMetaDataAttribute("value", "test"); Assert.ok( !ss._settings.isCurrentAndCachedSettingsEqual(), "Settings should differ from cached settings after a new attribute is set." ); await settingsFileWritten2; info("Settings write complete"); Assert.ok( ss._settings.isCurrentAndCachedSettingsEqual(), "Settings and cached settings should be the same after new attribte on settings is written." ); removeSettingsFile(); }); add_task(async function test_set_and_get_engine_metadata_attribute() { let ss = Services.search.wrappedJSObject; await loadSettingsFile("data/search.json"); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let engines = await ss.getEngines(); const settingsFileWritten2 = promiseAfterSettings(); ss._settings.setEngineMetaDataAttribute(engines[0].name, "value", "test"); await settingsFileWritten2; Assert.equal( "test", ss._settings.getEngineMetaDataAttribute(engines[0].name, "value"), `${engines[0].name}'s metadata property "value" should be set as "test" after calling getEngineMetaDataAttribute.` ); let userSettings = await ss._settings.get(); let engine = userSettings.engines.find(e => e._name == engines[0].name); Assert.equal( "test", engine._metaData.value, `${engines[0].name}'s metadata property "value" should be set as "test" from settings file.` ); removeSettingsFile(); }); add_task( async function test_settings_write_prevented_when_settings_unchanged() { let ss = Services.search.wrappedJSObject; await loadSettingsFile("data/search.json"); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; Assert.ok( ss._settings.isCurrentAndCachedSettingsEqual(), "Settings and cached settings should be the same after search service initializaiton." ); // Update settings. const settingsFileWritten2 = promiseAfterSettings(); ss._settings.setMetaDataAttribute("value", "test"); Assert.ok( !ss._settings.isCurrentAndCachedSettingsEqual(), "Settings should differ from cached settings after a new attribute is set." ); await settingsFileWritten2; // Set the same attribute as before to ensure there was no change. // Settings write should be prevented. let promiseWritePrevented = SearchTestUtils.promiseSearchNotification( "write-prevented-when-settings-unchanged" ); ss._settings.setMetaDataAttribute("value", "test"); Assert.ok( ss._settings.isCurrentAndCachedSettingsEqual(), "Settings and cached settings should be the same." ); await promiseWritePrevented; removeSettingsFile(); } ); /** * Test that the JSON settings written in the profile is correct. */ add_task(async function test_settings_write() { let ss = Services.search.wrappedJSObject; info("test settings writing"); await loadSettingsFile("data/search.json"); const settingsFileWritten = promiseAfterSettings(); await ss.reset(); await Services.search.init(); await settingsFileWritten; let settingsData = await promiseSettingsData(); // Remove buildID and locale, as they are no longer used. delete settingsTemplate.buildID; delete settingsTemplate.locale; for (let engine of settingsTemplate.engines) { // Remove _shortName from the settings template, as it is no longer supported, // but older settings used to have it, so we keep it in the template as an // example. if ("_shortName" in engine) { delete engine._shortName; } if ("_urls" in engine) { // Only app-provided engines support purpose & mozparams, others do not, // so filter them out of the expected template. for (let urls of engine._urls) { urls.params = urls.params.filter(p => !p.purpose && !p.mozparam); // resultDomain is also no longer supported. if ("resultDomain" in urls) { delete urls.resultDomain; } } } // Remove queryCharset, if it is the same as the default, as we don't save // it in that case. if (engine?.queryCharset == SearchUtils.DEFAULT_QUERY_CHARSET) { delete engine.queryCharset; } } // Note: the file is copied with an old version number, which should have // been updated on write. settingsTemplate.version = SearchUtils.SETTINGS_VERSION; isSubObjectOf(settingsTemplate, settingsData, (prop, value) => { if (prop != "_iconURL" && prop != "{}") { return false; } // Skip items that are to do with icons for extensions, as we can't // control the uuid. return value.startsWith("moz-extension://"); }); }); async function settings_write_check(disableFn) { let ss = Services.search.wrappedJSObject; sinon.stub(ss._settings, "_write").returns(Promise.resolve()); // Simulate the search service being initialized. disableFn(true); ss._settings.setMetaDataAttribute("value", "test"); Assert.ok( ss._settings._write.notCalled, "Should not have attempted to _write" ); // Wait for two periods of the normal delay to ensure we still do not write. await new Promise(r => // eslint-disable-next-line mozilla/no-arbitrary-setTimeout setTimeout(r, SearchSettings.SETTNGS_INVALIDATION_DELAY * 2) ); Assert.ok( ss._settings._write.notCalled, "Should not have attempted to _write" ); disableFn(false); await TestUtils.waitForCondition( () => ss._settings._write.calledOnce, "Should attempt to write the settings." ); sinon.restore(); } add_task(async function test_settings_write_prevented_during_init() { await settings_write_check(disable => { let status = disable ? "success" : "failed"; Services.search.wrappedJSObject.forceInitializationStatusForTests(status); }); }); add_task(async function test_settings_write_prevented_during_reload() { await settings_write_check( disable => (Services.search.wrappedJSObject._reloadingEngines = disable) ); }); var EXPECTED_ENGINE = { engine: { name: "Test search engine", alias: "", description: "A test search engine (based on Google search)", searchForm: "http://www.google.com/", wrappedJSObject: { _extensionID: "test-addon-id@mozilla.org", _iconURL: "data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAA" + "AIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9F" + "sfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2F" + "Ptft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2Fgg" + "M%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F" + "%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJ" + "vvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%" + "2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%" + "2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%" + "2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%" + "2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYS" + "BHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWc" + "TxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4j" + "wA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsgg" + "A7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7" + "kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%" + "2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFE" + "MwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%" + "2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCT" + "IYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesA" + "AN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOc" + "AAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v" + "8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA", _urls: [ { type: "application/x-suggestions+json", method: "GET", template: "http://suggestqueries.google.com/complete/search?output=firefox&client=firefox" + "&hl={moz:locale}&q={searchTerms}", params: "", }, { type: "text/html", method: "GET", template: "http://www.google.com/search", params: [ { name: "q", value: "{searchTerms}", purpose: undefined, }, ], }, ], }, }, };