Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: toolkit/components/search/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
/*
* Tests for the default engine telemetry event that can be tested via xpcshell,
* related to changing or selecting a different configuration.
* Other tests are typically in browser mochitests.
*/
"use strict";
ChromeUtils.defineESModuleGetters(this, {
AppProvidedConfigEngine:
"moz-src:///toolkit/components/search/ConfigSearchEngine.sys.mjs",
});
const BASE_CONFIG = [
{
identifier: "originalDefault",
base: {
name: "Original Default",
},
},
},
];
const MAIN_CONFIG = [
{
identifier: "originalDefault",
base: {
name: "Original Default",
},
},
},
{
identifier: "newDefault",
base: {
name: "New Default",
},
},
},
{
identifier: "defaultInLocaleFRNotRegionDE",
base: {
name: "Default in Locale FR and not Region DE",
urls: {
search: {
params: [
{
name: "ie",
value: "iso-8859-1",
},
{
name: "oe",
value: "iso-8859-1",
},
],
searchTermParamName: "q",
},
},
},
},
{
identifier: "defaultInRegionDE",
base: {
name: "Default in Region DE",
urls: {
search: {
params: [
{
name: "code",
experimentConfig: "code",
},
{
name: "test",
experimentConfig: "test",
},
],
searchTermParamName: "q",
},
},
},
},
{
identifier: "defaultForExperiment",
base: {
name: "Default for Experiment",
},
},
},
{
globalDefault: "newDefault",
specificDefaults: [
{
default: "defaultInLocaleFRNotRegionDE",
environment: { excludedRegions: ["DE"], locales: ["fr"] },
},
{
default: "defaultInRegionDE",
environment: { regions: ["DE"] },
},
{
default: "defaultForExperiment",
environment: { experiment: "test1" },
},
],
},
];
const CONFIG_WITH_MODIFIED_CLASSIFICATION = [
{
identifier: "originalDefault",
base: {
name: "Original Default",
},
classification: "unknown",
},
},
];
const testSearchEngine = {
id: "originalDefault",
providerId: "originalDefault",
partnerCode: "",
overriddenByThirdParty: false,
name: "Original Default",
loadPath: "[app]originalDefault",
};
const testNewDefaultEngine = {
id: "newDefault",
providerId: "newDefault",
partnerCode: "",
overriddenByThirdParty: false,
name: "New Default",
loadPath: "[app]newDefault",
};
const testDefaultInLocaleFRNotRegionDEEngine = {
id: "defaultInLocaleFRNotRegionDE",
providerId: "defaultInLocaleFRNotRegionDE",
partnerCode: "",
overriddenByThirdParty: false,
name: "Default in Locale FR and not Region DE",
loadPath: "[app]defaultInLocaleFRNotRegionDE",
};
const testPrefEngine = {
id: "defaultInRegionDE",
providerId: "defaultInRegionDE",
partnerCode: "",
overriddenByThirdParty: false,
name: "Default in Region DE",
loadPath: "[app]defaultInRegionDE",
};
const testDefaultForExperiment = {
id: "defaultForExperiment",
providerId: "defaultForExperiment",
partnerCode: "",
overriddenByThirdParty: false,
name: "Default for Experiment",
loadPath: "[app]defaultForExperiment",
};
function clearTelemetry() {
Services.telemetry.clearEvents();
Services.fog.testResetFOG();
}
async function checkTelemetry(
source,
prevEngine,
newEngine,
checkPrivate = false,
additionalEventsExpected = false
) {
let snapshot;
if (checkPrivate) {
snapshot = await Glean.searchEnginePrivate.changed.testGetValue();
} else {
snapshot = await Glean.searchEngineDefault.changed.testGetValue();
}
// additionalEventsExpected should be true whenever we expect an additional
// engine update due to the new details being different.
if (additionalEventsExpected) {
delete snapshot[0].timestamp;
Assert.deepEqual(
snapshot[0],
{
category: checkPrivate
? "search.engine.private"
: "search.engine.default",
name: "changed",
extra: {
change_reason: "engine-update",
previous_engine_id: prevEngine?.id ?? "",
new_engine_id: prevEngine?.id ?? "",
new_display_name: prevEngine?.name ?? "",
new_load_path: prevEngine?.loadPath ?? "",
new_submission_url: prevEngine?.submissionURL ?? "",
},
},
"Should have received the correct event details"
);
snapshot.shift();
}
delete snapshot[0].timestamp;
Assert.deepEqual(
snapshot[0],
{
category: checkPrivate
? "search.engine.private"
: "search.engine.default",
name: "changed",
extra: {
change_reason: source,
previous_engine_id: prevEngine?.id ?? "",
new_engine_id: newEngine?.id ?? "",
new_display_name: newEngine?.name ?? "",
new_load_path: newEngine?.loadPath ?? "",
new_submission_url: newEngine?.submissionURL ?? "",
},
},
"Should have received the correct event details"
);
let probeData = {
engineId: newEngine?.id ?? "",
providerId: newEngine?.providerId ?? "",
partnerCode: newEngine?.partnerCode ?? "",
overriddenByThirdParty: newEngine?.overriddenByThirdParty ?? false,
displayName: newEngine?.name ?? "",
loadPath: newEngine?.loadPath ?? "",
submissionUrl: newEngine?.submissionURL ?? "blank:",
};
if (checkPrivate) {
await assertGleanDefaultEngine({ normal: {}, private: probeData });
} else {
await assertGleanDefaultEngine({ normal: probeData });
}
}
add_setup(async () => {
Region._setHomeRegion("US", false);
Services.locale.availableLocales = [
...Services.locale.availableLocales,
"en",
"fr",
];
Services.locale.requestedLocales = ["en"];
Services.fog.initializeFOG();
sinon.stub(SearchService, "_showRemovalOfSearchEngineNotificationBox");
SearchTestUtils.setRemoteSettingsConfig(BASE_CONFIG);
await SearchService.init();
registerCleanupFunction(async () => {
sinon.restore();
});
});
add_task(async function test_configuration_changes_default() {
clearTelemetry();
await SearchTestUtils.updateRemoteSettingsConfig(MAIN_CONFIG);
await checkTelemetry(
"config",
testSearchEngine,
testNewDefaultEngine,
false,
true
);
});
add_task(async function test_experiment_changes_default() {
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
Services.prefs.setStringPref("browser.search.experiment", "test1");
await reloadObserved;
await checkTelemetry(
"experiment",
testNewDefaultEngine,
testDefaultForExperiment,
false
);
// Clear the pref so that we are no longer in an experiment.
reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
Services.prefs.clearUserPref("browser.search.experiment");
await reloadObserved;
});
add_task(async function test_locale_changes_default() {
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
Services.locale.requestedLocales = ["fr"];
await reloadObserved;
await checkTelemetry(
"locale",
testNewDefaultEngine,
testDefaultInLocaleFRNotRegionDEEngine,
false
);
});
add_task(async function test_region_changes_default() {
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
Region._setHomeRegion("DE", true);
await reloadObserved;
await checkTelemetry(
"region",
testDefaultInLocaleFRNotRegionDEEngine,
testPrefEngine,
false
);
});
add_task(async function test_user_changes_separate_private_pref() {
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
true
);
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.enabled",
true
);
Services.prefs.setBoolPref("browser.search.separatePrivateDefault", true);
await SearchService.setDefaultPrivate(
SearchService.getEngineById("newDefault"),
SearchService.CHANGE_REASON.UNKNOWN
);
Assert.notEqual(
await SearchService.getDefault(),
await SearchService.getDefaultPrivate(),
"Should have different engines for the pre-condition"
);
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
false
);
clearTelemetry();
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.enabled",
false
);
await checkTelemetry("user_private_split", testPrefEngine, null, true);
});
add_task(async function test_ui_enabled_with_separate_default_notifies() {
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
true
);
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.enabled",
true
);
Services.prefs.setBoolPref("browser.search.separatePrivateDefault", true);
await SearchService.setDefaultPrivate(
SearchService.getEngineById("newDefault"),
SearchService.CHANGE_REASON.UNKNOWN
);
clearTelemetry();
// Disabling the UI pref notifies because the private engine (newDefault)
// differs from the normal default (testPrefEngine).
let defaultChanged = SearchTestUtils.promiseSearchNotification(
SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE,
SearchUtils.TOPIC_ENGINE_MODIFIED
);
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
false
);
await defaultChanged;
await checkTelemetry(
"user_private_pref_enabled",
testNewDefaultEngine,
null,
true
);
clearTelemetry();
// Re-enabling the UI pref: the saved private engine was cleared when the pref
// was turned off, so the private engine falls back to the app default (same
// as the normal default). No notification fires, but telemetry records the change.
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
true
);
await checkTelemetry("user_private_pref_enabled", null, testPrefEngine, true);
clearTelemetry();
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
false
);
});
add_task(async function test_default_engine_update_private() {
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
true
);
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.enabled",
true
);
Services.prefs.setBoolPref("browser.search.separatePrivateDefault", true);
let extension = await SearchTestUtils.installSearchExtension(
{
name: "engineprivate",
id: "engineprivate@tests.mozilla.org",
search_url_get_params: `q={searchTerms}&version=1.0`,
version: "1.0",
},
{ skipUnload: true }
);
let engine = SearchService.getEngineByName("engineprivate");
Assert.ok(!!engine, "Should have loaded the engine");
await SearchService.setDefaultPrivate(
engine,
SearchService.CHANGE_REASON.UNKNOWN
);
clearTelemetry();
let promiseChanged = TestUtils.topicObserved(
"browser-search-engine-modified",
(eng, verb) => verb == "engine-changed"
);
let manifest = SearchTestUtils.createEngineManifest({
name: "Bar",
id: "engineprivate@tests.mozilla.org",
search_url_get_params: `q={searchTerms}&version=2.0`,
version: "2.0",
});
await extension.upgrade({
useAddonManager: "permanent",
manifest,
});
await AddonTestUtils.waitForSearchProviderStartup(extension);
await promiseChanged;
const defaultEngineData = {
id: engine.telemetryId,
providerId: "other",
partnerCode: "",
overriddenByThirdParty: false,
name: "Bar",
loadPath: engine._loadPath,
};
await checkTelemetry(
"engine-update",
defaultEngineData,
defaultEngineData,
true
);
await extension.unload();
Services.prefs.setBoolPref(
"browser.search.separatePrivateDefault.featureGate",
false
);
});
add_task(async function test_default_engine_update() {
clearTelemetry();
let extension = await SearchTestUtils.installSearchExtension(
{
name: "engine",
id: "engine@tests.mozilla.org",
search_url_get_params: `q={searchTerms}&version=1.0`,
version: "1.0",
},
{ skipUnload: true }
);
let engine = SearchService.getEngineByName("engine");
Assert.ok(!!engine, "Should have loaded the engine");
await SearchService.setDefault(engine, SearchService.CHANGE_REASON.UNKNOWN);
clearTelemetry();
let promiseChanged = TestUtils.topicObserved(
"browser-search-engine-modified",
(eng, verb) => verb == "engine-changed"
);
let manifest = SearchTestUtils.createEngineManifest({
name: "Bar",
id: "engine@tests.mozilla.org",
search_url_get_params: `q={searchTerms}&version=2.0`,
version: "2.0",
});
await extension.upgrade({
useAddonManager: "permanent",
manifest,
});
await AddonTestUtils.waitForSearchProviderStartup(extension);
await promiseChanged;
const defaultEngineData = {
id: engine.telemetryId,
providerId: "other",
partnerCode: "",
overriddenByThirdParty: false,
name: "Bar",
loadPath: engine._loadPath,
};
await checkTelemetry("engine-update", defaultEngineData, defaultEngineData);
await extension.unload();
});
add_task(async function test_non_telemetry_event_change_config_engine() {
// Test changing a non-telemetry event parameter for a config engine, this should
// not trigger a telemetry event.
await SearchTestUtils.updateRemoteSettingsConfig(BASE_CONFIG);
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
await SearchTestUtils.updateRemoteSettingsConfig(
CONFIG_WITH_MODIFIED_CLASSIFICATION
);
await reloadObserved;
// Ensure any pending notifications have cleared.
await TestUtils.waitForTick();
let snapshot = await Glean.searchEngineDefault.changed.testGetValue();
Assert.ok(
!snapshot,
"Should not have received any events for a non-telemetry related change to a config engine"
);
});
add_task(async function test_non_telemetry_event_change_addon_engine() {
// Test changing a non-telemetry event parameter for an add-on engine, this should
// not trigger a telemetry event.
clearTelemetry();
let extension = await SearchTestUtils.installSearchExtension(
{
name: "engine",
id: "engine@tests.mozilla.org",
search_url_get_params: `q={searchTerms}`,
encoding: "UTF-8",
version: "1.0",
},
{ skipUnload: true }
);
let engine = SearchService.getEngineByName("engine");
Assert.ok(!!engine, "Should have loaded the engine");
await SearchService.setDefault(engine, SearchService.CHANGE_REASON.UNKNOWN);
clearTelemetry();
let promiseChanged = TestUtils.topicObserved(
"browser-search-engine-modified",
(eng, verb) => verb == "engine-changed"
);
let manifest = SearchTestUtils.createEngineManifest({
name: "engine",
id: "engine@tests.mozilla.org",
search_url_get_params: `q={searchTerms}`,
encoding: "UTF-16",
version: "2.0",
});
await extension.upgrade({
useAddonManager: "permanent",
manifest,
});
await AddonTestUtils.waitForSearchProviderStartup(extension);
await promiseChanged;
// Ensure any pending notifications have cleared.
await TestUtils.waitForTick();
let snapshot = await Glean.searchEngineDefault.changed.testGetValue();
Assert.ok(
!snapshot,
"Should not have received any events for a non-telemetry related change to an add-on engine"
);
await extension.unload();
});