1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { Log } from "resource://gre/modules/Log.sys.mjs";
7 import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
9 import { ObjectUtils } from "resource://gre/modules/ObjectUtils.sys.mjs";
10 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
11 import { UpdateUtils } from "resource://gre/modules/UpdateUtils.sys.mjs";
13 const Utils = TelemetryUtils;
18 } from "resource://gre/modules/AddonManager.sys.mjs";
22 ChromeUtils.defineESModuleGetters(lazy, {
23 AttributionCode: "resource:///modules/AttributionCode.sys.mjs",
24 ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs",
25 WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
27 "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
30 ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
31 return ChromeUtils.importESModule(
32 "resource://gre/modules/FxAccounts.sys.mjs"
33 ).getFxAccountsSingleton();
36 // The maximum length of a string (e.g. description) in the addons section.
37 const MAX_ADDON_STRING_LENGTH = 100;
38 // The maximum length of a string value in the settings.attribution object.
39 const MAX_ATTRIBUTION_STRING_LENGTH = 100;
40 // The maximum lengths for the experiment id and branch in the experiments section.
41 const MAX_EXPERIMENT_ID_LENGTH = 100;
42 const MAX_EXPERIMENT_BRANCH_LENGTH = 100;
43 const MAX_EXPERIMENT_TYPE_LENGTH = 20;
44 const MAX_EXPERIMENT_ENROLLMENT_ID_LENGTH = 40;
47 * This is a policy object used to override behavior for testing.
49 // eslint-disable-next-line no-unused-vars
51 now: () => new Date(),
53 _browserDelayedStartup() {
54 if (Policy._intlLoaded) {
55 return Promise.resolve();
57 return new Promise(resolve => {
58 let startupTopic = "browser-delayed-startup-finished";
59 Services.obs.addObserver(function observer(subject, topic) {
60 if (topic == startupTopic) {
61 Services.obs.removeObserver(observer, startupTopic);
69 // This is used to buffer calls to setExperimentActive and friends, so that we
70 // don't prematurely initialize our environment if it is called early during
72 var gActiveExperimentStartupBuffer = new Map();
74 var gGlobalEnvironment;
75 function getGlobal() {
76 if (!gGlobalEnvironment) {
77 gGlobalEnvironment = new EnvironmentCache();
79 return gGlobalEnvironment;
82 export var TelemetryEnvironment = {
83 get currentEnvironment() {
84 return getGlobal().currentEnvironment;
88 return getGlobal().onInitialized();
92 return getGlobal().delayedInit();
95 registerChangeListener(name, listener) {
96 return getGlobal().registerChangeListener(name, listener);
99 unregisterChangeListener(name) {
100 return getGlobal().unregisterChangeListener(name);
104 * Add an experiment annotation to the environment.
105 * If an annotation with the same id already exists, it will be overwritten.
106 * This triggers a new subsession, subject to throttling.
108 * @param {String} id The id of the active experiment.
109 * @param {String} branch The experiment branch.
110 * @param {Object} [options] Optional object with options.
111 * @param {String} [options.type=false] The specific experiment type.
112 * @param {String} [options.enrollmentId=undefined] The id of the enrollment.
114 setExperimentActive(id, branch, options = {}) {
115 if (gGlobalEnvironment) {
116 gGlobalEnvironment.setExperimentActive(id, branch, options);
118 gActiveExperimentStartupBuffer.set(id, { branch, options });
123 * Remove an experiment annotation from the environment.
124 * If the annotation exists, a new subsession will triggered.
126 * @param {String} id The id of the active experiment.
128 setExperimentInactive(id) {
129 if (gGlobalEnvironment) {
130 gGlobalEnvironment.setExperimentInactive(id);
132 gActiveExperimentStartupBuffer.delete(id);
137 * Returns an object containing the data for the active experiments.
139 * The returned object is of the format:
142 * "<experiment id>": { branch: "<branch>" },
146 getActiveExperiments() {
147 if (gGlobalEnvironment) {
148 return gGlobalEnvironment.getActiveExperiments();
152 for (const [id, { branch }] of gActiveExperimentStartupBuffer.entries()) {
159 return getGlobal().shutdown();
162 // Policy to use when saving preferences. Exported for using them in tests.
163 // Reports "<user-set>" if there is a value set on the user branch
164 RECORD_PREF_STATE: 1,
166 // Reports the value set on the user branch, if one is set
167 RECORD_PREF_VALUE: 2,
169 // Reports the active value (set on either the user or default branch)
170 // for this pref, if one is set
171 RECORD_DEFAULTPREF_VALUE: 3,
173 // Reports "<set>" if a value for this pref is defined on either the user
175 RECORD_DEFAULTPREF_STATE: 4,
178 async testWatchPreferences(prefMap) {
179 return getGlobal()._watchPreferences(prefMap);
183 * Intended for use in tests only.
185 * In multiple tests we need a way to shut and re-start telemetry together
186 * with TelemetryEnvironment. This is problematic due to the fact that
187 * TelemetryEnvironment is a singleton. We, therefore, need this helper
188 * method to be able to re-set TelemetryEnvironment.
191 return getGlobal().reset();
195 * Intended for use in tests only.
198 getGlobal().shutdown();
199 gGlobalEnvironment = null;
200 gActiveExperimentStartupBuffer = new Map();
205 const RECORD_PREF_STATE = TelemetryEnvironment.RECORD_PREF_STATE;
206 const RECORD_PREF_VALUE = TelemetryEnvironment.RECORD_PREF_VALUE;
207 const RECORD_DEFAULTPREF_VALUE = TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE;
208 const RECORD_DEFAULTPREF_STATE = TelemetryEnvironment.RECORD_DEFAULTPREF_STATE;
209 const DEFAULT_ENVIRONMENT_PREFS = new Map([
210 ["app.feedback.baseURL", { what: RECORD_PREF_VALUE }],
211 ["app.support.baseURL", { what: RECORD_PREF_VALUE }],
212 ["accessibility.browsewithcaret", { what: RECORD_PREF_VALUE }],
213 ["accessibility.force_disabled", { what: RECORD_PREF_VALUE }],
214 ["app.normandy.test-prefs.bool", { what: RECORD_PREF_VALUE }],
215 ["app.normandy.test-prefs.integer", { what: RECORD_PREF_VALUE }],
216 ["app.normandy.test-prefs.string", { what: RECORD_PREF_VALUE }],
217 ["app.shield.optoutstudies.enabled", { what: RECORD_PREF_VALUE }],
218 ["app.update.interval", { what: RECORD_PREF_VALUE }],
219 ["app.update.service.enabled", { what: RECORD_PREF_VALUE }],
220 ["app.update.silent", { what: RECORD_PREF_VALUE }],
221 ["browser.cache.disk.enable", { what: RECORD_PREF_VALUE }],
222 ["browser.cache.disk.capacity", { what: RECORD_PREF_VALUE }],
223 ["browser.cache.memory.enable", { what: RECORD_PREF_VALUE }],
224 ["browser.formfill.enable", { what: RECORD_PREF_VALUE }],
225 ["browser.migrate.interactions.bookmarks", { what: RECORD_PREF_VALUE }],
226 ["browser.migrate.interactions.csvpasswords", { what: RECORD_PREF_VALUE }],
227 ["browser.migrate.interactions.history", { what: RECORD_PREF_VALUE }],
228 ["browser.migrate.interactions.passwords", { what: RECORD_PREF_VALUE }],
229 ["browser.newtabpage.enabled", { what: RECORD_PREF_VALUE }],
230 ["browser.privatebrowsing.autostart", { what: RECORD_PREF_VALUE }],
231 ["browser.shell.checkDefaultBrowser", { what: RECORD_PREF_VALUE }],
232 ["browser.search.region", { what: RECORD_PREF_VALUE }],
233 ["browser.search.suggest.enabled", { what: RECORD_PREF_VALUE }],
234 ["browser.search.widget.inNavBar", { what: RECORD_DEFAULTPREF_VALUE }],
235 ["browser.startup.homepage", { what: RECORD_PREF_STATE }],
236 ["browser.startup.page", { what: RECORD_PREF_VALUE }],
237 ["browser.tabs.firefox-view", { what: RECORD_PREF_VALUE }],
238 ["browser.tabs.firefox-view-next", { what: RECORD_PREF_VALUE }],
239 ["browser.urlbar.autoFill", { what: RECORD_DEFAULTPREF_VALUE }],
241 "browser.urlbar.autoFill.adaptiveHistory.enabled",
242 { what: RECORD_DEFAULTPREF_VALUE },
245 "browser.urlbar.dnsResolveSingleWordsAfterSearch",
246 { what: RECORD_DEFAULTPREF_VALUE },
249 "browser.urlbar.quicksuggest.onboardingDialogChoice",
250 { what: RECORD_DEFAULTPREF_VALUE },
253 "browser.urlbar.quicksuggest.dataCollection.enabled",
254 { what: RECORD_DEFAULTPREF_VALUE },
256 ["browser.urlbar.showSearchSuggestionsFirst", { what: RECORD_PREF_VALUE }],
257 ["browser.urlbar.showSearchTerms.enabled", { what: RECORD_PREF_VALUE }],
259 "browser.urlbar.suggest.quicksuggest.nonsponsored",
260 { what: RECORD_DEFAULTPREF_VALUE },
263 "browser.urlbar.suggest.quicksuggest.sponsored",
264 { what: RECORD_DEFAULTPREF_VALUE },
266 ["browser.urlbar.suggest.bestmatch", { what: RECORD_DEFAULTPREF_VALUE }],
267 ["browser.urlbar.suggest.searches", { what: RECORD_PREF_VALUE }],
268 ["devtools.chrome.enabled", { what: RECORD_PREF_VALUE }],
269 ["devtools.debugger.enabled", { what: RECORD_PREF_VALUE }],
270 ["devtools.debugger.remote-enabled", { what: RECORD_PREF_VALUE }],
271 ["doh-rollout.doorhanger-decision", { what: RECORD_PREF_VALUE }],
272 ["dom.ipc.processCount", { what: RECORD_PREF_VALUE }],
273 ["dom.max_script_run_time", { what: RECORD_PREF_VALUE }],
274 ["editor.truncate_user_pastes", { what: RECORD_PREF_VALUE }],
275 ["extensions.InstallTrigger.enabled", { what: RECORD_PREF_VALUE }],
276 ["extensions.InstallTriggerImpl.enabled", { what: RECORD_PREF_VALUE }],
277 ["extensions.autoDisableScopes", { what: RECORD_PREF_VALUE }],
278 ["extensions.blocklist.enabled", { what: RECORD_PREF_VALUE }],
279 ["extensions.enabledScopes", { what: RECORD_PREF_VALUE }],
280 ["extensions.eventPages.enabled", { what: RECORD_PREF_VALUE }],
281 ["extensions.formautofill.addresses.enabled", { what: RECORD_PREF_VALUE }],
283 "extensions.formautofill.addresses.capture.enabled",
284 { what: RECORD_PREF_VALUE },
286 ["extensions.formautofill.creditCards.enabled", { what: RECORD_PREF_VALUE }],
287 ["extensions.manifestV3.enabled", { what: RECORD_PREF_VALUE }],
288 ["extensions.quarantinedDomains.enabled", { what: RECORD_PREF_VALUE }],
289 ["extensions.strictCompatibility", { what: RECORD_PREF_VALUE }],
290 ["extensions.update.enabled", { what: RECORD_PREF_VALUE }],
291 ["extensions.update.url", { what: RECORD_PREF_VALUE }],
292 ["extensions.update.background.url", { what: RECORD_PREF_VALUE }],
293 ["extensions.screenshots.disabled", { what: RECORD_PREF_VALUE }],
294 ["general.config.filename", { what: RECORD_DEFAULTPREF_STATE }],
295 ["general.smoothScroll", { what: RECORD_PREF_VALUE }],
296 ["gfx.direct2d.disabled", { what: RECORD_PREF_VALUE }],
297 ["gfx.direct2d.force-enabled", { what: RECORD_PREF_VALUE }],
298 ["gfx.webrender.all", { what: RECORD_PREF_VALUE }],
299 ["layers.acceleration.disabled", { what: RECORD_PREF_VALUE }],
300 ["layers.acceleration.force-enabled", { what: RECORD_PREF_VALUE }],
301 ["layers.async-pan-zoom.enabled", { what: RECORD_PREF_VALUE }],
302 ["layers.async-video-oop.enabled", { what: RECORD_PREF_VALUE }],
303 ["layers.d3d11.disable-warp", { what: RECORD_PREF_VALUE }],
304 ["layers.d3d11.force-warp", { what: RECORD_PREF_VALUE }],
306 "layers.offmainthreadcomposition.force-disabled",
307 { what: RECORD_PREF_VALUE },
309 ["layers.prefer-d3d9", { what: RECORD_PREF_VALUE }],
310 ["layers.prefer-opengl", { what: RECORD_PREF_VALUE }],
311 ["layout.css.devPixelsPerPx", { what: RECORD_PREF_VALUE }],
312 ["media.gmp-gmpopenh264.enabled", { what: RECORD_PREF_VALUE }],
313 ["media.gmp-gmpopenh264.lastInstallFailed", { what: RECORD_PREF_VALUE }],
314 ["media.gmp-gmpopenh264.lastInstallFailReason", { what: RECORD_PREF_VALUE }],
315 ["media.gmp-gmpopenh264.lastInstallStart", { what: RECORD_PREF_VALUE }],
316 ["media.gmp-gmpopenh264.lastDownload", { what: RECORD_PREF_VALUE }],
317 ["media.gmp-gmpopenh264.lastDownloadFailed", { what: RECORD_PREF_VALUE }],
318 ["media.gmp-gmpopenh264.lastDownloadFailReason", { what: RECORD_PREF_VALUE }],
319 ["media.gmp-gmpopenh264.lastUpdate", { what: RECORD_PREF_VALUE }],
320 ["media.gmp-gmpopenh264.visible", { what: RECORD_PREF_VALUE }],
321 ["media.gmp-widevinecdm.enabled", { what: RECORD_PREF_VALUE }],
322 ["media.gmp-widevinecdm.lastInstallFailed", { what: RECORD_PREF_VALUE }],
323 ["media.gmp-widevinecdm.lastInstallFailReason", { what: RECORD_PREF_VALUE }],
324 ["media.gmp-widevinecdm.lastInstallStart", { what: RECORD_PREF_VALUE }],
325 ["media.gmp-widevinecdm.lastDownload", { what: RECORD_PREF_VALUE }],
326 ["media.gmp-widevinecdm.lastDownloadFailed", { what: RECORD_PREF_VALUE }],
327 ["media.gmp-widevinecdm.lastDownloadFailReason", { what: RECORD_PREF_VALUE }],
328 ["media.gmp-widevinecdm.lastUpdate", { what: RECORD_PREF_VALUE }],
329 ["media.gmp-widevinecdm.visible", { what: RECORD_PREF_VALUE }],
330 ["media.gmp-manager.lastCheck", { what: RECORD_PREF_VALUE }],
331 ["media.gmp-manager.lastEmptyCheck", { what: RECORD_PREF_VALUE }],
332 ["network.http.windows-sso.enabled", { what: RECORD_PREF_VALUE }],
333 ["network.proxy.autoconfig_url", { what: RECORD_PREF_STATE }],
334 ["network.proxy.http", { what: RECORD_PREF_STATE }],
335 ["network.proxy.ssl", { what: RECORD_PREF_STATE }],
336 ["network.trr.mode", { what: RECORD_PREF_VALUE }],
337 ["network.trr.strict_native_fallback", { what: RECORD_DEFAULTPREF_VALUE }],
338 ["pdfjs.disabled", { what: RECORD_PREF_VALUE }],
339 ["places.history.enabled", { what: RECORD_PREF_VALUE }],
340 ["privacy.firstparty.isolate", { what: RECORD_PREF_VALUE }],
341 ["privacy.resistFingerprinting", { what: RECORD_PREF_VALUE }],
342 ["privacy.fingerprintingProtection", { what: RECORD_PREF_VALUE }],
343 ["privacy.fingerprintingProtection.pbmode", { what: RECORD_PREF_VALUE }],
344 ["privacy.trackingprotection.enabled", { what: RECORD_PREF_VALUE }],
345 ["privacy.donottrackheader.enabled", { what: RECORD_PREF_VALUE }],
346 ["security.enterprise_roots.auto-enabled", { what: RECORD_PREF_VALUE }],
347 ["security.enterprise_roots.enabled", { what: RECORD_PREF_VALUE }],
348 ["security.pki.mitm_detected", { what: RECORD_PREF_VALUE }],
349 ["security.mixed_content.block_active_content", { what: RECORD_PREF_VALUE }],
350 ["security.mixed_content.block_display_content", { what: RECORD_PREF_VALUE }],
351 ["security.tls.version.enable-deprecated", { what: RECORD_PREF_VALUE }],
352 ["signon.management.page.breach-alerts.enabled", { what: RECORD_PREF_VALUE }],
353 ["signon.autofillForms", { what: RECORD_PREF_VALUE }],
354 ["signon.generation.enabled", { what: RECORD_PREF_VALUE }],
355 ["signon.rememberSignons", { what: RECORD_PREF_VALUE }],
356 ["signon.firefoxRelay.feature", { what: RECORD_PREF_VALUE }],
357 ["toolkit.telemetry.pioneerId", { what: RECORD_PREF_STATE }],
359 "widget.content.gtk-high-contrast.enabled",
360 { what: RECORD_DEFAULTPREF_VALUE },
362 ["xpinstall.signatures.required", { what: RECORD_PREF_VALUE }],
363 ["nimbus.debug", { what: RECORD_PREF_VALUE }],
366 const LOGGER_NAME = "Toolkit.Telemetry";
368 const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
369 const PREF_DISTRIBUTION_ID = "distribution.id";
370 const PREF_DISTRIBUTION_VERSION = "distribution.version";
371 const PREF_DISTRIBUTOR = "app.distributor";
372 const PREF_DISTRIBUTOR_CHANNEL = "app.distributor.channel";
373 const PREF_APP_PARTNER_BRANCH = "app.partner.";
374 const PREF_PARTNER_ID = "mozilla.partner.id";
376 const COMPOSITOR_CREATED_TOPIC = "compositor:created";
377 const COMPOSITOR_PROCESS_ABORTED_TOPIC = "compositor:process-aborted";
378 const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
379 "distribution-customization-complete";
380 const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
381 const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
382 const SEARCH_SERVICE_TOPIC = "browser-search-service";
383 const SESSIONSTORE_WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
384 const PREF_CHANGED_TOPIC = "nsPref:changed";
385 const GMP_PROVIDER_REGISTERED_TOPIC = "gmp-provider-registered";
386 const AUTO_UPDATE_PREF_CHANGE_TOPIC =
387 UpdateUtils.PER_INSTALLATION_PREFS["app.update.auto"].observerTopic;
388 const BACKGROUND_UPDATE_PREF_CHANGE_TOPIC =
389 UpdateUtils.PER_INSTALLATION_PREFS["app.update.background.enabled"]
391 const SERVICES_INFO_CHANGE_TOPIC = "sync-ui-state:update";
394 * Enforces the parameter to a boolean value.
395 * @param aValue The input value.
396 * @return {Boolean|Object} If aValue is a boolean or a number, returns its truthfulness
397 * value. Otherwise, return null.
399 function enforceBoolean(aValue) {
400 if (typeof aValue !== "number" && typeof aValue !== "boolean") {
403 return Boolean(aValue);
407 * Get the current browser locale.
408 * @return a string with the locale or null on failure.
410 function getBrowserLocale() {
412 return Services.locale.appLocaleAsBCP47;
419 * Get the current OS locale.
420 * @return a string with the OS locale or null on failure.
422 function getSystemLocale() {
424 return Cc["@mozilla.org/intl/ospreferences;1"].getService(
433 * Get the current OS locales.
434 * @return an array of strings with the OS locales or null on failure.
436 function getSystemLocales() {
438 return Cc["@mozilla.org/intl/ospreferences;1"].getService(
447 * Get the current OS regional preference locales.
448 * @return an array of strings with the OS regional preference locales or null on failure.
450 function getRegionalPrefsLocales() {
452 return Cc["@mozilla.org/intl/ospreferences;1"].getService(
454 ).regionalPrefsLocales;
460 function getIntlSettings() {
462 requestedLocales: Services.locale.requestedLocales,
463 availableLocales: Services.locale.availableLocales,
464 appLocales: Services.locale.appLocalesAsBCP47,
465 systemLocales: getSystemLocales(),
466 regionalPrefsLocales: getRegionalPrefsLocales(),
467 acceptLanguages: Services.prefs
468 .getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString)
470 .map(str => str.trim()),
475 * Safely get a sysinfo property and return its value. If the property is not
476 * available, return aDefault.
478 * @param aPropertyName the property name to get.
479 * @param aDefault the value to return if aPropertyName is not available.
480 * @return The property value, if available, or aDefault.
482 function getSysinfoProperty(aPropertyName, aDefault) {
484 // |getProperty| may throw if |aPropertyName| does not exist.
485 return Services.sysinfo.getProperty(aPropertyName);
492 * Safely get a gfxInfo field and return its value. If the field is not available, return
495 * @param aPropertyName the property name to get.
496 * @param aDefault the value to return if aPropertyName is not available.
497 * @return The property value, if available, or aDefault.
499 function getGfxField(aPropertyName, aDefault) {
500 let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
503 // Accessing the field may throw if |aPropertyName| does not exist.
504 let gfxProp = gfxInfo[aPropertyName];
505 if (gfxProp !== undefined && gfxProp !== "") {
514 * Returns a substring of the input string.
516 * @param {String} aString The input string.
517 * @param {Integer} aMaxLength The maximum length of the returned substring. If this is
518 * greater than the length of the input string, we return the whole input string.
519 * @return {String} The substring or null if the input string is null.
521 function limitStringToLength(aString, aMaxLength) {
522 if (typeof aString !== "string") {
525 return aString.substring(0, aMaxLength);
529 * Force a value to be a string.
530 * Only if the value is null, null is returned instead.
532 function forceToStringOrNull(aValue) {
533 if (aValue === null) {
537 return String(aValue);
541 * Get the information about a graphic adapter.
543 * @param aSuffix A suffix to add to the properties names.
544 * @return An object containing the adapter properties.
546 function getGfxAdapter(aSuffix = "") {
547 // Note that gfxInfo, and so getGfxField, might return "Unknown" for the RAM on failures,
549 let memoryMB = parseInt(getGfxField("adapterRAM" + aSuffix, null), 10);
550 if (Number.isNaN(memoryMB)) {
555 description: getGfxField("adapterDescription" + aSuffix, null),
556 vendorID: getGfxField("adapterVendorID" + aSuffix, null),
557 deviceID: getGfxField("adapterDeviceID" + aSuffix, null),
558 subsysID: getGfxField("adapterSubsysID" + aSuffix, null),
560 driver: getGfxField("adapterDriver" + aSuffix, null),
561 driverVendor: getGfxField("adapterDriverVendor" + aSuffix, null),
562 driverVersion: getGfxField("adapterDriverVersion" + aSuffix, null),
563 driverDate: getGfxField("adapterDriverDate" + aSuffix, null),
568 * Encapsulates the asynchronous magic interfacing with the addon manager. The builder
569 * is owned by a parent environment object and is an addon listener.
571 function EnvironmentAddonBuilder(environment) {
572 this._environment = environment;
574 // The pending task blocks addon manager shutdown. It can either be the initial load
576 this._pendingTask = null;
578 // Have we added an observer to listen for blocklist changes that still needs to be
580 this._gmpProviderObserverAdded = false;
582 // Set to true once initial load is complete and we're watching for changes.
583 this._loaded = false;
585 // The state reported by the shutdown blocker if we hang shutdown.
586 this._shutdownState = "Initial";
588 EnvironmentAddonBuilder.prototype = {
590 * Get the initial set of addons.
591 * @returns Promise<void> when the initial load is complete.
594 AddonManager.beforeShutdown.addBlocker(
595 "EnvironmentAddonBuilder",
596 () => this._shutdownBlocker(),
597 { fetchState: () => this._shutdownState }
600 this._pendingTask = (async () => {
602 this._shutdownState = "Awaiting _updateAddons";
603 // Gather initial addons details
604 await this._updateAddons();
606 if (!this._environment._addonsAreFull) {
607 // The addon database has not been loaded, wait for it to
608 // initialize and gather full data as soon as it does.
609 this._shutdownState = "Awaiting AddonManagerPrivate.databaseReady";
610 await AddonManagerPrivate.databaseReady;
612 // Now gather complete addons details.
613 this._shutdownState = "Awaiting second _updateAddons";
614 await this._updateAddons();
617 this._environment._log.error("init - Exception in _updateAddons", err);
619 this._pendingTask = null;
620 this._shutdownState = "_pendingTask init complete. No longer blocking.";
624 return this._pendingTask;
628 * Register an addon listener and watch for changes.
632 AddonManager.addAddonListener(this);
637 this._onAddonChange(addon);
640 this._onAddonChange(addon);
643 this._onAddonChange(addon);
645 onUninstalling(addon) {
646 this._onAddonChange(addon);
648 onUninstalled(addon) {
649 this._onAddonChange(addon);
651 onPropertyChanged(addon, propertiesChanged) {
652 // Avoid to update the telemetry environment for onPropertyChanged
653 // calls that we are not actually interested in (and quarantineIgnoredByApp
654 // is not expected to change at runtime, unless the entire active addons
655 // entry is also replaced, e.g. on the extension being uninstalled and
657 if (!propertiesChanged.includes("quarantineIgnoredByUser")) {
660 this._onAddonChange(addon);
663 _onAddonChange(addon) {
664 if (addon && addon.isBuiltin && !addon.isSystem) {
667 this._environment._log.trace("_onAddonChange");
668 this._checkForChanges("addons-changed");
672 observe(aSubject, aTopic, aData) {
673 this._environment._log.trace("observe - Topic " + aTopic);
674 if (aTopic == GMP_PROVIDER_REGISTERED_TOPIC) {
675 Services.obs.removeObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
676 this._gmpProviderObserverAdded = false;
677 let gmpPluginsPromise = this._getActiveGMPlugins();
678 gmpPluginsPromise.then(
680 let { addons } = this._environment._currentEnvironment;
681 addons.activeGMPlugins = gmpPlugins;
684 this._environment._log.error(
685 "blocklist observe: Error collecting plugins",
693 _checkForChanges(changeReason) {
694 if (this._pendingTask) {
695 this._environment._log.trace(
696 "_checkForChanges - task already pending, dropping change with reason " +
702 this._shutdownState = "_checkForChanges awaiting _updateAddons";
703 this._pendingTask = this._updateAddons().then(
705 this._pendingTask = null;
706 this._shutdownState = "No longer blocking, _updateAddons resolved";
707 if (result.changed) {
708 this._environment._onEnvironmentChange(
710 result.oldEnvironment
715 this._pendingTask = null;
716 this._shutdownState = "No longer blocking, _updateAddons rejected";
717 this._environment._log.error(
718 "_checkForChanges: Error collecting addons",
727 AddonManager.removeAddonListener(this);
728 if (this._gmpProviderObserverAdded) {
729 Services.obs.removeObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
733 // At startup, _pendingTask is set to a Promise that does not resolve
734 // until the addons database has been read so complete details about
735 // addons are available. Returning it here will cause it to block
736 // profileBeforeChange, guranteeing that full information will be
737 // available by the time profileBeforeChangeTelemetry is fired.
738 return this._pendingTask;
742 * Collect the addon data for the environment.
744 * This should only be called from _pendingTask; otherwise we risk
745 * running this during addon manager shutdown.
747 * @returns Promise<Object> This returns a Promise resolved with a status object with the following members:
748 * changed - Whether the environment changed.
749 * oldEnvironment - Only set if a change occured, contains the environment data before the change.
751 async _updateAddons() {
752 this._environment._log.trace("_updateAddons");
755 activeAddons: await this._getActiveAddons(),
756 theme: await this._getActiveTheme(),
757 activeGMPlugins: await this._getActiveGMPlugins(),
762 !this._environment._currentEnvironment.addons ||
763 !ObjectUtils.deepEqual(
765 this._environment._currentEnvironment.addons.activeAddons
769 if (result.changed) {
770 this._environment._log.trace("_updateAddons: addons differ");
771 result.oldEnvironment = Cu.cloneInto(
772 this._environment._currentEnvironment,
776 this._environment._currentEnvironment.addons = addons;
782 * Get the addon data in object form.
783 * @return Promise<object> containing the addon data.
785 async _getActiveAddons() {
786 // Request addons, asynchronously.
787 // "theme" is excluded because it is already handled by _getActiveTheme.
788 let { addons: allAddons, fullData } = await AddonManager.getActiveAddons(
789 AddonManagerPrivate.getAddonTypesByProvider("XPIProvider").filter(
790 addonType => addonType != "theme"
794 this._environment._addonsAreFull = fullData;
795 let activeAddons = {};
796 for (let addon of allAddons) {
797 // Don't collect any information about the new built-in search webextensions
798 if (addon.isBuiltin && !addon.isSystem) {
801 // Weird addon data in the wild can lead to exceptions while collecting
804 // Make sure to have valid dates.
805 let updateDate = new Date(Math.max(0, addon.updateDate));
807 activeAddons[addon.id] = {
808 version: limitStringToLength(addon.version, MAX_ADDON_STRING_LENGTH),
811 updateDay: Utils.millisecondsToDays(updateDate.getTime()),
812 isSystem: addon.isSystem,
813 isWebExtension: addon.isWebExtension,
814 multiprocessCompatible: true,
817 // getActiveAddons() gives limited data during startup and full
818 // data after the addons database is loaded.
820 let installDate = new Date(Math.max(0, addon.installDate));
821 Object.assign(activeAddons[addon.id], {
823 addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
824 description: limitStringToLength(
826 MAX_ADDON_STRING_LENGTH
828 name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
829 userDisabled: enforceBoolean(addon.userDisabled),
830 appDisabled: addon.appDisabled,
831 foreignInstall: enforceBoolean(addon.foreignInstall),
832 hasBinaryComponents: false,
833 installDay: Utils.millisecondsToDays(installDate.getTime()),
834 signedState: addon.signedState,
835 quarantineIgnoredByApp: enforceBoolean(
836 addon.quarantineIgnoredByApp
838 quarantineIgnoredByUser: enforceBoolean(
839 addon.quarantineIgnoredByUser
844 this._environment._log.error(
845 "_getActiveAddons - An addon was discarded due to an error",
856 * Get the currently active theme data in object form.
857 * @return Promise<object> containing the active theme data.
859 async _getActiveTheme() {
860 // Request themes, asynchronously.
861 let { addons: themes } = await AddonManager.getActiveAddons(["theme"]);
863 let activeTheme = {};
864 // We only store information about the active theme.
865 let theme = themes.find(theme => theme.isActive);
867 // Make sure to have valid dates.
868 let installDate = new Date(Math.max(0, theme.installDate));
869 let updateDate = new Date(Math.max(0, theme.updateDate));
874 theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
875 description: limitStringToLength(
877 MAX_ADDON_STRING_LENGTH
879 name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
880 userDisabled: enforceBoolean(theme.userDisabled),
881 appDisabled: theme.appDisabled,
882 version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
884 foreignInstall: enforceBoolean(theme.foreignInstall),
885 hasBinaryComponents: false,
886 installDay: Utils.millisecondsToDays(installDate.getTime()),
887 updateDay: Utils.millisecondsToDays(updateDate.getTime()),
895 * Get the GMPlugins data in object form.
897 * @return Object containing the GMPlugins data.
899 * This should only be called from _pendingTask; otherwise we risk
900 * running this during addon manager shutdown.
902 async _getActiveGMPlugins() {
903 // If we haven't yet loaded the blocklist, pass back dummy data for now,
904 // and add an observer to update this data as soon as we get it.
905 if (!AddonManager.hasProvider("GMPProvider")) {
906 if (!this._gmpProviderObserverAdded) {
907 Services.obs.addObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
908 this._gmpProviderObserverAdded = true;
914 applyBackgroundUpdates: 1,
918 // Request plugins, asynchronously.
919 let allPlugins = await AddonManager.getAddonsByTypes(["plugin"]);
921 let activeGMPlugins = {};
922 for (let plugin of allPlugins) {
923 // Only get info for active GMplugins.
924 if (!plugin.isGMPlugin || !plugin.isActive) {
929 activeGMPlugins[plugin.id] = {
930 version: plugin.version,
931 userDisabled: enforceBoolean(plugin.userDisabled),
932 applyBackgroundUpdates: plugin.applyBackgroundUpdates,
935 this._environment._log.error(
936 "_getActiveGMPlugins - A GMPlugin was discarded due to an error",
943 return activeGMPlugins;
947 function EnvironmentCache() {
948 this._log = Log.repository.getLoggerWithMessagePrefix(
950 "TelemetryEnvironment::"
952 this._log.trace("constructor");
954 this._shutdown = false;
955 // Don't allow querying the search service too early to prevent
956 // impacting the startup performance.
957 this._canQuerySearch = false;
958 // To guard against slowing down startup, defer gathering heavy environment
959 // entries until the session is restored.
960 this._sessionWasRestored = false;
962 // A map of listeners that will be called on environment changes.
963 this._changeListeners = new Map();
965 // A map of watched preferences which trigger an Environment change when
966 // modified. Every entry contains a recording policy (RECORD_PREF_*).
967 this._watchedPrefs = DEFAULT_ENVIRONMENT_PREFS;
969 this._currentEnvironment = {
970 build: this._getBuild(),
971 partner: this._getPartner(),
972 system: this._getSystem(),
975 this._addObservers();
977 // Build the remaining asynchronous parts of the environment. Don't register change listeners
978 // until the initial environment has been built.
980 let p = [this._updateSettings()];
981 this._addonBuilder = new EnvironmentAddonBuilder(this);
982 p.push(this._addonBuilder.init());
984 this._currentEnvironment.profile = {};
985 p.push(this._updateProfile());
986 if (AppConstants.MOZ_BUILD_APP == "browser") {
987 p.push(this._loadAttributionAsync());
989 p.push(this._loadAsyncUpdateSettings());
990 p.push(this._loadIntlData());
995 ] of gActiveExperimentStartupBuffer.entries()) {
996 this.setExperimentActive(id, branch, options);
998 gActiveExperimentStartupBuffer = null;
1001 this._initTask = null;
1002 this._startWatchingPrefs();
1003 this._addonBuilder.watchForChanges();
1004 this._updateGraphicsFeatures();
1005 return this.currentEnvironment;
1008 this._initTask = Promise.all(p).then(
1011 // log errors but eat them for consumers
1012 this._log.error("EnvironmentCache - error while initializing", err);
1017 // Addons may contain partial or full data depending on whether the Addons DB
1018 // has had a chance to load. Do we have full data yet?
1019 this._addonsAreFull = false;
1021 EnvironmentCache.prototype = {
1023 * The current environment data. The returned data is cloned to avoid
1024 * unexpected sharing or mutation.
1027 get currentEnvironment() {
1028 return Cu.cloneInto(this._currentEnvironment, {});
1032 * Wait for the current enviroment to be fully initialized.
1033 * @returns Promise<object>
1036 if (this._initTask) {
1037 return this._initTask;
1039 return Promise.resolve(this.currentEnvironment);
1043 * This gets called when the delayed init completes.
1045 async delayedInit() {
1046 this._processData = await Services.sysinfo.processInfo;
1047 let processData = await Services.sysinfo.processInfo;
1048 // Remove isWow64 and isWowARM64 from processData
1049 // to strip it down to just CPU info
1050 delete processData.isWow64;
1051 delete processData.isWowARM64;
1054 if (!this._initTask) {
1055 oldEnv = this.currentEnvironment;
1058 this._cpuData = this._getCPUData();
1059 // Augment the return value from the promises with cached values
1060 this._cpuData = { ...processData, ...this._cpuData };
1062 this._currentEnvironment.system.cpu = this._getCPUData();
1064 if (AppConstants.platform == "win") {
1065 this._hddData = await Services.sysinfo.diskInfo;
1066 let osData = await Services.sysinfo.osInfo;
1068 if (!this._initTask) {
1069 // We've finished creating the initial env, so notify for the update
1070 // This is all a bit awkward because `currentEnvironment` clones
1071 // the object, which we need to pass to the notification, but we
1072 // should only notify once we've updated the current environment...
1073 // Ideally, _onEnvironmentChange should somehow deal with all this
1074 // instead of all the consumers.
1075 oldEnv = this.currentEnvironment;
1078 this._osData = this._getOSData();
1080 // Augment the return values from the promises with cached values
1081 this._osData = Object.assign(osData, this._osData);
1083 this._currentEnvironment.system.os = this._getOSData();
1084 this._currentEnvironment.system.hdd = this._getHDDData();
1086 // Windows only values stored in processData
1087 this._currentEnvironment.system.isWow64 = this._getProcessData().isWow64;
1088 this._currentEnvironment.system.isWowARM64 =
1089 this._getProcessData().isWowARM64;
1092 if (!this._initTask) {
1093 this._onEnvironmentChange("system-info", oldEnv);
1098 * Register a listener for environment changes.
1099 * @param name The name of the listener. If a new listener is registered
1100 * with the same name, the old listener will be replaced.
1101 * @param listener function(reason, oldEnvironment) - Will receive a reason for
1102 the change and the environment data before the change.
1104 registerChangeListener(name, listener) {
1105 this._log.trace("registerChangeListener for " + name);
1106 if (this._shutdown) {
1107 this._log.warn("registerChangeListener - already shutdown");
1110 this._changeListeners.set(name, listener);
1114 * Unregister from listening to environment changes.
1115 * It's fine to call this on an unitialized TelemetryEnvironment.
1116 * @param name The name of the listener to remove.
1118 unregisterChangeListener(name) {
1119 this._log.trace("unregisterChangeListener for " + name);
1120 if (this._shutdown) {
1121 this._log.warn("registerChangeListener - already shutdown");
1124 this._changeListeners.delete(name);
1127 setExperimentActive(id, branch, options) {
1128 this._log.trace(`setExperimentActive - id: ${id}, branch: ${branch}`);
1129 // Make sure both the id and the branch have sane lengths.
1130 const saneId = limitStringToLength(id, MAX_EXPERIMENT_ID_LENGTH);
1131 const saneBranch = limitStringToLength(
1133 MAX_EXPERIMENT_BRANCH_LENGTH
1135 if (!saneId || !saneBranch) {
1137 "setExperimentActive - the provided arguments are not strings."
1142 // Warn the user about any content truncation.
1143 if (saneId.length != id.length || saneBranch.length != branch.length) {
1145 "setExperimentActive - the experiment id or branch were truncated."
1149 // Truncate the experiment type if present.
1150 if (options.hasOwnProperty("type")) {
1151 let type = limitStringToLength(options.type, MAX_EXPERIMENT_TYPE_LENGTH);
1152 if (type.length != options.type.length) {
1153 options.type = type;
1155 "setExperimentActive - the experiment type was truncated."
1160 // Truncate the enrollment id if present.
1161 if (options.hasOwnProperty("enrollmentId")) {
1162 let enrollmentId = limitStringToLength(
1163 options.enrollmentId,
1164 MAX_EXPERIMENT_ENROLLMENT_ID_LENGTH
1166 if (enrollmentId.length != options.enrollmentId.length) {
1167 options.enrollmentId = enrollmentId;
1169 "setExperimentActive - the enrollment id was truncated."
1174 let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
1175 // Add the experiment annotation.
1176 let experiments = this._currentEnvironment.experiments || {};
1177 experiments[saneId] = { branch: saneBranch };
1178 if (options.hasOwnProperty("type")) {
1179 experiments[saneId].type = options.type;
1181 if (options.hasOwnProperty("enrollmentId")) {
1182 experiments[saneId].enrollmentId = options.enrollmentId;
1184 this._currentEnvironment.experiments = experiments;
1185 // Notify of the change.
1186 this._onEnvironmentChange("experiment-annotation-changed", oldEnvironment);
1189 setExperimentInactive(id) {
1190 this._log.trace("setExperimentInactive");
1191 let experiments = this._currentEnvironment.experiments || {};
1192 if (id in experiments) {
1193 // Only attempt to notify if a previous annotation was found and removed.
1194 let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
1195 // Remove the experiment annotation.
1196 delete this._currentEnvironment.experiments[id];
1197 // Notify of the change.
1198 this._onEnvironmentChange(
1199 "experiment-annotation-changed",
1205 getActiveExperiments() {
1206 return Cu.cloneInto(this._currentEnvironment.experiments || {}, {});
1210 this._log.trace("shutdown");
1211 this._shutdown = true;
1215 * Only used in tests, set the preferences to watch.
1216 * @param aPreferences A map of preferences names and their recording policy.
1218 _watchPreferences(aPreferences) {
1219 this._stopWatchingPrefs();
1220 this._watchedPrefs = aPreferences;
1221 this._updateSettings();
1222 this._startWatchingPrefs();
1226 * Get an object containing the values for the watched preferences. Depending on the
1227 * policy, the value for a preference or whether it was changed by user is reported.
1229 * @return An object containing the preferences values.
1233 for (let [pref, policy] of this._watchedPrefs.entries()) {
1234 let prefValue = this._getPrefValue(pref, policy.what);
1236 if (prefValue === undefined) {
1240 prefData[pref] = prefValue;
1246 * Get the value of a preference given the preference name and the policy.
1247 * @param pref Name of the preference.
1248 * @param what Policy of the preference.
1250 * @returns The value we need to store for this preference. It can be undefined
1251 * or null if the preference is invalid or has a value set by the user.
1253 _getPrefValue(pref, what) {
1254 // Check the policy for the preference and decide if we need to store its value
1255 // or whether it changed from the default value.
1256 let prefType = Services.prefs.getPrefType(pref);
1259 what == TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE ||
1260 what == TelemetryEnvironment.RECORD_DEFAULTPREF_STATE
1262 // For default prefs, make sure they exist
1263 if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
1266 } else if (!Services.prefs.prefHasUserValue(pref)) {
1267 // For user prefs, make sure they are set
1271 if (what == TelemetryEnvironment.RECORD_DEFAULTPREF_STATE) {
1273 } else if (what == TelemetryEnvironment.RECORD_PREF_STATE) {
1274 return "<user-set>";
1275 } else if (prefType == Ci.nsIPrefBranch.PREF_STRING) {
1276 return Services.prefs.getStringPref(pref);
1277 } else if (prefType == Ci.nsIPrefBranch.PREF_BOOL) {
1278 return Services.prefs.getBoolPref(pref);
1279 } else if (prefType == Ci.nsIPrefBranch.PREF_INT) {
1280 return Services.prefs.getIntPref(pref);
1281 } else if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
1285 `Unexpected preference type ("${prefType}") for "${pref}".`
1289 QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
1292 * Start watching the preferences.
1294 _startWatchingPrefs() {
1295 this._log.trace("_startWatchingPrefs - " + this._watchedPrefs);
1297 Services.prefs.addObserver("", this, true);
1300 _onPrefChanged(aData) {
1301 this._log.trace("_onPrefChanged");
1302 let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
1303 this._currentEnvironment.settings.userPrefs[aData] = this._getPrefValue(
1305 this._watchedPrefs.get(aData).what
1307 this._onEnvironmentChange("pref-changed", oldEnvironment);
1311 * Do not receive any more change notifications for the preferences.
1313 _stopWatchingPrefs() {
1314 this._log.trace("_stopWatchingPrefs");
1316 Services.prefs.removeObserver("", this);
1320 // Watch the search engine change and service topics.
1321 Services.obs.addObserver(this, SESSIONSTORE_WINDOWS_RESTORED_TOPIC);
1322 Services.obs.addObserver(this, COMPOSITOR_CREATED_TOPIC);
1323 Services.obs.addObserver(this, COMPOSITOR_PROCESS_ABORTED_TOPIC);
1324 Services.obs.addObserver(this, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
1325 Services.obs.addObserver(this, GFX_FEATURES_READY_TOPIC);
1326 Services.obs.addObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
1327 Services.obs.addObserver(this, SEARCH_SERVICE_TOPIC);
1328 Services.obs.addObserver(this, AUTO_UPDATE_PREF_CHANGE_TOPIC);
1329 Services.obs.addObserver(this, BACKGROUND_UPDATE_PREF_CHANGE_TOPIC);
1330 Services.obs.addObserver(this, SERVICES_INFO_CHANGE_TOPIC);
1333 _removeObservers() {
1334 Services.obs.removeObserver(this, SESSIONSTORE_WINDOWS_RESTORED_TOPIC);
1335 Services.obs.removeObserver(this, COMPOSITOR_CREATED_TOPIC);
1336 Services.obs.removeObserver(this, COMPOSITOR_PROCESS_ABORTED_TOPIC);
1338 Services.obs.removeObserver(
1340 DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC
1343 Services.obs.removeObserver(this, GFX_FEATURES_READY_TOPIC);
1344 Services.obs.removeObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
1345 Services.obs.removeObserver(this, SEARCH_SERVICE_TOPIC);
1346 Services.obs.removeObserver(this, AUTO_UPDATE_PREF_CHANGE_TOPIC);
1347 Services.obs.removeObserver(this, BACKGROUND_UPDATE_PREF_CHANGE_TOPIC);
1348 Services.obs.removeObserver(this, SERVICES_INFO_CHANGE_TOPIC);
1351 observe(aSubject, aTopic, aData) {
1352 this._log.trace("observe - aTopic: " + aTopic + ", aData: " + aData);
1354 case SEARCH_ENGINE_MODIFIED_TOPIC:
1356 aData != "engine-default" &&
1357 aData != "engine-default-private" &&
1358 aData != "engine-changed"
1363 aData == "engine-changed" &&
1364 aSubject.QueryInterface(Ci.nsISearchEngine) &&
1365 Services.search.defaultEngine != aSubject
1369 // Record the new default search choice and send the change notification.
1370 this._onSearchEngineChange();
1372 case SEARCH_SERVICE_TOPIC:
1373 if (aData != "init-complete") {
1376 // Now that the search engine init is complete, record the default search choice.
1377 this._canQuerySearch = true;
1378 this._updateSearchEngine();
1380 case GFX_FEATURES_READY_TOPIC:
1381 case COMPOSITOR_CREATED_TOPIC:
1382 // Full graphics information is not available until we have created at
1383 // least one off-main-thread-composited window. Thus we wait for the
1384 // first compositor to be created and then query nsIGfxInfo again.
1385 this._updateGraphicsFeatures();
1387 case COMPOSITOR_PROCESS_ABORTED_TOPIC:
1388 // Our compositor process has been killed for whatever reason, so refresh
1389 // our reported graphics features and trigger an environment change.
1390 this._onCompositorProcessAborted();
1392 case DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC:
1393 // Distribution customizations are applied after final-ui-startup. query
1394 // partner prefs again when they are ready.
1395 this._updatePartner();
1396 Services.obs.removeObserver(this, aTopic);
1398 case SESSIONSTORE_WINDOWS_RESTORED_TOPIC:
1399 this._sessionWasRestored = true;
1400 // Make sure to initialize the search service once we've done restoring
1401 // the windows, so that we don't risk loosing search data.
1402 Services.search.init();
1403 // The default browser check could take some time, so just call it after
1404 // the session was restored.
1405 this._updateDefaultBrowser();
1407 case PREF_CHANGED_TOPIC:
1408 let options = this._watchedPrefs.get(aData);
1409 if (options && !options.requiresRestart) {
1410 this._onPrefChanged(aData);
1413 case AUTO_UPDATE_PREF_CHANGE_TOPIC:
1414 this._currentEnvironment.settings.update.autoDownload = aData == "true";
1416 case BACKGROUND_UPDATE_PREF_CHANGE_TOPIC:
1417 this._currentEnvironment.settings.update.background = aData == "true";
1419 case SERVICES_INFO_CHANGE_TOPIC:
1420 this._updateServicesInfo();
1426 * Update the default search engine value.
1428 _updateSearchEngine() {
1429 if (!this._canQuerySearch) {
1430 this._log.trace("_updateSearchEngine - ignoring early call");
1435 "_updateSearchEngine - isInitialized: " + Services.search.isInitialized
1437 if (!Services.search.isInitialized) {
1441 // Make sure we have a settings section.
1442 this._currentEnvironment.settings = this._currentEnvironment.settings || {};
1444 // Update the search engine entry in the current environment.
1445 const defaultEngineInfo = Services.search.getDefaultEngineInfo();
1446 this._currentEnvironment.settings.defaultSearchEngine =
1447 defaultEngineInfo.defaultSearchEngine;
1448 this._currentEnvironment.settings.defaultSearchEngineData = {
1449 ...defaultEngineInfo.defaultSearchEngineData,
1451 if ("defaultPrivateSearchEngine" in defaultEngineInfo) {
1452 this._currentEnvironment.settings.defaultPrivateSearchEngine =
1453 defaultEngineInfo.defaultPrivateSearchEngine;
1455 if ("defaultPrivateSearchEngineData" in defaultEngineInfo) {
1456 this._currentEnvironment.settings.defaultPrivateSearchEngineData = {
1457 ...defaultEngineInfo.defaultPrivateSearchEngineData,
1463 * Update the default search engine value and trigger the environment change.
1465 _onSearchEngineChange() {
1466 this._log.trace("_onSearchEngineChange");
1468 // Finally trigger the environment change notification.
1469 let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
1470 this._updateSearchEngine();
1471 this._onEnvironmentChange("search-engine-changed", oldEnvironment);
1475 * Refresh the Telemetry environment and trigger an environment change due to
1476 * a change in compositor process (normally this will mean we've fallen back
1477 * from out-of-process to in-process compositing).
1479 _onCompositorProcessAborted() {
1480 this._log.trace("_onCompositorProcessAborted");
1482 // Trigger the environment change notification.
1483 let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
1484 this._updateGraphicsFeatures();
1485 this._onEnvironmentChange("gfx-features-changed", oldEnvironment);
1489 * Update the graphics features object.
1491 _updateGraphicsFeatures() {
1492 let gfxData = this._currentEnvironment.system.gfx;
1494 let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
1495 gfxData.features = gfxInfo.getFeatures();
1497 this._log.error("nsIGfxInfo.getFeatures() caught error", e);
1502 * Update the partner prefs.
1505 this._currentEnvironment.partner = this._getPartner();
1509 * Get the build data in object form.
1510 * @return Object containing the build data.
1514 applicationId: Services.appinfo.ID || null,
1515 applicationName: Services.appinfo.name || null,
1516 architecture: Services.sysinfo.get("arch"),
1517 buildId: Services.appinfo.appBuildID || null,
1518 version: Services.appinfo.version || null,
1519 vendor: Services.appinfo.vendor || null,
1520 displayVersion: AppConstants.MOZ_APP_VERSION_DISPLAY || null,
1521 platformVersion: Services.appinfo.platformVersion || null,
1522 xpcomAbi: Services.appinfo.XPCOMABI,
1523 updaterAvailable: AppConstants.MOZ_UPDATER,
1530 * Determine if we're the default browser.
1531 * @returns null on error, true if we are the default browser, or false otherwise.
1533 _isDefaultBrowser() {
1534 let isDefault = (service, ...args) => {
1536 return !!service.isDefaultBrowser(...args);
1539 "_isDefaultBrowser - Could not determine if default browser",
1546 if (!("@mozilla.org/browser/shell-service;1" in Cc)) {
1548 "_isDefaultBrowser - Could not obtain browser shell service"
1554 let { ShellService } = ChromeUtils.importESModule(
1555 "resource:///modules/ShellService.sys.mjs"
1557 // This uses the same set of flags used by the pref pane.
1558 return isDefault(ShellService, false, true);
1560 this._log.error("_isDefaultBrowser - Could not obtain shell service JSM");
1564 let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
1567 // This uses the same set of flags used by the pref pane.
1568 return isDefault(shellService, true);
1570 this._log.error("_isDefaultBrowser - Could not obtain shell service", ex);
1575 _updateDefaultBrowser() {
1576 if (AppConstants.platform === "android") {
1579 // Make sure to have a settings section.
1580 this._currentEnvironment.settings = this._currentEnvironment.settings || {};
1581 this._currentEnvironment.settings.isDefaultBrowser = this
1582 ._sessionWasRestored
1583 ? this._isDefaultBrowser()
1588 * Update the cached settings data.
1591 let updateChannel = null;
1593 updateChannel = Utils.getUpdateChannel();
1596 this._currentEnvironment.settings = {
1597 blocklistEnabled: Services.prefs.getBoolPref(
1598 PREF_BLOCKLIST_ENABLED,
1601 e10sEnabled: Services.appinfo.browserTabsRemoteAutostart,
1602 e10sMultiProcesses: Services.appinfo.maxWebProcessCount,
1603 fissionEnabled: Services.appinfo.fissionAutostart,
1604 telemetryEnabled: Utils.isTelemetryEnabled,
1605 locale: getBrowserLocale(),
1606 // We need to wait for browser-delayed-startup-finished to ensure that the locales
1607 // have settled, once that's happened we can get the intl data directly.
1608 intl: Policy._intlLoaded ? getIntlSettings() : {},
1610 channel: updateChannel,
1611 enabled: !Services.policies || Services.policies.isAllowed("appUpdate"),
1613 userPrefs: this._getPrefData(),
1614 sandbox: this._getSandboxData(),
1617 // Services.appinfo.launcherProcessState is not available in all build
1618 // configurations, in which case an exception may be thrown.
1620 this._currentEnvironment.settings.launcherProcessState =
1621 Services.appinfo.launcherProcessState;
1624 this._currentEnvironment.settings.addonCompatibilityCheckEnabled =
1625 AddonManager.checkCompatibility;
1627 if (AppConstants.MOZ_BUILD_APP == "browser") {
1628 this._updateAttribution();
1630 this._updateDefaultBrowser();
1631 this._updateSearchEngine();
1632 this._loadAsyncUpdateSettingsFromCache();
1636 let effectiveContentProcessLevel = null;
1637 let contentWin32kLockdownState = null;
1639 let sandboxSettings = Cc[
1640 "@mozilla.org/sandbox/sandbox-settings;1"
1641 ].getService(Ci.mozISandboxSettings);
1642 effectiveContentProcessLevel =
1643 sandboxSettings.effectiveContentSandboxLevel;
1645 // The possible values for this are defined in the ContentWin32kLockdownState
1646 // enum in security/sandbox/common/SandboxSettings.h
1647 contentWin32kLockdownState = sandboxSettings.contentWin32kLockdownState;
1650 effectiveContentProcessLevel,
1651 contentWin32kLockdownState,
1656 * Update the cached profile data.
1657 * @returns Promise<> resolved when the I/O is complete.
1659 async _updateProfile() {
1660 let profileAccessor = await lazy.ProfileAge();
1662 let creationDate = await profileAccessor.created;
1663 let resetDate = await profileAccessor.reset;
1664 let firstUseDate = await profileAccessor.firstUse;
1666 this._currentEnvironment.profile.creationDate =
1667 Utils.millisecondsToDays(creationDate);
1669 this._currentEnvironment.profile.resetDate =
1670 Utils.millisecondsToDays(resetDate);
1673 this._currentEnvironment.profile.firstUseDate =
1674 Utils.millisecondsToDays(firstUseDate);
1679 * Load the attribution data object and updates the environment.
1680 * @returns Promise<> resolved when the I/O is complete.
1682 async _loadAttributionAsync() {
1684 await lazy.AttributionCode.getAttrDataAsync();
1686 // The AttributionCode.sys.mjs module might not be always available
1687 // (e.g. tests). Gracefully handle this.
1690 this._updateAttribution();
1694 * Update the environment with the cached attribution data.
1696 _updateAttribution() {
1699 data = lazy.AttributionCode.getCachedAttributionData();
1701 // The AttributionCode.sys.mjs module might not be always available
1702 // (e.g. tests). Gracefully handle this.
1705 if (!data || !Object.keys(data).length) {
1709 let attributionData = {};
1710 for (let key in data) {
1711 attributionData[key] =
1712 // At least one of these may be boolean, and limitStringToLength
1713 // returns null for non-string inputs.
1714 typeof data[key] === "string"
1715 ? limitStringToLength(data[key], MAX_ATTRIBUTION_STRING_LENGTH)
1718 this._currentEnvironment.settings.attribution = attributionData;
1722 * Load the per-installation update settings, cache them, and add them to the
1725 async _loadAsyncUpdateSettings() {
1726 if (AppConstants.MOZ_UPDATER) {
1727 this._updateAutoDownloadCache =
1728 await UpdateUtils.getAppUpdateAutoEnabled();
1729 this._updateBackgroundCache = await UpdateUtils.readUpdateConfigSetting(
1730 "app.update.background.enabled"
1733 this._updateAutoDownloadCache = false;
1734 this._updateBackgroundCache = false;
1736 this._loadAsyncUpdateSettingsFromCache();
1740 * Update the environment with the cached values for per-installation update
1743 _loadAsyncUpdateSettingsFromCache() {
1744 if (this._updateAutoDownloadCache !== undefined) {
1745 this._currentEnvironment.settings.update.autoDownload =
1746 this._updateAutoDownloadCache;
1748 if (this._updateBackgroundCache !== undefined) {
1749 this._currentEnvironment.settings.update.background =
1750 this._updateBackgroundCache;
1755 * Get i18n data about the system.
1756 * @return A promise of completion.
1758 async _loadIntlData() {
1759 // Wait for the startup topic.
1760 await Policy._browserDelayedStartup();
1761 this._currentEnvironment.settings.intl = getIntlSettings();
1762 Policy._intlLoaded = true;
1764 // This exists as a separate function for testing.
1765 async _getFxaSignedInUser() {
1766 return lazy.fxAccounts.getSignedInUser();
1769 async _updateServicesInfo() {
1770 let syncEnabled = false;
1771 let accountEnabled = false;
1773 Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject;
1774 syncEnabled = weaveService && weaveService.enabled;
1776 // All sync users are account users, definitely.
1777 accountEnabled = true;
1779 // Not all account users are sync users. See if they're signed into FxA.
1781 let user = await this._getFxaSignedInUser();
1783 accountEnabled = true;
1786 // We don't know. This might be a transient issue which will clear
1787 // itself up later, but the information in telemetry is quite possibly stale
1788 // (this is called from a change listener), so clear it out to avoid
1789 // reporting data which might be wrong until we can figure it out.
1790 delete this._currentEnvironment.services;
1791 this._log.error("_updateServicesInfo() caught error", e);
1795 this._currentEnvironment.services = {
1802 * Get the partner data in object form.
1803 * @return Object containing the partner data.
1806 let defaults = Services.prefs.getDefaultBranch(null);
1808 distributionId: defaults.getStringPref(PREF_DISTRIBUTION_ID, null),
1809 distributionVersion: defaults.getCharPref(
1810 PREF_DISTRIBUTION_VERSION,
1813 partnerId: defaults.getCharPref(PREF_PARTNER_ID, null),
1814 distributor: defaults.getCharPref(PREF_DISTRIBUTOR, null),
1815 distributorChannel: defaults.getCharPref(PREF_DISTRIBUTOR_CHANNEL, null),
1818 // Get the PREF_APP_PARTNER_BRANCH branch and append its children to partner data.
1819 let partnerBranch = Services.prefs.getDefaultBranch(
1820 PREF_APP_PARTNER_BRANCH
1822 partnerData.partnerNames = partnerBranch.getChildList("");
1829 * Get the CPU information.
1830 * @return Object containing the CPU information data.
1833 if (this._cpuData) {
1834 return this._cpuData;
1839 const CPU_EXTENSIONS = [
1858 // Enumerate the available CPU extensions.
1859 let availableExts = [];
1860 for (let ext of CPU_EXTENSIONS) {
1861 if (getSysinfoProperty(ext, false)) {
1862 availableExts.push(ext);
1866 this._cpuData.extensions = availableExts;
1868 return this._cpuData;
1873 * Get the process information.
1874 * @return Object containing the process information data.
1877 if (this._processData) {
1878 return this._processData;
1884 * Get the device information, if we are on a portable device.
1885 * @return Object containing the device information data, or null if
1886 * not a portable device.
1889 if (AppConstants.platform !== "android") {
1894 model: getSysinfoProperty("device", null),
1895 manufacturer: getSysinfoProperty("manufacturer", null),
1896 hardware: getSysinfoProperty("hardware", null),
1897 isTablet: getSysinfoProperty("tablet", null),
1903 * Get the OS information.
1904 * @return Object containing the OS data.
1908 return this._osData;
1911 name: forceToStringOrNull(getSysinfoProperty("name", null)),
1912 version: forceToStringOrNull(getSysinfoProperty("version", null)),
1913 locale: forceToStringOrNull(getSystemLocale()),
1916 if (AppConstants.platform == "android") {
1917 this._osData.kernelVersion = forceToStringOrNull(
1918 getSysinfoProperty("kernel_version", null)
1920 } else if (AppConstants.platform === "win") {
1921 // The path to the "UBR" key, queried to get additional version details on Windows.
1922 const WINDOWS_UBR_KEY_PATH =
1923 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
1925 let versionInfo = lazy.WindowsVersionInfo.get({ throwOnError: false });
1926 this._osData.servicePackMajor = versionInfo.servicePackMajor;
1927 this._osData.servicePackMinor = versionInfo.servicePackMinor;
1928 this._osData.windowsBuildNumber = versionInfo.buildNumber;
1929 // We only need the UBR if we're at or above Windows 10.
1931 typeof this._osData.version === "string" &&
1932 Services.vc.compare(this._osData.version, "10") >= 0
1934 // Query the UBR key and only add it to the environment if it's available.
1935 // |readRegKey| doesn't throw, but rather returns 'undefined' on error.
1936 let ubr = lazy.WindowsRegistry.readRegKey(
1937 Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
1938 WINDOWS_UBR_KEY_PATH,
1940 Ci.nsIWindowsRegKey.WOW64_64
1942 this._osData.windowsUBR = ubr !== undefined ? ubr : null;
1946 return this._osData;
1951 * Get the HDD information.
1952 * @return Object containing the HDD data.
1955 if (this._hddData) {
1956 return this._hddData;
1958 let nullData = { model: null, revision: null, type: null };
1959 return { profile: nullData, binary: nullData, system: nullData };
1963 * Get registered security product information.
1964 * @return Object containing the security product data
1966 _getSecurityAppData() {
1967 const maxStringLength = 256;
1970 ["registeredAntiVirus", "antivirus"],
1971 ["registeredAntiSpyware", "antispyware"],
1972 ["registeredFirewall", "firewall"],
1977 for (let [inKey, outKey] of keys) {
1978 let prop = getSysinfoProperty(inKey, null);
1980 prop = limitStringToLength(prop, maxStringLength).split(";");
1983 result[outKey] = prop;
1990 * Get the GFX information.
1991 * @return Object containing the GFX data.
1995 D2DEnabled: getGfxField("D2DEnabled", null),
1996 DWriteEnabled: getGfxField("DWriteEnabled", null),
1997 ContentBackend: getGfxField("ContentBackend", null),
1998 Headless: getGfxField("isHeadless", null),
1999 EmbeddedInFirefoxReality: getGfxField("EmbeddedInFirefoxReality", null),
2000 TargetFrameRate: getGfxField("TargetFrameRate", null),
2001 // The following line is disabled due to main thread jank and will be enabled
2002 // again as part of bug 1154500.
2003 // DWriteVersion: getGfxField("DWriteVersion", null),
2009 if (AppConstants.platform !== "android") {
2010 let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
2012 gfxData.monitors = gfxInfo.getMonitors();
2014 this._log.error("nsIGfxInfo.getMonitors() caught error", e);
2019 let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
2020 gfxData.features = gfxInfo.getFeatures();
2022 this._log.error("nsIGfxInfo.getFeatures() caught error", e);
2025 // GfxInfo does not yet expose a way to iterate through all the adapters.
2026 gfxData.adapters.push(getGfxAdapter(""));
2027 gfxData.adapters[0].GPUActive = true;
2029 // If we have a second adapter add it to the gfxData.adapters section.
2030 let hasGPU2 = getGfxField("adapterDeviceID2", null) !== null;
2032 this._log.trace("_getGFXData - Only one display adapter detected.");
2036 this._log.trace("_getGFXData - Two display adapters detected.");
2038 gfxData.adapters.push(getGfxAdapter("2"));
2039 gfxData.adapters[1].GPUActive = getGfxField("isGPU2Active", null);
2045 * Get the system data in object form.
2046 * @return Object containing the system data.
2049 let memoryMB = getSysinfoProperty("memsize", null);
2051 // Send RAM size in megabytes. Rounding because sysinfo doesn't
2052 // always provide RAM in multiples of 1024.
2053 memoryMB = Math.round(memoryMB / 1024 / 1024);
2056 let virtualMB = getSysinfoProperty("virtualmemsize", null);
2058 // Send the total virtual memory size in megabytes. Rounding because
2059 // sysinfo doesn't always provide RAM in multiples of 1024.
2060 virtualMB = Math.round(virtualMB / 1024 / 1024);
2065 virtualMaxMB: virtualMB,
2066 cpu: this._getCPUData(),
2067 os: this._getOSData(),
2068 hdd: this._getHDDData(),
2069 gfx: this._getGFXData(),
2070 appleModelId: getSysinfoProperty("appleModelId", null),
2071 hasWinPackageId: getSysinfoProperty("hasWinPackageId", null),
2074 if (AppConstants.platform === "win") {
2075 // This is only sent for Mozilla produced MSIX packages
2076 let winPackageFamilyName = getSysinfoProperty("winPackageFamilyName", "");
2078 winPackageFamilyName.startsWith("Mozilla.") ||
2079 winPackageFamilyName.startsWith("MozillaCorporation.")
2081 data = { winPackageFamilyName, ...data };
2083 data = { ...this._getProcessData(), ...data };
2084 } else if (AppConstants.platform == "android") {
2085 data.device = this._getDeviceData();
2089 if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
2090 data.sec = this._getSecurityAppData();
2096 _onEnvironmentChange(what, oldEnvironment) {
2097 this._log.trace("_onEnvironmentChange for " + what);
2099 // We are already skipping change events in _checkChanges if there is a pending change task running.
2100 if (this._shutdown) {
2101 this._log.trace("_onEnvironmentChange - Already shut down.");
2105 if (ObjectUtils.deepEqual(this._currentEnvironment, oldEnvironment)) {
2106 this._log.trace("_onEnvironmentChange - Environment didn't change");
2110 for (let [name, listener] of this._changeListeners) {
2112 this._log.debug("_onEnvironmentChange - calling " + name);
2113 listener(what, oldEnvironment);
2116 "_onEnvironmentChange - listener " + name + " caught error",
2124 this._shutdown = false;