/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm", this); Cu.import("resource://gre/modules/PromiseUtils.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://testing-common/AddonManagerTesting.jsm"); Cu.import("resource://testing-common/httpd.js"); Cu.import("resource://testing-common/MockRegistrar.jsm", this); Cu.import("resource://gre/modules/FileUtils.jsm"); // AttributionCode is only needed for Firefox XPCOMUtils.defineLazyModuleGetter(this, "AttributionCode", "resource:///modules/AttributionCode.jsm"); // Lazy load |LightweightThemeManager|, we won't be using it on Gonk. XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge", "resource://gre/modules/ProfileAge.jsm"); // 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; const PLATFORM_VERSION = "1.9.2"; const APP_VERSION = "1"; const APP_ID = "xpcshell@tests.mozilla.org"; const APP_NAME = "XPCShell"; const DISTRIBUTION_ID = "distributor-id"; const DISTRIBUTION_VERSION = "4.5.6b"; const DISTRIBUTOR_NAME = "Some Distributor"; const DISTRIBUTOR_CHANNEL = "A Channel"; const PARTNER_NAME = "test"; const PARTNER_ID = "NicePartner-ID-3785"; const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete"; const GFX_VENDOR_ID = "0xabcd"; const GFX_DEVICE_ID = "0x1234"; // The profile reset date, in milliseconds (Today) const PROFILE_RESET_DATE_MS = Date.now(); // The profile creation date, in milliseconds (Yesterday). const PROFILE_CREATION_DATE_MS = PROFILE_RESET_DATE_MS - MILLISECONDS_PER_DAY; const FLASH_PLUGIN_NAME = "Shockwave Flash"; const FLASH_PLUGIN_DESC = "A mock flash plugin"; const FLASH_PLUGIN_VERSION = "\u201c1.1.1.1\u201d"; const PLUGIN_MIME_TYPE1 = "application/x-shockwave-flash"; const PLUGIN_MIME_TYPE2 = "text/plain"; const PLUGIN2_NAME = "Quicktime"; const PLUGIN2_DESC = "A mock Quicktime plugin"; const PLUGIN2_VERSION = "2.3"; const PERSONA_ID = "3785"; // Defined by LightweightThemeManager, it is appended to the PERSONA_ID. const PERSONA_ID_SUFFIX = "@personas.mozilla.org"; const PERSONA_NAME = "Test Theme"; const PERSONA_DESCRIPTION = "A nice theme/persona description."; const PLUGIN_UPDATED_TOPIC = "plugins-list-updated"; // system add-ons are enabled at startup, so record date when the test starts const SYSTEM_ADDON_INSTALL_DATE = Date.now(); // Valid attribution code to write so that settings.attribution can be tested. const ATTRIBUTION_CODE = "source%3Dgoogle.com"; /** * Used to mock plugin tags in our fake plugin host. */ function PluginTag(aName, aDescription, aVersion, aEnabled) { this.name = aName; this.description = aDescription; this.version = aVersion; this.disabled = !aEnabled; } PluginTag.prototype = { name: null, description: null, version: null, filename: null, fullpath: null, disabled: false, blocklisted: false, clicktoplay: true, mimeTypes: [ PLUGIN_MIME_TYPE1, PLUGIN_MIME_TYPE2 ], getMimeTypes: function(count) { count.value = this.mimeTypes.length; return this.mimeTypes; } }; // A container for the plugins handled by the fake plugin host. var gInstalledPlugins = [ new PluginTag("Java", "A mock Java plugin", "1.0", false /* Disabled */), new PluginTag(FLASH_PLUGIN_NAME, FLASH_PLUGIN_DESC, FLASH_PLUGIN_VERSION, true), ]; // A fake plugin host for testing plugin telemetry environment. var PluginHost = { getPluginTags: function(countRef) { countRef.value = gInstalledPlugins.length; return gInstalledPlugins; }, QueryInterface: function(iid) { if (iid.equals(Ci.nsIPluginHost) || iid.equals(Ci.nsISupports)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } } function registerFakePluginHost() { MockRegistrar.register("@mozilla.org/plugin/host;1", PluginHost); } var SysInfo = { overrides: {}, getProperty(name) { // Assert.ok(false, "Mock SysInfo: " + name + ", " + JSON.stringify(this.overrides)); if (name in this.overrides) { return this.overrides[name]; } try { return this._genuine.getProperty(name); } catch (ex) { throw ex; } }, get(name) { return this._genuine.get(name); }, QueryInterface(iid) { if (iid.equals(Ci.nsIPropertyBag2) || iid.equals(Ci.nsISupports)) return this; throw Cr.NS_ERROR_NO_INTERFACE; } }; function registerFakeSysInfo() { MockRegistrar.register("@mozilla.org/system-info;1", SysInfo); } function MockAddonWrapper(aAddon) { this.addon = aAddon; } MockAddonWrapper.prototype = { get id() { return this.addon.id; }, get type() { return "service"; }, 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: function(aAddon) { this._addons.push(aAddon); AddonManagerPrivate.callAddonListeners("onInstalled", new MockAddonWrapper(aAddon)); }, getAddonsByTypes: function (aTypes, aCallback) { aCallback(this._addons.map(a => new MockAddonWrapper(a))); }, shutdown() { return Promise.resolve(); }, }; return mockProvider; } /** * Used to spoof the Persona Id. */ function spoofTheme(aId, aName, aDesc) { return { id: aId, name: aName, description: aDesc, headerURL: "http://lwttest.invalid/a.png", footerURL: "http://lwttest.invalid/b.png", textcolor: Math.random().toString(), accentcolor: Math.random().toString() }; } function spoofGfxAdapter() { try { let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug); gfxInfo.spoofVendorID(GFX_VENDOR_ID); gfxInfo.spoofDeviceID(GFX_DEVICE_ID); } catch (x) { // If we can't test gfxInfo, that's fine, we'll note it later. } } function spoofProfileReset() { let profileAccessor = new ProfileAge(); return profileAccessor.writeTimes({ created: PROFILE_CREATION_DATE_MS, reset: PROFILE_RESET_DATE_MS }); } function spoofPartnerInfo() { let prefsToSpoof = {}; prefsToSpoof["distribution.id"] = DISTRIBUTION_ID; prefsToSpoof["distribution.version"] = DISTRIBUTION_VERSION; prefsToSpoof["app.distributor"] = DISTRIBUTOR_NAME; prefsToSpoof["app.distributor.channel"] = DISTRIBUTOR_CHANNEL; prefsToSpoof["app.partner.test"] = PARTNER_NAME; prefsToSpoof["mozilla.partner.id"] = PARTNER_ID; // Spoof the preferences. for (let pref in prefsToSpoof) { Preferences.set(pref, prefsToSpoof[pref]); } } function getAttributionFile() { let file = Services.dirsvc.get("LocalAppData", Ci.nsIFile); file.append("mozilla"); file.append(AppConstants.MOZ_APP_NAME); file.append("postSigningData"); return file; } function spoofAttributionData() { if (gIsWindows) { AttributionCode._clearCache(); let stream = Cc["@mozilla.org/network/file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); stream.init(getAttributionFile(), -1, -1, 0); stream.write(ATTRIBUTION_CODE, ATTRIBUTION_CODE.length); } } function cleanupAttributionData() { if (gIsWindows) { getAttributionFile().remove(false); AttributionCode._clearCache(); } } /** * Check that a value is a string and not empty. * * @param aValue The variable to check. * @return True if |aValue| has type "string" and is not empty, False otherwise. */ function checkString(aValue) { return (typeof aValue == "string") && (aValue != ""); } /** * If value is non-null, check if it's a valid string. * * @param aValue The variable to check. * @return True if it's null or a valid string, false if it's non-null and an invalid * string. */ function checkNullOrString(aValue) { if (aValue) { return checkString(aValue); } else if (aValue === null) { return true; } return false; } /** * If value is non-null, check if it's a boolean. * * @param aValue The variable to check. * @return True if it's null or a valid boolean, false if it's non-null and an invalid * boolean. */ function checkNullOrBool(aValue) { return aValue === null || (typeof aValue == "boolean"); } function checkBuildSection(data) { const expectedInfo = { applicationId: APP_ID, applicationName: APP_NAME, buildId: gAppInfo.appBuildID, version: APP_VERSION, vendor: "Mozilla", platformVersion: PLATFORM_VERSION, xpcomAbi: "noarch-spidermonkey", }; Assert.ok("build" in data, "There must be a build section in Environment."); for (let f in expectedInfo) { Assert.ok(checkString(data.build[f]), f + " must be a valid string."); Assert.equal(data.build[f], expectedInfo[f], f + " must have the correct value."); } // Make sure architecture is in the environment. Assert.ok(checkString(data.build.architecture)); if (gIsMac) { let macUtils = Cc["@mozilla.org/xpcom/mac-utils;1"].getService(Ci.nsIMacUtils); if (macUtils && macUtils.isUniversalBinary) { Assert.ok(checkString(data.build.architecturesInBinary)); } } } function checkSettingsSection(data) { const EXPECTED_FIELDS_TYPES = { blocklistEnabled: "boolean", e10sEnabled: "boolean", e10sCohort: "string", telemetryEnabled: "boolean", locale: "string", update: "object", userPrefs: "object", }; Assert.ok("settings" in data, "There must be a settings section in Environment."); for (let f in EXPECTED_FIELDS_TYPES) { Assert.equal(typeof data.settings[f], EXPECTED_FIELDS_TYPES[f], f + " must have the correct type."); } // Check "addonCompatibilityCheckEnabled" separately, as it is not available // on Gonk. if (gIsGonk) { Assert.ok(!("addonCompatibilityCheckEnabled" in data.settings), "Must not be available on Gonk."); } else { Assert.equal(data.settings.addonCompatibilityCheckEnabled, AddonManager.checkCompatibility); } // Check "isDefaultBrowser" separately, as it is not available on Android an can either be // null or boolean on other platforms. if (gIsAndroid) { Assert.ok(!("isDefaultBrowser" in data.settings), "Must not be available on Android."); } else { Assert.ok(checkNullOrBool(data.settings.isDefaultBrowser)); } // Check "channel" separately, as it can either be null or string. let update = data.settings.update; Assert.ok(checkNullOrString(update.channel)); Assert.equal(typeof update.enabled, "boolean"); Assert.equal(typeof update.autoDownload, "boolean"); // Check "defaultSearchEngine" separately, as it can either be undefined or string. if ("defaultSearchEngine" in data.settings) { checkString(data.settings.defaultSearchEngine); Assert.equal(typeof data.settings.defaultSearchEngineData, "object"); } if ("attribution" in data.settings) { Assert.equal(typeof data.settings.attribution, "object"); Assert.equal(data.settings.attribution.source, "google.com"); } } function checkProfileSection(data) { Assert.ok("profile" in data, "There must be a profile section in Environment."); Assert.equal(data.profile.creationDate, truncateToDays(PROFILE_CREATION_DATE_MS)); Assert.equal(data.profile.resetDate, truncateToDays(PROFILE_RESET_DATE_MS)); } function checkPartnerSection(data, isInitial) { const EXPECTED_FIELDS = { distributionId: DISTRIBUTION_ID, distributionVersion: DISTRIBUTION_VERSION, partnerId: PARTNER_ID, distributor: DISTRIBUTOR_NAME, distributorChannel: DISTRIBUTOR_CHANNEL, }; Assert.ok("partner" in data, "There must be a partner section in Environment."); for (let f in EXPECTED_FIELDS) { let expected = isInitial ? null : EXPECTED_FIELDS[f]; Assert.strictEqual(data.partner[f], expected, f + " must have the correct value."); } // Check that "partnerNames" exists and contains the correct element. Assert.ok(Array.isArray(data.partner.partnerNames)); if (isInitial) { Assert.equal(data.partner.partnerNames.length, 0); } else { Assert.ok(data.partner.partnerNames.includes(PARTNER_NAME)); } } function checkGfxAdapter(data) { const EXPECTED_ADAPTER_FIELDS_TYPES = { description: "string", vendorID: "string", deviceID: "string", subsysID: "string", RAM: "number", driver: "string", driverVersion: "string", driverDate: "string", GPUActive: "boolean", }; for (let f in EXPECTED_ADAPTER_FIELDS_TYPES) { Assert.ok(f in data, f + " must be available."); if (data[f]) { // Since we have a non-null value, check if it has the correct type. Assert.equal(typeof data[f], EXPECTED_ADAPTER_FIELDS_TYPES[f], f + " must have the correct type."); } } } function checkSystemSection(data) { const EXPECTED_FIELDS = [ "memoryMB", "cpu", "os", "hdd", "gfx" ]; const EXPECTED_HDD_FIELDS = [ "profile", "binary", "system" ]; Assert.ok("system" in data, "There must be a system section in Environment."); // Make sure we have all the top level sections and fields. for (let f of EXPECTED_FIELDS) { Assert.ok(f in data.system, f + " must be available."); } Assert.ok(Number.isFinite(data.system.memoryMB), "MemoryMB must be a number."); if (gIsWindows || gIsMac || gIsLinux) { let EXTRA_CPU_FIELDS = ["cores", "model", "family", "stepping", "l2cacheKB", "l3cacheKB", "speedMHz", "vendor"]; for (let f of EXTRA_CPU_FIELDS) { // Note this is testing TelemetryEnvironment.js only, not that the // values are valid - null is the fallback. Assert.ok(f in data.system.cpu, f + " must be available under cpu."); } if (gIsWindows) { Assert.equal(typeof data.system.isWow64, "boolean", "isWow64 must be available on Windows and have the correct type."); Assert.ok("virtualMaxMB" in data.system, "virtualMaxMB must be available."); Assert.ok(Number.isFinite(data.system.virtualMaxMB), "virtualMaxMB must be a number."); } // We insist these are available for (let f of ["cores"]) { Assert.ok(!(f in data.system.cpu) || Number.isFinite(data.system.cpu[f]), f + " must be a number if non null."); } // These should be numbers if they are not null for (let f of ["model", "family", "stepping", "l2cacheKB", "l3cacheKB", "speedMHz"]) { 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."); } } let cpuData = data.system.cpu; Assert.ok(Number.isFinite(cpuData.count), "CPU count must be a number."); Assert.ok(Array.isArray(cpuData.extensions), "CPU extensions must be available."); // Device data is only available on Android or Gonk. if (gIsAndroid || gIsGonk) { let deviceData = data.system.device; Assert.ok(checkNullOrString(deviceData.model)); Assert.ok(checkNullOrString(deviceData.manufacturer)); Assert.ok(checkNullOrString(deviceData.hardware)); Assert.ok(checkNullOrBool(deviceData.isTablet)); } let osData = data.system.os; Assert.ok(checkNullOrString(osData.name)); Assert.ok(checkNullOrString(osData.version)); Assert.ok(checkNullOrString(osData.locale)); // Service pack is only available on Windows. if (gIsWindows) { Assert.ok(Number.isFinite(osData["servicePackMajor"]), "ServicePackMajor must be a number."); Assert.ok(Number.isFinite(osData["servicePackMinor"]), "ServicePackMinor must be a number."); if ("windowsBuildNumber" in osData) { // This might not be available on all Windows platforms. Assert.ok(Number.isFinite(osData["windowsBuildNumber"]), "windowsBuildNumber must be a number."); } if ("windowsUBR" in osData) { // This might not be available on all Windows platforms. Assert.ok((osData["windowsUBR"] === null) || Number.isFinite(osData["windowsUBR"]), "windowsUBR must be null or a number."); } } else if (gIsAndroid || gIsGonk) { Assert.ok(checkNullOrString(osData.kernelVersion)); } let check = gIsWindows ? checkString : checkNullOrString; for (let disk of EXPECTED_HDD_FIELDS) { Assert.ok(check(data.system.hdd[disk].model)); Assert.ok(check(data.system.hdd[disk].revision)); } let gfxData = data.system.gfx; Assert.ok("D2DEnabled" in gfxData); Assert.ok("DWriteEnabled" in gfxData); // DWriteVersion is disabled due to main thread jank and will be enabled // again as part of bug 1154500. // Assert.ok("DWriteVersion" in gfxData); if (gIsWindows) { Assert.equal(typeof gfxData.D2DEnabled, "boolean"); Assert.equal(typeof gfxData.DWriteEnabled, "boolean"); // As above, will be enabled again as part of bug 1154500. // Assert.ok(checkString(gfxData.DWriteVersion)); } Assert.ok("adapters" in gfxData); Assert.ok(gfxData.adapters.length > 0, "There must be at least one GFX adapter."); for (let adapter of gfxData.adapters) { checkGfxAdapter(adapter); } Assert.equal(typeof gfxData.adapters[0].GPUActive, "boolean"); Assert.ok(gfxData.adapters[0].GPUActive, "The first GFX adapter must be active."); Assert.ok(Array.isArray(gfxData.monitors)); if (gIsWindows || gIsMac) { Assert.ok(gfxData.monitors.length >= 1, "There is at least one monitor."); Assert.equal(typeof gfxData.monitors[0].screenWidth, "number"); Assert.equal(typeof gfxData.monitors[0].screenHeight, "number"); if (gIsWindows) { Assert.equal(typeof gfxData.monitors[0].refreshRate, "number"); Assert.equal(typeof gfxData.monitors[0].pseudoDisplay, "boolean"); } if (gIsMac) { Assert.equal(typeof gfxData.monitors[0].scale, "number"); } } Assert.equal(typeof gfxData.features, "object"); Assert.equal(typeof gfxData.features.compositor, "string"); try { // If we've not got nsIGfxInfoDebug, then this will throw and stop us doing // this test. let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug); if (gIsWindows || gIsMac) { Assert.equal(GFX_VENDOR_ID, gfxData.adapters[0].vendorID); Assert.equal(GFX_DEVICE_ID, gfxData.adapters[0].deviceID); } let features = gfxInfo.getFeatures(); Assert.equal(features.compositor, gfxData.features.compositor); Assert.equal(features.opengl, gfxData.features.opengl); Assert.equal(features.webgl, gfxData.features.webgl); } catch (e) {} } function checkActiveAddon(data) { let signedState = mozinfo.addon_signing ? "number" : "undefined"; // system add-ons have an undefined signState if (data.isSystem) signedState = "undefined"; const EXPECTED_ADDON_FIELDS_TYPES = { blocklisted: "boolean", name: "string", userDisabled: "boolean", appDisabled: "boolean", version: "string", scope: "number", type: "string", foreignInstall: "boolean", hasBinaryComponents: "boolean", installDay: "number", updateDay: "number", signedState: signedState, isSystem: "boolean", }; for (let f in EXPECTED_ADDON_FIELDS_TYPES) { Assert.ok(f in data, f + " must be available."); Assert.equal(typeof data[f], EXPECTED_ADDON_FIELDS_TYPES[f], f + " must have the correct type."); } // We check "description" separately, as it can be null. Assert.ok(checkNullOrString(data.description)); } function checkPlugin(data) { const EXPECTED_PLUGIN_FIELDS_TYPES = { name: "string", version: "string", description: "string", blocklisted: "boolean", disabled: "boolean", clicktoplay: "boolean", updateDay: "number", }; for (let f in EXPECTED_PLUGIN_FIELDS_TYPES) { Assert.ok(f in data, f + " must be available."); Assert.equal(typeof data[f], EXPECTED_PLUGIN_FIELDS_TYPES[f], f + " must have the correct type."); } Assert.ok(Array.isArray(data.mimeTypes)); for (let type of data.mimeTypes) { Assert.ok(checkString(type)); } } function checkTheme(data) { // "hasBinaryComponents" is not available when testing. const EXPECTED_THEME_FIELDS_TYPES = { id: "string", blocklisted: "boolean", name: "string", userDisabled: "boolean", appDisabled: "boolean", version: "string", scope: "number", foreignInstall: "boolean", installDay: "number", updateDay: "number", }; for (let f in EXPECTED_THEME_FIELDS_TYPES) { Assert.ok(f in data, f + " must be available."); Assert.equal(typeof data[f], EXPECTED_THEME_FIELDS_TYPES[f], f + " must have the correct type."); } // We check "description" separately, as it can be null. Assert.ok(checkNullOrString(data.description)); } function checkActiveGMPlugin(data) { // GMP plugin version defaults to null until GMPDownloader runs to update it. if (data.version) { Assert.equal(typeof data.version, "string"); } Assert.equal(typeof data.userDisabled, "boolean"); Assert.equal(typeof data.applyBackgroundUpdates, "number"); } function checkAddonsSection(data, expectBrokenAddons) { const EXPECTED_FIELDS = [ "activeAddons", "theme", "activePlugins", "activeGMPlugins", "activeExperiment", "persona", ]; Assert.ok("addons" in data, "There must be an addons section in Environment."); for (let f of EXPECTED_FIELDS) { Assert.ok(f in data.addons, f + " must be available."); } // Check the active addons, if available. if (!expectBrokenAddons) { let activeAddons = data.addons.activeAddons; for (let addon in activeAddons) { checkActiveAddon(activeAddons[addon]); } } // Check "theme" structure. if (Object.keys(data.addons.theme).length !== 0) { checkTheme(data.addons.theme); } // Check the active plugins. Assert.ok(Array.isArray(data.addons.activePlugins)); for (let plugin of data.addons.activePlugins) { checkPlugin(plugin); } // Check active GMPlugins let activeGMPlugins = data.addons.activeGMPlugins; for (let gmPlugin in activeGMPlugins) { checkActiveGMPlugin(activeGMPlugins[gmPlugin]); } // Check the active Experiment let experiment = data.addons.activeExperiment; if (Object.keys(experiment).length !== 0) { Assert.ok(checkString(experiment.id)); Assert.ok(checkString(experiment.branch)); } // Check persona Assert.ok(checkNullOrString(data.addons.persona)); } function checkEnvironmentData(data, isInitial = false, expectBrokenAddons = false) { checkBuildSection(data); checkSettingsSection(data); checkProfileSection(data); checkPartnerSection(data, isInitial); checkSystemSection(data); checkAddonsSection(data, expectBrokenAddons); } add_task(function* setup() { // Load a custom manifest to provide search engine loading from JAR files. do_load_manifest("chrome.manifest"); registerFakeSysInfo(); spoofGfxAdapter(); do_get_profile(); // The system add-on must be installed before AddonManager is started. const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true); 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; loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION); // Spoof the persona ID, but not on Gonk. if (!gIsGonk) { LightweightThemeManager.currentTheme = spoofTheme(PERSONA_ID, PERSONA_NAME, PERSONA_DESCRIPTION); } // Register a fake plugin host for consistent flash version data. registerFakePluginHost(); // Setup a webserver to serve Addons, Plugins, 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()); do_register_cleanup(() => 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") { spoofAttributionData(); do_register_cleanup(cleanupAttributionData); } yield spoofProfileReset(); TelemetryEnvironment.delayedInit(); }); add_task(function* test_checkEnvironment() { let environmentData = yield TelemetryEnvironment.onInitialized(); checkEnvironmentData(environmentData, true); spoofPartnerInfo(); Services.obs.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null); environmentData = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(environmentData); }); add_task(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}], ]); Preferences.set(PREF_TEST_4, expectedValue); Preferences.set(PREF_TEST_5, expectedValue); // Set the Environment preferences to watch. TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH); let deferred = PromiseUtils.defer(); // 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. Preferences.set(PREF_TEST_1, expectedValue); Preferences.set(PREF_TEST_2, false); Preferences.set(PREF_TEST_5, unexpectedValue); let eventEnvironmentData = yield 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], "", "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(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. Preferences.set(PREF_TEST, false); // Set the Environment preferences to watch. TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH); let deferred = PromiseUtils.defer(); TelemetryEnvironment.registerChangeListener("testWatchPrefs_reset", deferred.resolve); Assert.strictEqual(TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST], ""); // Trigger a change in the watched preferences. Preferences.reset(PREF_TEST); yield deferred.promise; Assert.strictEqual(TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST], undefined); // Unregister the listener. TelemetryEnvironment.unregisterChangeListener("testWatchPrefs_reset"); }); add_task(function* test_addonsWatch_InterestingChange() { const ADDON_INSTALL_URL = gDataRoot + "restartless.xpi"; const ADDON_ID = "tel-restartless-xpi@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); yield AddonManagerTesting.installXPIFromURL(ADDON_INSTALL_URL); yield checkpointPromise; assertCheckpoint(1); Assert.ok(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons); checkpointPromise = registerCheckpointPromise(2); let addon = yield AddonManagerTesting.getAddonById(ADDON_ID); addon.userDisabled = true; yield checkpointPromise; assertCheckpoint(2); Assert.ok(!(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons)); checkpointPromise = registerCheckpointPromise(3); addon.userDisabled = false; yield checkpointPromise; assertCheckpoint(3); Assert.ok(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons); checkpointPromise = registerCheckpointPromise(4); yield AddonManagerTesting.uninstallAddonByID(ADDON_ID); yield 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(function* test_pluginsWatch_Add() { if (gIsAndroid) { Assert.ok(true, "Skipping: there is no Plugin Manager on Android."); return; } Assert.equal(TelemetryEnvironment.currentEnvironment.addons.activePlugins.length, 1); let newPlugin = new PluginTag(PLUGIN2_NAME, PLUGIN2_DESC, PLUGIN2_VERSION, true); gInstalledPlugins.push(newPlugin); let deferred = PromiseUtils.defer(); let receivedNotifications = 0; let callback = (reason, data) => { receivedNotifications++; Assert.equal(reason, "addons-changed"); deferred.resolve(); }; TelemetryEnvironment.registerChangeListener("testWatchPlugins_Add", callback); Services.obs.notifyObservers(null, PLUGIN_UPDATED_TOPIC, null); yield deferred.promise; Assert.equal(TelemetryEnvironment.currentEnvironment.addons.activePlugins.length, 2); TelemetryEnvironment.unregisterChangeListener("testWatchPlugins_Add"); Assert.equal(receivedNotifications, 1, "We must only receive one notification."); }); add_task(function* test_pluginsWatch_Remove() { if (gIsAndroid) { Assert.ok(true, "Skipping: there is no Plugin Manager on Android."); return; } // Find the test plugin. let plugin = gInstalledPlugins.find(p => (p.name == PLUGIN2_NAME)); Assert.ok(plugin, "The test plugin must exist."); // Remove it from the PluginHost. gInstalledPlugins = gInstalledPlugins.filter(p => p != plugin); let deferred = PromiseUtils.defer(); let receivedNotifications = 0; let callback = () => { receivedNotifications++; deferred.resolve(); }; TelemetryEnvironment.registerChangeListener("testWatchPlugins_Remove", callback); Services.obs.notifyObservers(null, PLUGIN_UPDATED_TOPIC, null); yield deferred.promise; TelemetryEnvironment.unregisterChangeListener("testWatchPlugins_Remove"); Assert.equal(receivedNotifications, 1, "We must only receive one notification."); }); add_task(function* test_addonsWatch_NotInterestingChange() { // We are not interested to dictionary addons changes. const DICTIONARY_ADDON_INSTALL_URL = gDataRoot + "dictionary.xpi"; const INTERESTING_ADDON_INSTALL_URL = gDataRoot + "restartless.xpi"; let receivedNotification = false; let deferred = PromiseUtils.defer(); TelemetryEnvironment.registerChangeListener("testNotInteresting", () => { Assert.ok(!receivedNotification, "Should not receive multiple notifications"); receivedNotification = true; deferred.resolve(); }); yield AddonManagerTesting.installXPIFromURL(DICTIONARY_ADDON_INSTALL_URL); yield AddonManagerTesting.installXPIFromURL(INTERESTING_ADDON_INSTALL_URL); yield deferred.promise; Assert.ok(!("telemetry-dictionary@tests.mozilla.org" in TelemetryEnvironment.currentEnvironment.addons.activeAddons), "Dictionaries should not appear in active addons."); TelemetryEnvironment.unregisterChangeListener("testNotInteresting"); }); add_task(function* test_addonsAndPlugins() { const ADDON_INSTALL_URL = gDataRoot + "restartless.xpi"; const ADDON_ID = "tel-restartless-xpi@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: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED, isSystem: false, }; 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, }; const EXPECTED_PLUGIN_DATA = { name: FLASH_PLUGIN_NAME, version: FLASH_PLUGIN_VERSION, description: FLASH_PLUGIN_DESC, blocklisted: false, disabled: false, clicktoplay: true, }; // Install an addon so we have some data. yield AddonManagerTesting.installXPIFromURL(ADDON_INSTALL_URL); let data = TelemetryEnvironment.currentEnvironment; 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 theme data. let theme = data.addons.theme; Assert.equal(theme.id, (PERSONA_ID + PERSONA_ID_SUFFIX)); Assert.equal(theme.name, PERSONA_NAME); Assert.equal(theme.description, PERSONA_DESCRIPTION); // Check plugin data. Assert.equal(data.addons.activePlugins.length, 1, "We must have only one active plugin."); let targetPlugin = data.addons.activePlugins[0]; for (let f in EXPECTED_PLUGIN_DATA) { Assert.equal(targetPlugin[f], EXPECTED_PLUGIN_DATA[f], f + " must have the correct value."); } // Check plugin mime types. Assert.ok(targetPlugin.mimeTypes.find(m => m == PLUGIN_MIME_TYPE1)); Assert.ok(targetPlugin.mimeTypes.find(m => m == PLUGIN_MIME_TYPE2)); Assert.ok(!targetPlugin.mimeTypes.find(m => m == "Not There.")); let personaId = (gIsGonk) ? null : PERSONA_ID; Assert.equal(data.addons.persona, personaId, "The correct Persona Id must be reported."); // Uninstall the addon. yield AddonManagerTesting.uninstallAddonByID(ADDON_ID); }); add_task(function* test_signedAddon() { const ADDON_INSTALL_URL = gDataRoot + "signed.xpi"; const ADDON_ID = "tel-signed-xpi@tests.mozilla.org"; const ADDON_INSTALL_DATE = truncateToDays(Date.now()); const EXPECTED_ADDON_DATA = { blocklisted: false, description: "A signed addon which gets enabled without a reboot.", 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: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED, }; let deferred = PromiseUtils.defer(); TelemetryEnvironment.registerChangeListener("test_signedAddon", deferred.resolve); // Install the addon. yield AddonManagerTesting.installXPIFromURL(ADDON_INSTALL_URL); yield deferred.promise; // Unregister the listener. TelemetryEnvironment.unregisterChangeListener("test_signedAddon"); let data = TelemetryEnvironment.currentEnvironment; 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."); } }); add_task(function* test_addonsFieldsLimit() { const ADDON_INSTALL_URL = gDataRoot + "long-fields.xpi"; const ADDON_ID = "tel-longfields-xpi@tests.mozilla.org"; // Install the addon and wait for the TelemetryEnvironment to pick it up. let deferred = PromiseUtils.defer(); TelemetryEnvironment.registerChangeListener("test_longFieldsAddon", deferred.resolve); yield AddonManagerTesting.installXPIFromURL(ADDON_INSTALL_URL); yield deferred.promise; TelemetryEnvironment.unregisterChangeListener("test_longFieldsAddon"); let data = TelemetryEnvironment.currentEnvironment; 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"); }); add_task(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, }; const ADDON_INSTALL_URL = gDataRoot + "restartless.xpi"; const ADDON_ID = "tel-restartless-xpi@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: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED, }; 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); yield checkpointPromise; assertCheckpoint(1); // Now install an addon which returns the correct information. checkpointPromise = registerCheckpointPromise(2); yield AddonManagerTesting.installXPIFromURL(ADDON_INSTALL_URL); yield checkpointPromise; assertCheckpoint(2); // Check that the new environment contains the Social addon installed with the broken // manifest and the rest of the data. let data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data, false, true /* expect broken addons*/); 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. yield AddonManagerTesting.uninstallAddonByID(ADDON_ID); }); add_task(function* test_defaultSearchEngine() { // Check that no default engine is in the environment before the search service is // initialized. let data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data); Assert.ok(!("defaultSearchEngine" in data.settings)); Assert.ok(!("defaultSearchEngineData" in data.settings)); // Load the engines definitions from a custom JAR file: that's needed so that // the search provider reports an engine identifier. let url = "chrome://testsearchplugin/locale/searchplugins/"; let resProt = Services.io.getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler); resProt.setSubstitution("search-plugins", Services.io.newURI(url, null, null)); // Initialize the search service. yield new Promise(resolve => Services.search.init(resolve)); // Our default engine from the JAR file has an identifier. Check if it is correctly // reported. data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data); Assert.equal(data.settings.defaultSearchEngine, "telemetrySearchIdentifier"); let expectedSearchEngineData = { name: "telemetrySearchIdentifier", loadPath: "jar:[other]/searchTest.jar!testsearchplugin/telemetrySearchIdentifier.xml", origin: "default", submissionURL: "http://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB?search=&sourceid=Mozilla-search" }; Assert.deepEqual(data.settings.defaultSearchEngineData, expectedSearchEngineData); // Remove all the search engines. for (let engine of Services.search.getEngines()) { Services.search.removeEngine(engine); } // The search service does not notify "engine-current" when removing a default engine. // Manually force the notification. // TODO: remove this when bug 1165341 is resolved. Services.obs.notifyObservers(null, "browser-search-engine-modified", "engine-current"); // Then check that no default engine is reported if none is available. data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data); Assert.equal(data.settings.defaultSearchEngine, "NONE"); Assert.deepEqual(data.settings.defaultSearchEngineData, {name:"NONE"}); // Add a new search engine (this will have no engine identifier). const SEARCH_ENGINE_ID = "telemetry_default"; const SEARCH_ENGINE_URL = "http://www.example.org/?search={searchTerms}"; Services.search.addEngineWithDetails(SEARCH_ENGINE_ID, "", null, "", "get", SEARCH_ENGINE_URL); // Register a new change listener and then wait for the search engine change to be notified. let deferred = PromiseUtils.defer(); TelemetryEnvironment.registerChangeListener("testWatch_SearchDefault", deferred.resolve); Services.search.defaultEngine = Services.search.getEngineByName(SEARCH_ENGINE_ID); yield deferred.promise; data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data); const EXPECTED_SEARCH_ENGINE = "other-" + SEARCH_ENGINE_ID; Assert.equal(data.settings.defaultSearchEngine, EXPECTED_SEARCH_ENGINE); const EXPECTED_SEARCH_ENGINE_DATA = { name: "telemetry_default", loadPath: "[other]addEngineWithDetails", origin: "verified" }; Assert.deepEqual(data.settings.defaultSearchEngineData, EXPECTED_SEARCH_ENGINE_DATA); TelemetryEnvironment.unregisterChangeListener("testWatch_SearchDefault"); // Cleanly install an engine from an xml file, and check if origin is // recorded as "verified". let promise = new Promise(resolve => { TelemetryEnvironment.registerChangeListener("testWatch_SearchDefault", resolve); }); let engine = yield new Promise((resolve, reject) => { Services.obs.addObserver(function obs(obsSubject, obsTopic, obsData) { try { let searchEngine = obsSubject.QueryInterface(Ci.nsISearchEngine); do_print("Observed " + obsData + " for " + searchEngine.name); if (obsData != "engine-added" || searchEngine.name != "engine-telemetry") { return; } Services.obs.removeObserver(obs, "browser-search-engine-modified"); resolve(searchEngine); } catch (ex) { reject(ex); } }, "browser-search-engine-modified", false); Services.search.addEngine("file://" + do_get_cwd().path + "/engine.xml", null, null, false); }); Services.search.defaultEngine = engine; yield promise; TelemetryEnvironment.unregisterChangeListener("testWatch_SearchDefault"); data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data); Assert.deepEqual(data.settings.defaultSearchEngineData, {"name":"engine-telemetry", "loadPath":"[other]/engine.xml", "origin":"verified"}); // Now break this engine's load path hash. promise = new Promise(resolve => { TelemetryEnvironment.registerChangeListener("testWatch_SearchDefault", resolve); }); engine.wrappedJSObject.setAttr("loadPathHash", "broken"); Services.obs.notifyObservers(null, "browser-search-engine-modified", "engine-current"); yield promise; TelemetryEnvironment.unregisterChangeListener("testWatch_SearchDefault"); data = TelemetryEnvironment.currentEnvironment; Assert.equal(data.settings.defaultSearchEngineData.origin, "invalid"); Services.search.removeEngine(engine); // 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}], ]); Preferences.reset(PREF_TEST); // Watch the test preference. TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH); deferred = PromiseUtils.defer(); TelemetryEnvironment.registerChangeListener("testSearchEngine_pref", deferred.resolve); // Trigger an environment change. Preferences.set(PREF_TEST, 1); yield deferred.promise; TelemetryEnvironment.unregisterChangeListener("testSearchEngine_pref"); // Check that the search engine information is correctly retained when prefs change. data = TelemetryEnvironment.currentEnvironment; checkEnvironmentData(data); Assert.equal(data.settings.defaultSearchEngine, EXPECTED_SEARCH_ENGINE); // Check that by default we are not sending a cohort identifier... Assert.equal(data.settings.searchCohort, undefined); // ... but that if a cohort identifier is set, we send it. Services.prefs.setCharPref("browser.search.cohort", "testcohort"); Services.obs.notifyObservers(null, "browser-search-service", "init-complete"); data = TelemetryEnvironment.currentEnvironment; Assert.equal(data.settings.searchCohort, "testcohort"); }); add_task(function* test_osstrings() { // First test that numbers in sysinfo properties are converted to string fields // in system.os. SysInfo.overrides = { version: 1, name: 2, kernel_version: 3, }; yield TelemetryEnvironment.testCleanRestart().onInitialized(); let data = TelemetryEnvironment.currentEnvironment; 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. SysInfo.overrides = { version: null, name: null, kernel_version: null, }; yield TelemetryEnvironment.testCleanRestart().onInitialized(); data = TelemetryEnvironment.currentEnvironment; 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. SysInfo.overrides = {}; yield TelemetryEnvironment.testCleanRestart().onInitialized(); }); add_task(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}], ]); Preferences.reset(PREF_TEST); // Set up the preferences and listener, then the trigger shutdown 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 Preferences.set(PREF_TEST, 1); // Unregister the listener. TelemetryEnvironment.unregisterChangeListener("test_environmentShutdownChange"); });