summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js1472
1 files changed, 1472 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
new file mode 100644
index 0000000000..aa9fbf11f0
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -0,0 +1,1472 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { AddonManager, AddonManagerPrivate } = ChromeUtils.importESModule(
+ "resource://gre/modules/AddonManager.sys.mjs"
+);
+const { TelemetryEnvironment } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryEnvironment.sys.mjs"
+);
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+const { TelemetryEnvironmentTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryEnvironmentTesting.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ ExtensionTestUtils:
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs",
+});
+
+async function installXPIFromURL(url) {
+ let install = await AddonManager.getInstallForURL(url);
+ return install.install();
+}
+
+// The webserver hosting the addons.
+var gHttpServer = null;
+// The URL of the webserver root.
+var gHttpRoot = null;
+// The URL of the data directory, on the webserver.
+var gDataRoot = null;
+
+function MockAddonWrapper(aAddon) {
+ this.addon = aAddon;
+}
+MockAddonWrapper.prototype = {
+ get id() {
+ return this.addon.id;
+ },
+
+ get type() {
+ return this.addon.type;
+ },
+
+ get appDisabled() {
+ return false;
+ },
+
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get scope() {
+ return AddonManager.SCOPE_PROFILE;
+ },
+
+ get foreignInstall() {
+ return false;
+ },
+
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ get blocklistState() {
+ return 0; // Not blocked.
+ },
+
+ get pendingOperations() {
+ return AddonManager.PENDING_NONE;
+ },
+
+ get permissions() {
+ return AddonManager.PERM_CAN_UNINSTALL | AddonManager.PERM_CAN_DISABLE;
+ },
+
+ get isActive() {
+ return true;
+ },
+
+ get name() {
+ return this.addon.name;
+ },
+
+ get version() {
+ return this.addon.version;
+ },
+
+ get creator() {
+ return new AddonManagerPrivate.AddonAuthor(this.addon.author);
+ },
+
+ get userDisabled() {
+ return this.appDisabled;
+ },
+};
+
+function createMockAddonProvider(aName) {
+ let mockProvider = {
+ _addons: [],
+
+ get name() {
+ return aName;
+ },
+
+ addAddon(aAddon) {
+ this._addons.push(aAddon);
+ AddonManagerPrivate.callAddonListeners(
+ "onInstalled",
+ new MockAddonWrapper(aAddon)
+ );
+ },
+
+ async getAddonsByTypes(aTypes) {
+ return this._addons
+ .filter(a => !aTypes || aTypes.includes(a.type))
+ .map(a => new MockAddonWrapper(a));
+ },
+
+ shutdown() {
+ return Promise.resolve();
+ },
+ };
+
+ return mockProvider;
+}
+
+add_task(async function setup() {
+ TelemetryEnvironmentTesting.registerFakeSysInfo();
+ TelemetryEnvironmentTesting.spoofGfxAdapter();
+ do_get_profile();
+
+ // We need to ensure FOG is initialized, otherwise we will panic trying to get test values.
+ Services.fog.initializeFOG();
+
+ // The system add-on must be installed before AddonManager is started.
+ const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"]);
+ distroDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ do_get_file("system.xpi").copyTo(
+ distroDir,
+ "tel-system-xpi@tests.mozilla.org.xpi"
+ );
+ let system_addon = FileUtils.File(distroDir.path);
+ system_addon.append("tel-system-xpi@tests.mozilla.org.xpi");
+ system_addon.lastModifiedTime = SYSTEM_ADDON_INSTALL_DATE;
+ await loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION);
+
+ TelemetryEnvironmentTesting.init(gAppInfo);
+
+ // The test runs in a fresh profile so starting the AddonManager causes
+ // the addons database to be created (as does setting new theme).
+ // For test_addonsStartup below, we want to test a "warm" startup where
+ // there is already a database on disk. Simulate that here by just
+ // restarting the AddonManager.
+ await AddonTestUtils.promiseShutdownManager();
+ await AddonTestUtils.overrideBuiltIns({ system: [] });
+ AddonTestUtils.addonStartup.remove(true);
+ await AddonTestUtils.promiseStartupManager();
+
+ // Setup a webserver to serve Addons, etc.
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gDataRoot = gHttpRoot + "data/";
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ registerCleanupFunction(() => gHttpServer.stop(() => {}));
+
+ // Create the attribution data file, so that settings.attribution will exist.
+ // The attribution functionality only exists in Firefox.
+ if (AppConstants.MOZ_BUILD_APP == "browser") {
+ TelemetryEnvironmentTesting.spoofAttributionData();
+ registerCleanupFunction(async function () {
+ await TelemetryEnvironmentTesting.cleanupAttributionData;
+ });
+ }
+
+ await TelemetryEnvironmentTesting.spoofProfileReset();
+ await TelemetryEnvironment.delayedInit();
+ await SearchTestUtils.useTestEngines("data", "search-extensions");
+});
+
+add_task(async function test_checkEnvironment() {
+ // During startup we have partial addon records.
+ // First make sure we haven't yet read the addons DB, then test that
+ // we have some partial addons data.
+ Assert.equal(
+ AddonManagerPrivate.isDBLoaded(),
+ false,
+ "addons database is not loaded"
+ );
+
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkAddonsSection(data, false, true);
+
+ // Check that settings.intl is lazily loaded.
+ Assert.equal(
+ typeof data.settings.intl,
+ "object",
+ "intl is initially an object"
+ );
+ Assert.equal(
+ Object.keys(data.settings.intl).length,
+ 0,
+ "intl is initially empty"
+ );
+
+ // Now continue with startup.
+ let initPromise = TelemetryEnvironment.onInitialized();
+ finishAddonManagerStartup();
+
+ // Fake the delayed startup event for intl data to load.
+ fakeIntlReady();
+
+ let environmentData = await initPromise;
+ TelemetryEnvironmentTesting.checkEnvironmentData(environmentData, {
+ isInitial: true,
+ });
+
+ TelemetryEnvironmentTesting.spoofPartnerInfo();
+ Services.obs.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
+
+ environmentData = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(environmentData, {
+ assertProcessData: true,
+ });
+});
+
+add_task(async function test_prefWatchPolicies() {
+ const PREF_TEST_1 = "toolkit.telemetry.test.pref_new";
+ const PREF_TEST_2 = "toolkit.telemetry.test.pref1";
+ const PREF_TEST_3 = "toolkit.telemetry.test.pref2";
+ const PREF_TEST_4 = "toolkit.telemetry.test.pref_old";
+ const PREF_TEST_5 = "toolkit.telemetry.test.requiresRestart";
+
+ const expectedValue = "some-test-value";
+ const unexpectedValue = "unexpected-test-value";
+
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST_1, { what: TelemetryEnvironment.RECORD_PREF_VALUE }],
+ [PREF_TEST_2, { what: TelemetryEnvironment.RECORD_PREF_STATE }],
+ [PREF_TEST_3, { what: TelemetryEnvironment.RECORD_PREF_STATE }],
+ [PREF_TEST_4, { what: TelemetryEnvironment.RECORD_PREF_VALUE }],
+ [
+ PREF_TEST_5,
+ { what: TelemetryEnvironment.RECORD_PREF_VALUE, requiresRestart: true },
+ ],
+ ]);
+
+ Services.prefs.setStringPref(PREF_TEST_4, expectedValue);
+ Services.prefs.setStringPref(PREF_TEST_5, expectedValue);
+
+ // Set the Environment preferences to watch.
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+ let deferred = Promise.withResolvers();
+
+ // Check that the pref values are missing or present as expected
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_1],
+ undefined
+ );
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_4],
+ expectedValue
+ );
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_5],
+ expectedValue
+ );
+
+ TelemetryEnvironment.registerChangeListener(
+ "testWatchPrefs",
+ (reason, data) => deferred.resolve(data)
+ );
+ let oldEnvironmentData = TelemetryEnvironment.currentEnvironment;
+
+ // Trigger a change in the watched preferences.
+ Services.prefs.setStringPref(PREF_TEST_1, expectedValue);
+ Services.prefs.setBoolPref(PREF_TEST_2, false);
+ Services.prefs.setStringPref(PREF_TEST_5, unexpectedValue);
+ let eventEnvironmentData = await deferred.promise;
+
+ // Unregister the listener.
+ TelemetryEnvironment.unregisterChangeListener("testWatchPrefs");
+
+ // Check environment contains the correct data.
+ Assert.deepEqual(oldEnvironmentData, eventEnvironmentData);
+ let userPrefs = TelemetryEnvironment.currentEnvironment.settings.userPrefs;
+
+ Assert.equal(
+ userPrefs[PREF_TEST_1],
+ expectedValue,
+ "Environment contains the correct preference value."
+ );
+ Assert.equal(
+ userPrefs[PREF_TEST_2],
+ "<user-set>",
+ "Report that the pref was user set but the value is not shown."
+ );
+ Assert.ok(
+ !(PREF_TEST_3 in userPrefs),
+ "Do not report if preference not user set."
+ );
+ Assert.equal(
+ userPrefs[PREF_TEST_5],
+ expectedValue,
+ "The pref value in the environment data should still be the same"
+ );
+});
+
+add_task(async function test_prefWatch_prefReset() {
+ const PREF_TEST = "toolkit.telemetry.test.pref1";
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST, { what: TelemetryEnvironment.RECORD_PREF_STATE }],
+ ]);
+
+ // Set the preference to a non-default value.
+ Services.prefs.setBoolPref(PREF_TEST, false);
+
+ // Set the Environment preferences to watch.
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "testWatchPrefs_reset",
+ deferred.resolve
+ );
+
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST],
+ "<user-set>"
+ );
+
+ // Trigger a change in the watched preferences.
+ Services.prefs.clearUserPref(PREF_TEST);
+ await deferred.promise;
+
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST],
+ undefined
+ );
+
+ // Unregister the listener.
+ TelemetryEnvironment.unregisterChangeListener("testWatchPrefs_reset");
+});
+
+add_task(async function test_prefDefault() {
+ const PREF_TEST = "toolkit.telemetry.test.defaultpref1";
+ const expectedValue = "some-test-value";
+
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST, { what: TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE }],
+ ]);
+
+ // Set the preference to a default value.
+ Services.prefs.getDefaultBranch(null).setCharPref(PREF_TEST, expectedValue);
+
+ // Set the Environment preferences to watch.
+ // We're not watching, but this function does the setup we need.
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST],
+ expectedValue
+ );
+});
+
+add_task(async function test_prefDefaultState() {
+ const PREF_TEST = "toolkit.telemetry.test.defaultpref2";
+ const expectedValue = "some-test-value";
+
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST, { what: TelemetryEnvironment.RECORD_DEFAULTPREF_STATE }],
+ ]);
+
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+
+ Assert.equal(
+ PREF_TEST in TelemetryEnvironment.currentEnvironment.settings.userPrefs,
+ false
+ );
+
+ // Set the preference to a default value.
+ Services.prefs.getDefaultBranch(null).setCharPref(PREF_TEST, expectedValue);
+
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST],
+ "<set>"
+ );
+});
+
+add_task(async function test_prefInvalid() {
+ const PREF_TEST_1 = "toolkit.telemetry.test.invalid1";
+ const PREF_TEST_2 = "toolkit.telemetry.test.invalid2";
+
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST_1, { what: TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE }],
+ [PREF_TEST_2, { what: TelemetryEnvironment.RECORD_DEFAULTPREF_STATE }],
+ ]);
+
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_1],
+ undefined
+ );
+ Assert.strictEqual(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_2],
+ undefined
+ );
+});
+
+add_task(async function test_addonsWatch_InterestingChange() {
+ const ADDON_INSTALL_URL = gDataRoot + "restartless.xpi";
+ const ADDON_ID = "tel-restartless-webext@tests.mozilla.org";
+ // We only expect a single notification for each install, uninstall, enable, disable.
+ const EXPECTED_NOTIFICATIONS = 4;
+
+ let receivedNotifications = 0;
+
+ let registerCheckpointPromise = aExpected => {
+ return new Promise(resolve =>
+ TelemetryEnvironment.registerChangeListener(
+ "testWatchAddons_Changes" + aExpected,
+ (reason, data) => {
+ Assert.equal(reason, "addons-changed");
+ receivedNotifications++;
+ resolve();
+ }
+ )
+ );
+ };
+
+ let assertCheckpoint = aExpected => {
+ Assert.equal(receivedNotifications, aExpected);
+ TelemetryEnvironment.unregisterChangeListener(
+ "testWatchAddons_Changes" + aExpected
+ );
+ };
+
+ // Test for receiving one notification after each change.
+ let checkpointPromise = registerCheckpointPromise(1);
+ await installXPIFromURL(ADDON_INSTALL_URL);
+ await checkpointPromise;
+ assertCheckpoint(1);
+ Assert.ok(
+ ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons
+ );
+
+ checkpointPromise = registerCheckpointPromise(2);
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ await addon.disable();
+ await checkpointPromise;
+ assertCheckpoint(2);
+ Assert.ok(
+ !(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons)
+ );
+
+ checkpointPromise = registerCheckpointPromise(3);
+ let startupPromise = AddonTestUtils.promiseWebExtensionStartup(ADDON_ID);
+ await addon.enable();
+ await checkpointPromise;
+ assertCheckpoint(3);
+ Assert.ok(
+ ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons
+ );
+ await startupPromise;
+
+ checkpointPromise = registerCheckpointPromise(4);
+ (await AddonManager.getAddonByID(ADDON_ID)).uninstall();
+ await checkpointPromise;
+ assertCheckpoint(4);
+ Assert.ok(
+ !(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons)
+ );
+
+ Assert.equal(
+ receivedNotifications,
+ EXPECTED_NOTIFICATIONS,
+ "We must only receive the notifications we expect."
+ );
+});
+
+add_task(async function test_addonsWatch_NotInterestingChange() {
+ // Plugins from GMPProvider are listed separately in addons.activeGMPlugins.
+ // We simulate the "plugin" type in this test and verify that it is excluded.
+ const PLUGIN_ID = "tel-fake-gmp-plugin@tests.mozilla.org";
+ // "theme" type is already covered by addons.theme, so we aren't interested.
+ const THEME_ID = "tel-theme@tests.mozilla.org";
+ // "dictionary" type should be in addon.activeAddons.
+ const DICT_ID = "tel-dict@tests.mozilla.org";
+
+ let receivedNotification = false;
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener("testNotInteresting", () => {
+ Assert.ok(
+ !receivedNotification,
+ "Should not receive multiple notifications"
+ );
+ receivedNotification = true;
+ deferred.resolve();
+ });
+
+ // "plugin" type, to verify that non-XPIProvider types such as the "plugin"
+ // type from GMPProvider are not included in activeAddons.
+ let fakePluginProvider = createMockAddonProvider("Fake GMPProvider");
+ AddonManagerPrivate.registerProvider(fakePluginProvider);
+ fakePluginProvider.addAddon({
+ id: PLUGIN_ID,
+ name: "Fake plugin",
+ version: "1",
+ type: "plugin",
+ });
+
+ // "theme" type.
+ let themeXpi = AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ theme: {},
+ browser_specific_settings: { gecko: { id: THEME_ID } },
+ },
+ });
+ let themeAddon = (await AddonTestUtils.promiseInstallFile(themeXpi)).addon;
+
+ let dictXpi = AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ dictionaries: {},
+ browser_specific_settings: { gecko: { id: DICT_ID } },
+ },
+ });
+ let dictAddon = (await AddonTestUtils.promiseInstallFile(dictXpi)).addon;
+
+ await deferred.promise;
+ Assert.ok(
+ !(PLUGIN_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons),
+ "GMP plugins should not appear in active addons."
+ );
+ Assert.ok(
+ !(THEME_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons),
+ "Themes should not appear in active addons."
+ );
+ Assert.ok(
+ DICT_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons,
+ "Dictionaries should appear in active addons."
+ );
+
+ TelemetryEnvironment.unregisterChangeListener("testNotInteresting");
+
+ AddonManagerPrivate.unregisterProvider(fakePluginProvider);
+ await themeAddon.uninstall();
+ await dictAddon.uninstall();
+});
+
+add_task(async function test_addons() {
+ const ADDON_INSTALL_URL = gDataRoot + "restartless.xpi";
+ const ADDON_ID = "tel-restartless-webext@tests.mozilla.org";
+ const ADDON_INSTALL_DATE = truncateToDays(Date.now());
+ const EXPECTED_ADDON_DATA = {
+ blocklisted: false,
+ description: "A restartless addon which gets enabled without a reboot.",
+ name: "XPI Telemetry Restartless Test",
+ userDisabled: false,
+ appDisabled: false,
+ version: "1.0",
+ scope: 1,
+ type: "extension",
+ foreignInstall: false,
+ hasBinaryComponents: false,
+ installDay: ADDON_INSTALL_DATE,
+ updateDay: ADDON_INSTALL_DATE,
+ signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
+ isSystem: false,
+ isWebExtension: true,
+ multiprocessCompatible: true,
+ quarantineIgnoredByUser: false,
+ // quarantineIgnoredByApp expected to be true because
+ // the test addon is signed as privileged (see signedState).
+ quarantineIgnoredByApp: true,
+ };
+ const SYSTEM_ADDON_ID = "tel-system-xpi@tests.mozilla.org";
+ const EXPECTED_SYSTEM_ADDON_DATA = {
+ blocklisted: false,
+ description: "A system addon which is shipped with Firefox.",
+ name: "XPI Telemetry System Add-on Test",
+ userDisabled: false,
+ appDisabled: false,
+ version: "1.0",
+ scope: 1,
+ type: "extension",
+ foreignInstall: false,
+ hasBinaryComponents: false,
+ installDay: truncateToDays(SYSTEM_ADDON_INSTALL_DATE),
+ updateDay: truncateToDays(SYSTEM_ADDON_INSTALL_DATE),
+ signedState: undefined,
+ isSystem: true,
+ isWebExtension: true,
+ multiprocessCompatible: true,
+ quarantineIgnoredByUser: false,
+ // quarantineIgnoredByApp expected to be true because
+ // the test addon is a system addon (see isSystem).
+ quarantineIgnoredByApp: true,
+ };
+
+ const WEBEXTENSION_ADDON_ID = "tel-webextension-xpi@tests.mozilla.org";
+ const WEBEXTENSION_ADDON_INSTALL_DATE = truncateToDays(Date.now());
+ const EXPECTED_WEBEXTENSION_ADDON_DATA = {
+ blocklisted: false,
+ description: "A webextension addon.",
+ name: "XPI Telemetry WebExtension Add-on Test",
+ userDisabled: false,
+ appDisabled: false,
+ version: "1.0",
+ scope: 1,
+ type: "extension",
+ foreignInstall: false,
+ hasBinaryComponents: false,
+ installDay: WEBEXTENSION_ADDON_INSTALL_DATE,
+ updateDay: WEBEXTENSION_ADDON_INSTALL_DATE,
+ signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
+ isSystem: false,
+ isWebExtension: true,
+ multiprocessCompatible: true,
+ quarantineIgnoredByUser: false,
+ // quarantineIgnoredByApp expected to be true because
+ // the test addon is signed as privileged (see signedState).
+ quarantineIgnoredByApp: true,
+ };
+
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_WebExtension",
+ (reason, data) => {
+ Assert.equal(reason, "addons-changed");
+ deferred.resolve();
+ }
+ );
+
+ // Install an add-on so we have some data.
+ let addon = await installXPIFromURL(ADDON_INSTALL_URL);
+
+ // Install a webextension as well.
+ // Note: all addons are webextensions, so doing this again is redundant...
+ ExtensionTestUtils.init(this);
+
+ let webextension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "permanent",
+ manifest: {
+ name: "XPI Telemetry WebExtension Add-on Test",
+ description: "A webextension addon.",
+ version: "1.0",
+ browser_specific_settings: {
+ gecko: {
+ id: WEBEXTENSION_ADDON_ID,
+ },
+ },
+ },
+ });
+
+ await webextension.startup();
+ await deferred.promise;
+ TelemetryEnvironment.unregisterChangeListener("test_WebExtension");
+
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ // Check addon data.
+ Assert.ok(
+ ADDON_ID in data.addons.activeAddons,
+ "We must have one active addon."
+ );
+ let targetAddon = data.addons.activeAddons[ADDON_ID];
+ for (let f in EXPECTED_ADDON_DATA) {
+ Assert.equal(
+ targetAddon[f],
+ EXPECTED_ADDON_DATA[f],
+ f + " must have the correct value."
+ );
+ }
+
+ // Check system add-on data.
+ Assert.ok(
+ SYSTEM_ADDON_ID in data.addons.activeAddons,
+ "We must have one active system addon."
+ );
+ let targetSystemAddon = data.addons.activeAddons[SYSTEM_ADDON_ID];
+ for (let f in EXPECTED_SYSTEM_ADDON_DATA) {
+ Assert.equal(
+ targetSystemAddon[f],
+ EXPECTED_SYSTEM_ADDON_DATA[f],
+ f + " must have the correct value."
+ );
+ }
+
+ // Check webextension add-on data.
+ Assert.ok(
+ WEBEXTENSION_ADDON_ID in data.addons.activeAddons,
+ "We must have one active webextension addon."
+ );
+ let targetWebExtensionAddon = data.addons.activeAddons[WEBEXTENSION_ADDON_ID];
+ for (let f in EXPECTED_WEBEXTENSION_ADDON_DATA) {
+ Assert.equal(
+ targetWebExtensionAddon[f],
+ EXPECTED_WEBEXTENSION_ADDON_DATA[f],
+ f + " must have the correct value."
+ );
+ }
+
+ await webextension.unload();
+
+ // Uninstall the addon.
+ await addon.startupPromise;
+ await addon.uninstall();
+});
+
+add_task(async function test_signedAddon() {
+ AddonTestUtils.useRealCertChecks = true;
+
+ const ADDON_INSTALL_URL = gDataRoot + "signed-webext.xpi";
+ const ADDON_ID = "tel-signed-webext@tests.mozilla.org";
+ const ADDON_INSTALL_DATE = truncateToDays(Date.now());
+ const EXPECTED_ADDON_DATA = {
+ blocklisted: false,
+ description: "A signed webextension",
+ name: "XPI Telemetry Signed Test",
+ userDisabled: false,
+ appDisabled: false,
+ version: "1.0",
+ scope: 1,
+ type: "extension",
+ foreignInstall: false,
+ hasBinaryComponents: false,
+ installDay: ADDON_INSTALL_DATE,
+ updateDay: ADDON_INSTALL_DATE,
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ quarantineIgnoredByUser: false,
+ // quarantineIgnoredByApp expected to be false because
+ // the test addon is signed as a non-privileged (see signedState),
+ // and it doesn't include any recommendations.
+ quarantineIgnoredByApp: false,
+ };
+
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_signedAddon",
+ deferred.resolve
+ );
+
+ // Install the addon.
+ let addon = await installXPIFromURL(ADDON_INSTALL_URL);
+
+ await deferred.promise;
+ // Unregister the listener.
+ TelemetryEnvironment.unregisterChangeListener("test_signedAddon");
+
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ // Check addon data.
+ Assert.ok(
+ ADDON_ID in data.addons.activeAddons,
+ "Add-on should be in the environment."
+ );
+ let targetAddon = data.addons.activeAddons[ADDON_ID];
+ for (let f in EXPECTED_ADDON_DATA) {
+ Assert.equal(
+ targetAddon[f],
+ EXPECTED_ADDON_DATA[f],
+ f + " must have the correct value."
+ );
+ }
+
+ // Make sure quarantineIgnoredByUser property is updated also in the
+ // telemetry environment in response to the user changing it.
+ deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_quarantineIgnoreByUser_changed",
+ deferred.resolve
+ );
+
+ addon.quarantineIgnoredByUser = true;
+ await deferred.promise;
+ // Unregister the listener.
+ TelemetryEnvironment.unregisterChangeListener(
+ "test_quarantineIgnoreByUser_changed"
+ );
+
+ Assert.equal(
+ TelemetryEnvironment.currentEnvironment.addons.activeAddons[ADDON_ID]
+ .quarantineIgnoredByUser,
+ true,
+ "Expect quarantineIgnoredByUser to be set to true"
+ );
+
+ AddonTestUtils.useRealCertChecks = false;
+ await addon.startupPromise;
+ await addon.uninstall();
+});
+
+add_task(async function test_addonsFieldsLimit() {
+ const ADDON_INSTALL_URL = gDataRoot + "long-fields.xpi";
+ const ADDON_ID = "tel-longfields-webext@tests.mozilla.org";
+
+ // Install the addon and wait for the TelemetryEnvironment to pick it up.
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_longFieldsAddon",
+ deferred.resolve
+ );
+ let addon = await installXPIFromURL(ADDON_INSTALL_URL);
+ await deferred.promise;
+ TelemetryEnvironment.unregisterChangeListener("test_longFieldsAddon");
+
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ // Check that the addon is available and that the string fields are limited.
+ Assert.ok(
+ ADDON_ID in data.addons.activeAddons,
+ "Add-on should be in the environment."
+ );
+ let targetAddon = data.addons.activeAddons[ADDON_ID];
+
+ // TelemetryEnvironment limits the length of string fields for activeAddons to 100 chars,
+ // to mitigate misbehaving addons.
+ Assert.lessOrEqual(
+ targetAddon.version.length,
+ 100,
+ "The version string must have been limited"
+ );
+ Assert.lessOrEqual(
+ targetAddon.name.length,
+ 100,
+ "The name string must have been limited"
+ );
+ Assert.lessOrEqual(
+ targetAddon.description.length,
+ 100,
+ "The description string must have been limited"
+ );
+
+ await addon.startupPromise;
+ await addon.uninstall();
+});
+
+add_task(async function test_collectionWithbrokenAddonData() {
+ const BROKEN_ADDON_ID = "telemetry-test2.example.com@services.mozilla.org";
+ const BROKEN_MANIFEST = {
+ id: "telemetry-test2.example.com@services.mozilla.org",
+ name: "telemetry broken addon",
+ origin: "https://telemetry-test2.example.com",
+ version: 1, // This is intentionally not a string.
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ type: "extension",
+ };
+
+ const ADDON_INSTALL_URL = gDataRoot + "restartless.xpi";
+ const ADDON_ID = "tel-restartless-webext@tests.mozilla.org";
+ const ADDON_INSTALL_DATE = truncateToDays(Date.now());
+ const EXPECTED_ADDON_DATA = {
+ blocklisted: false,
+ description: "A restartless addon which gets enabled without a reboot.",
+ name: "XPI Telemetry Restartless Test",
+ userDisabled: false,
+ appDisabled: false,
+ version: "1.0",
+ scope: 1,
+ type: "extension",
+ foreignInstall: false,
+ hasBinaryComponents: false,
+ installDay: ADDON_INSTALL_DATE,
+ updateDay: ADDON_INSTALL_DATE,
+ signedState: AddonManager.SIGNEDSTATE_MISSING,
+ };
+
+ let receivedNotifications = 0;
+
+ let registerCheckpointPromise = aExpected => {
+ return new Promise(resolve =>
+ TelemetryEnvironment.registerChangeListener(
+ "testBrokenAddon_collection" + aExpected,
+ (reason, data) => {
+ Assert.equal(reason, "addons-changed");
+ receivedNotifications++;
+ resolve();
+ }
+ )
+ );
+ };
+
+ let assertCheckpoint = aExpected => {
+ Assert.equal(receivedNotifications, aExpected);
+ TelemetryEnvironment.unregisterChangeListener(
+ "testBrokenAddon_collection" + aExpected
+ );
+ };
+
+ // Register the broken provider and install the broken addon.
+ let checkpointPromise = registerCheckpointPromise(1);
+ let brokenAddonProvider = createMockAddonProvider(
+ "Broken Extensions Provider"
+ );
+ AddonManagerPrivate.registerProvider(brokenAddonProvider);
+ brokenAddonProvider.addAddon(BROKEN_MANIFEST);
+ await checkpointPromise;
+ assertCheckpoint(1);
+
+ // Now install an addon which returns the correct information.
+ checkpointPromise = registerCheckpointPromise(2);
+ let addon = await installXPIFromURL(ADDON_INSTALL_URL);
+ await checkpointPromise;
+ assertCheckpoint(2);
+
+ // Check that the new environment contains the info from the broken provider,
+ // despite the addon missing some details.
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data, {
+ expectBrokenAddons: true,
+ });
+
+ let activeAddons = data.addons.activeAddons;
+ Assert.ok(
+ BROKEN_ADDON_ID in activeAddons,
+ "The addon with the broken manifest must be reported."
+ );
+ Assert.equal(
+ activeAddons[BROKEN_ADDON_ID].version,
+ null,
+ "null should be reported for invalid data."
+ );
+ Assert.ok(ADDON_ID in activeAddons, "The valid addon must be reported.");
+ Assert.equal(
+ activeAddons[ADDON_ID].description,
+ EXPECTED_ADDON_DATA.description,
+ "The description for the valid addon should be correct."
+ );
+
+ // Unregister the broken provider so we don't mess with other tests.
+ AddonManagerPrivate.unregisterProvider(brokenAddonProvider);
+
+ // Uninstall the valid addon.
+ await addon.startupPromise;
+ await addon.uninstall();
+});
+
+add_task(
+ { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
+ async function test_delayed_defaultBrowser() {
+ // Skip this test on Thunderbird since it is not a browser, so it cannot
+ // be the default browser.
+
+ // Make sure we don't have anything already cached for this test.
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+
+ let environmentData = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(environmentData);
+ Assert.equal(
+ environmentData.settings.isDefaultBrowser,
+ null,
+ "isDefaultBrowser must be null before the session is restored."
+ );
+
+ Services.obs.notifyObservers(null, "sessionstore-windows-restored");
+
+ environmentData = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(environmentData);
+ Assert.ok(
+ "isDefaultBrowser" in environmentData.settings,
+ "isDefaultBrowser must be available after the session is restored."
+ );
+ Assert.equal(
+ typeof environmentData.settings.isDefaultBrowser,
+ "boolean",
+ "isDefaultBrowser must be of the right type."
+ );
+
+ // Make sure pref-flipping doesn't overwrite the browser default state.
+ const PREF_TEST = "toolkit.telemetry.test.pref1";
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST, { what: TelemetryEnvironment.RECORD_PREF_STATE }],
+ ]);
+ Services.prefs.clearUserPref(PREF_TEST);
+
+ // Watch the test preference.
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "testDefaultBrowser_pref",
+ deferred.resolve
+ );
+ // Trigger an environment change.
+ Services.prefs.setIntPref(PREF_TEST, 1);
+ await deferred.promise;
+ TelemetryEnvironment.unregisterChangeListener("testDefaultBrowser_pref");
+
+ // Check that the data is still available.
+ environmentData = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(environmentData);
+ Assert.ok(
+ "isDefaultBrowser" in environmentData.settings,
+ "isDefaultBrowser must still be available after a pref is flipped."
+ );
+ }
+);
+
+add_task(async function test_osstrings() {
+ // First test that numbers in sysinfo properties are converted to string fields
+ // in system.os.
+ TelemetryEnvironmentTesting.setSysInfoOverrides({
+ version: 1,
+ name: 2,
+ kernel_version: 3,
+ });
+
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ Assert.equal(data.system.os.version, "1");
+ Assert.equal(data.system.os.name, "2");
+ if (AppConstants.platform == "android") {
+ Assert.equal(data.system.os.kernelVersion, "3");
+ }
+
+ // Check that null values are also handled.
+ TelemetryEnvironmentTesting.setSysInfoOverrides({
+ version: null,
+ name: null,
+ kernel_version: null,
+ });
+
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ Assert.equal(data.system.os.version, null);
+ Assert.equal(data.system.os.name, null);
+ if (AppConstants.platform == "android") {
+ Assert.equal(data.system.os.kernelVersion, null);
+ }
+
+ // Clean up.
+ TelemetryEnvironmentTesting.setSysInfoOverrides({});
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+});
+
+add_task(async function test_experimentsAPI() {
+ const EXPERIMENT1 = "experiment-1";
+ const EXPERIMENT1_BRANCH = "nice-branch";
+ const EXPERIMENT2 = "experiment-2";
+ const EXPERIMENT2_BRANCH = "other-branch";
+
+ let checkExperiment = (environmentData, id, branch, type = null) => {
+ Assert.ok(
+ "experiments" in environmentData,
+ "The current environment must report the experiment annotations."
+ );
+ Assert.ok(
+ id in environmentData.experiments,
+ "The experiments section must contain the expected experiment id."
+ );
+ Assert.equal(
+ environmentData.experiments[id].branch,
+ branch,
+ "The experiment branch must be correct."
+ );
+ };
+
+ // Clean the environment and check that it's reporting the correct info.
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ // We don't expect the experiments section to be there if no annotation
+ // happened.
+ Assert.ok(
+ !("experiments" in data),
+ "No experiments section must be reported if nothing was annotated."
+ );
+
+ // Add a change listener and add an experiment annotation.
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_experimentsAPI",
+ (reason, env) => {
+ deferred.resolve(env);
+ }
+ );
+ TelemetryEnvironment.setExperimentActive(EXPERIMENT1, EXPERIMENT1_BRANCH);
+ let eventEnvironmentData = await deferred.promise;
+
+ // Check that the old environment does not contain the experiments.
+ TelemetryEnvironmentTesting.checkEnvironmentData(eventEnvironmentData);
+ Assert.ok(
+ !("experiments" in eventEnvironmentData),
+ "No experiments section must be reported in the old environment."
+ );
+
+ // Check that the current environment contains the right experiment.
+ data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+ checkExperiment(data, EXPERIMENT1, EXPERIMENT1_BRANCH);
+
+ TelemetryEnvironment.unregisterChangeListener("test_experimentsAPI");
+
+ // Add a second annotation and check that both experiments are there.
+ deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_experimentsAPI2",
+ (reason, env) => {
+ deferred.resolve(env);
+ }
+ );
+ TelemetryEnvironment.setExperimentActive(EXPERIMENT2, EXPERIMENT2_BRANCH);
+ eventEnvironmentData = await deferred.promise;
+
+ // Check that the current environment contains both the experiment.
+ data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+ checkExperiment(data, EXPERIMENT1, EXPERIMENT1_BRANCH);
+ checkExperiment(data, EXPERIMENT2, EXPERIMENT2_BRANCH);
+
+ // The previous environment should only contain the first experiment.
+ checkExperiment(eventEnvironmentData, EXPERIMENT1, EXPERIMENT1_BRANCH);
+ Assert.ok(
+ !(EXPERIMENT2 in eventEnvironmentData),
+ "The old environment must not contain the new experiment annotation."
+ );
+
+ TelemetryEnvironment.unregisterChangeListener("test_experimentsAPI2");
+
+ // Check that removing an unknown experiment annotation does not trigger
+ // a notification.
+ TelemetryEnvironment.registerChangeListener("test_experimentsAPI3", () => {
+ Assert.ok(
+ false,
+ "Removing an unknown experiment annotation must not trigger a change."
+ );
+ });
+ TelemetryEnvironment.setExperimentInactive("unknown-experiment-id");
+ // Also make sure that passing non-string parameters arguments doesn't throw nor
+ // trigger a notification.
+ TelemetryEnvironment.setExperimentActive({}, "some-branch");
+ TelemetryEnvironment.setExperimentActive("some-id", {});
+ TelemetryEnvironment.unregisterChangeListener("test_experimentsAPI3");
+
+ // Check that removing a known experiment leaves the other in place and triggers
+ // a change.
+ deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener(
+ "test_experimentsAPI4",
+ (reason, env) => {
+ deferred.resolve(env);
+ }
+ );
+ TelemetryEnvironment.setExperimentInactive(EXPERIMENT1);
+ eventEnvironmentData = await deferred.promise;
+
+ // Check that the current environment contains just the second experiment.
+ data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+ Assert.ok(
+ !(EXPERIMENT1 in data),
+ "The current environment must not contain the removed experiment annotation."
+ );
+ checkExperiment(data, EXPERIMENT2, EXPERIMENT2_BRANCH);
+
+ // The previous environment should contain both annotations.
+ checkExperiment(eventEnvironmentData, EXPERIMENT1, EXPERIMENT1_BRANCH);
+ checkExperiment(eventEnvironmentData, EXPERIMENT2, EXPERIMENT2_BRANCH);
+
+ // Set an experiment with a type and check that it correctly shows up.
+ TelemetryEnvironment.setExperimentActive(
+ "typed-experiment",
+ "random-branch",
+ { type: "ab-test" }
+ );
+ data = TelemetryEnvironment.currentEnvironment;
+ checkExperiment(data, "typed-experiment", "random-branch", "ab-test");
+});
+
+add_task(async function test_experimentsAPI_limits() {
+ const EXPERIMENT =
+ "experiment-2-experiment-2-experiment-2-experiment-2-experiment-2" +
+ "-experiment-2-experiment-2-experiment-2-experiment-2";
+ const EXPERIMENT_BRANCH =
+ "other-branch-other-branch-other-branch-other-branch-other" +
+ "-branch-other-branch-other-branch-other-branch-other-branch";
+ const EXPERIMENT_TRUNCATED = EXPERIMENT.substring(0, 100);
+ const EXPERIMENT_BRANCH_TRUNCATED = EXPERIMENT_BRANCH.substring(0, 100);
+
+ // Clean the environment and check that it's reporting the correct info.
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ // We don't expect the experiments section to be there if no annotation
+ // happened.
+ Assert.ok(
+ !("experiments" in data),
+ "No experiments section must be reported if nothing was annotated."
+ );
+
+ // Add a change listener and wait for the annotation to happen.
+ let deferred = Promise.withResolvers();
+ TelemetryEnvironment.registerChangeListener("test_experimentsAPI", () =>
+ deferred.resolve()
+ );
+ TelemetryEnvironment.setExperimentActive(EXPERIMENT, EXPERIMENT_BRANCH);
+ await deferred.promise;
+
+ // Check that the current environment contains the truncated values
+ // for the experiment data.
+ data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+ Assert.ok(
+ "experiments" in data,
+ "The environment must contain an experiments section."
+ );
+ Assert.ok(
+ EXPERIMENT_TRUNCATED in data.experiments,
+ "The experiments must be reporting the truncated id."
+ );
+ Assert.ok(
+ !(EXPERIMENT in data.experiments),
+ "The experiments must not be reporting the full id."
+ );
+ Assert.equal(
+ EXPERIMENT_BRANCH_TRUNCATED,
+ data.experiments[EXPERIMENT_TRUNCATED].branch,
+ "The experiments must be reporting the truncated branch."
+ );
+
+ TelemetryEnvironment.unregisterChangeListener("test_experimentsAPI");
+
+ // Check that an overly long type is truncated.
+ const longType = "a0123456678901234567890123456789";
+ TelemetryEnvironment.setExperimentActive("exp", "some-branch", {
+ type: longType,
+ });
+ data = TelemetryEnvironment.currentEnvironment;
+ Assert.equal(data.experiments.exp.type, longType.substring(0, 20));
+});
+
+if (gIsWindows) {
+ add_task(async function test_environmentHDDInfo() {
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ let empty = { model: null, revision: null, type: null };
+ Assert.deepEqual(
+ data.system.hdd,
+ { binary: empty, profile: empty, system: empty },
+ "Should have no data yet."
+ );
+ await TelemetryEnvironment.delayedInit();
+ data = TelemetryEnvironment.currentEnvironment;
+ for (let k of TelemetryEnvironmentTesting.EXPECTED_HDD_FIELDS) {
+ TelemetryEnvironmentTesting.checkString(data.system.hdd[k].model);
+ TelemetryEnvironmentTesting.checkString(data.system.hdd[k].revision);
+ TelemetryEnvironmentTesting.checkString(data.system.hdd[k].type);
+ }
+ });
+
+ add_task(async function test_environmentProcessInfo() {
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ Assert.deepEqual(data.system.isWow64, null, "Should have no data yet.");
+ await TelemetryEnvironment.delayedInit();
+ data = TelemetryEnvironment.currentEnvironment;
+ Assert.equal(
+ typeof data.system.isWow64,
+ "boolean",
+ "isWow64 must be a boolean."
+ );
+ Assert.equal(
+ typeof data.system.isWowARM64,
+ "boolean",
+ "isWowARM64 must be a boolean."
+ );
+ Assert.equal(
+ typeof data.system.hasWinPackageId,
+ "boolean",
+ "hasWinPackageId must be a boolean."
+ );
+ // This is only sent for Mozilla produced MSIX packages
+ Assert.ok(
+ !("winPackageFamilyName" in data.system) ||
+ data.system.winPackageFamilyName === null ||
+ typeof data.system.winPackageFamilyName === "string",
+ "winPackageFamilyName must be a string if non null"
+ );
+ // These should be numbers if they are not null
+ for (let f of [
+ "count",
+ "model",
+ "family",
+ "stepping",
+ "l2cacheKB",
+ "l3cacheKB",
+ "speedMHz",
+ "cores",
+ ]) {
+ Assert.ok(
+ !(f in data.system.cpu) ||
+ data.system.cpu[f] === null ||
+ Number.isFinite(data.system.cpu[f]),
+ f + " must be a number if non null."
+ );
+ }
+ Assert.ok(
+ TelemetryEnvironmentTesting.checkString(data.system.cpu.vendor),
+ "vendor must be a valid string."
+ );
+ });
+
+ add_task(async function test_environmentOSInfo() {
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ Assert.deepEqual(
+ data.system.os.installYear,
+ null,
+ "Should have no data yet."
+ );
+ await TelemetryEnvironment.delayedInit();
+ data = TelemetryEnvironment.currentEnvironment;
+ Assert.ok(
+ Number.isFinite(data.system.os.installYear),
+ "Install year must be a number."
+ );
+ });
+}
+
+add_task(
+ { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
+ async function test_environmentServicesInfo() {
+ let cache = TelemetryEnvironment.testCleanRestart();
+ await cache.onInitialized();
+ let oldGetFxaSignedInUser = cache._getFxaSignedInUser;
+ try {
+ // Test the 'yes to both' case.
+
+ // This makes the weave service return that the usere is definitely a sync user
+ Services.prefs.setStringPref(
+ "services.sync.username",
+ "c00lperson123@example.com"
+ );
+ let calledFxa = false;
+ cache._getFxaSignedInUser = () => {
+ calledFxa = true;
+ return null;
+ };
+
+ await cache._updateServicesInfo();
+ ok(
+ !calledFxa,
+ "Shouldn't need to ask FxA if they're definitely signed in"
+ );
+ deepEqual(cache.currentEnvironment.services, {
+ accountEnabled: true,
+ syncEnabled: true,
+ });
+
+ // Test the fxa-but-not-sync case.
+ Services.prefs.clearUserPref("services.sync.username");
+ // We don't actually inspect the returned object, just t
+ cache._getFxaSignedInUser = async () => {
+ return {};
+ };
+ await cache._updateServicesInfo();
+ deepEqual(cache.currentEnvironment.services, {
+ accountEnabled: true,
+ syncEnabled: false,
+ });
+ // Test the "no to both" case.
+ cache._getFxaSignedInUser = async () => {
+ return null;
+ };
+ await cache._updateServicesInfo();
+ deepEqual(cache.currentEnvironment.services, {
+ accountEnabled: false,
+ syncEnabled: false,
+ });
+ // And finally, the 'fxa is in an error state' case.
+ cache._getFxaSignedInUser = () => {
+ throw new Error("You'll never know");
+ };
+ await cache._updateServicesInfo();
+ equal(cache.currentEnvironment.services, null);
+ } finally {
+ cache._getFxaSignedInUser = oldGetFxaSignedInUser;
+ Services.prefs.clearUserPref("services.sync.username");
+ }
+ }
+);
+
+add_task(async function test_normandyTestPrefsGoneAfter91() {
+ const testPrefBool = "app.normandy.test-prefs.bool";
+ const testPrefInteger = "app.normandy.test-prefs.integer";
+ const testPrefString = "app.normandy.test-prefs.string";
+
+ Services.prefs.setBoolPref(testPrefBool, true);
+ Services.prefs.setIntPref(testPrefInteger, 10);
+ Services.prefs.setCharPref(testPrefString, "test-string");
+
+ const data = TelemetryEnvironment.currentEnvironment;
+
+ if (Services.vc.compare(data.build.version, "91") > 0) {
+ Assert.equal(
+ data.settings.userPrefs["app.normandy.test-prefs.bool"],
+ null,
+ "This probe should expire in FX91. bug 1686105 "
+ );
+ Assert.equal(
+ data.settings.userPrefs["app.normandy.test-prefs.integer"],
+ null,
+ "This probe should expire in FX91. bug 1686105 "
+ );
+ Assert.equal(
+ data.settings.userPrefs["app.normandy.test-prefs.string"],
+ null,
+ "This probe should expire in FX91. bug 1686105 "
+ );
+ }
+});
+
+add_task(async function test_environmentShutdown() {
+ // Define and reset the test preference.
+ const PREF_TEST = "toolkit.telemetry.test.pref1";
+ const PREFS_TO_WATCH = new Map([
+ [PREF_TEST, { what: TelemetryEnvironment.RECORD_PREF_STATE }],
+ ]);
+ Services.prefs.clearUserPref(PREF_TEST);
+
+ // Set up the preferences and listener, then the trigger shutdown
+ await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH);
+ TelemetryEnvironment.registerChangeListener(
+ "test_environmentShutdownChange",
+ () => {
+ // Register a new change listener that asserts if change is propogated
+ Assert.ok(false, "No change should be propagated after shutdown.");
+ }
+ );
+ TelemetryEnvironment.shutdown();
+
+ // Flipping the test preference after shutdown should not trigger the listener
+ Services.prefs.setIntPref(PREF_TEST, 1);
+
+ // Unregister the listener.
+ TelemetryEnvironment.unregisterChangeListener(
+ "test_environmentShutdownChange"
+ );
+});
+
+add_task(async function test_environmentDidntChange() {
+ // Clean the environment and check that it's reporting the correct info.
+ await TelemetryEnvironment.testCleanRestart().onInitialized();
+ let data = TelemetryEnvironment.currentEnvironment;
+ TelemetryEnvironmentTesting.checkEnvironmentData(data);
+
+ const LISTENER_NAME = "test_environmentDidntChange";
+ TelemetryEnvironment.registerChangeListener(LISTENER_NAME, () => {
+ Assert.ok(false, "The environment didn't actually change.");
+ });
+
+ // Don't actually change the environment, but notify of a compositor abort.
+ const COMPOSITOR_ABORTED_TOPIC = "compositor:process-aborted";
+ Services.obs.notifyObservers(null, COMPOSITOR_ABORTED_TOPIC);
+
+ TelemetryEnvironment.unregisterChangeListener(LISTENER_NAME);
+});