498 lines
17 KiB
JavaScript
498 lines
17 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
|
|
|
let lazy = {};
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
BrowserInitState: "resource:///modules/BrowserGlue.sys.mjs",
|
|
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
|
|
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
|
|
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
|
|
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
|
OsEnvironment: "resource://gre/modules/OsEnvironment.sys.mjs",
|
|
PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
|
|
ShellService: "resource:///modules/ShellService.sys.mjs",
|
|
TelemetryReportingPolicy:
|
|
"resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
|
|
UsageReporting: "resource://gre/modules/UsageReporting.sys.mjs",
|
|
});
|
|
|
|
/**
|
|
* Used to collect various bits of telemetry during browser startup.
|
|
*
|
|
*/
|
|
export let StartupTelemetry = {
|
|
// Some tasks are expensive because they involve significant disk IO, and
|
|
// may also write information to disk. If we submit the telemetry that may
|
|
// happen anyway, but if we don't then this is undesirable, so those tasks are
|
|
// only run if we will submit the results.
|
|
// Why run any telemetry code at all if we don't submit the data? Because
|
|
// local and autoland builds usually do not submit telemetry, but we still
|
|
// want to be able to run automated tests to check the code _worked_.
|
|
get _willUseExpensiveTelemetry() {
|
|
return (
|
|
AppConstants.MOZ_TELEMETRY_REPORTING &&
|
|
Services.prefs.getBoolPref(
|
|
"datareporting.healthreport.uploadEnabled",
|
|
false
|
|
)
|
|
);
|
|
},
|
|
|
|
_runIdleTasks(tasks, profilerMarker) {
|
|
for (let task of tasks) {
|
|
ChromeUtils.idleDispatch(async () => {
|
|
if (!Services.startup.shuttingDown) {
|
|
let startTime = Cu.now();
|
|
try {
|
|
await task();
|
|
} catch (ex) {
|
|
console.error(ex);
|
|
} finally {
|
|
ChromeUtils.addProfilerMarker(
|
|
profilerMarker,
|
|
startTime,
|
|
task.toSource()
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
browserIdleStartup() {
|
|
let tasks = [
|
|
// FOG doesn't need to be initialized _too_ early because it has a pre-init buffer.
|
|
() => this.initFOG(),
|
|
|
|
() => this.contentBlocking(),
|
|
() => this.dataSanitization(),
|
|
() => this.pipEnabled(),
|
|
() => this.sslKeylogFile(),
|
|
() => this.osAuthEnabled(),
|
|
() => this.startupConditions(),
|
|
() => this.httpsOnlyState(),
|
|
() => this.globalPrivacyControl(),
|
|
];
|
|
if (this._willUseExpensiveTelemetry) {
|
|
tasks.push(() => lazy.PlacesDBUtils.telemetry());
|
|
}
|
|
if (AppConstants.platform == "win") {
|
|
tasks.push(
|
|
() => this.pinningStatus(),
|
|
() => this.isDefaultHandler()
|
|
);
|
|
} else if (AppConstants.platform == "macosx") {
|
|
tasks.push(() => this.macDockStatus());
|
|
}
|
|
|
|
this._runIdleTasks(tasks, "startupTelemetryIdleTask");
|
|
},
|
|
|
|
/**
|
|
* Use this function as an entry point to collect telemetry that we hope
|
|
* to collect once per session, at any arbitrary point in time, and
|
|
*
|
|
* **which we are okay with sometimes not running at all.**
|
|
*
|
|
* See BrowserGlue.sys.mjs's _scheduleBestEffortUserIdleTasks for more
|
|
* details.
|
|
*/
|
|
bestEffortIdleStartup() {
|
|
let tasks = [
|
|
() => this.primaryPasswordEnabled(),
|
|
() => this.trustObjectCount(),
|
|
() => lazy.OsEnvironment.reportAllowedAppSources(),
|
|
];
|
|
if (AppConstants.platform == "win" && this._willUseExpensiveTelemetry) {
|
|
tasks.push(
|
|
() => lazy.BrowserUsageTelemetry.reportProfileCount(),
|
|
() => lazy.BrowserUsageTelemetry.reportInstallationTelemetry()
|
|
);
|
|
}
|
|
this._runIdleTasks(tasks, "startupTelemetryLateIdleTask");
|
|
},
|
|
|
|
/**
|
|
* Initialize Firefox-on-Glean.
|
|
*
|
|
* This is at the top because it's a bit different from the other code here
|
|
* which is strictly collecting specific metrics.
|
|
*/
|
|
async initFOG() {
|
|
// Handle Usage Profile ID. Similar logic to what's happening in
|
|
// `TelemetryControllerParent` for the client ID. Must be done before
|
|
// initializing FOG so that ping enabled/disabled states are correct
|
|
// before Glean takes actions.
|
|
await lazy.UsageReporting.ensureInitialized();
|
|
|
|
// If needed, delay initializing FOG until policy interaction is
|
|
// completed. See comments in `TelemetryReportingPolicy`.
|
|
await lazy.TelemetryReportingPolicy.ensureUserIsNotified();
|
|
|
|
Services.fog.initializeFOG();
|
|
|
|
// Register Glean to listen for experiment updates releated to the
|
|
// "gleanInternalSdk" feature defined in the t/c/nimbus/FeatureManifest.yaml
|
|
// This feature is intended for internal Glean use only. For features wishing
|
|
// to set a remote metric configuration, please use the "glean" feature for
|
|
// the purpose of setting the data-control-plane features via Server Knobs.
|
|
lazy.NimbusFeatures.gleanInternalSdk.onUpdate(() => {
|
|
let cfg = lazy.NimbusFeatures.gleanInternalSdk.getVariable(
|
|
"gleanMetricConfiguration"
|
|
);
|
|
Services.fog.applyServerKnobsConfig(JSON.stringify(cfg));
|
|
});
|
|
|
|
// Register Glean to listen for experiment updates releated to the
|
|
// "glean" feature defined in the t/c/nimbus/FeatureManifest.yaml
|
|
lazy.NimbusFeatures.glean.onUpdate(() => {
|
|
const enrollments = lazy.NimbusFeatures.glean.getAllEnrollments();
|
|
for (const enrollment of enrollments) {
|
|
const cfg = enrollment.value.gleanMetricConfiguration;
|
|
if (typeof cfg === "object" && cfg !== null) {
|
|
Services.fog.applyServerKnobsConfig(JSON.stringify(cfg));
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
startupConditions() {
|
|
let nowSeconds = Math.round(Date.now() / 1000);
|
|
// Don't include cases where we don't have the pref. This rules out the first install
|
|
// as well as the first run of a build since this was introduced. These could by some
|
|
// definitions be referred to as "cold" startups, but probably not since we likely
|
|
// just wrote many of the files we use to disk. This way we should approximate a lower
|
|
// bound to the number of cold startups rather than an upper bound.
|
|
let lastCheckSeconds = Services.prefs.getIntPref(
|
|
"browser.startup.lastColdStartupCheck",
|
|
nowSeconds
|
|
);
|
|
Services.prefs.setIntPref(
|
|
"browser.startup.lastColdStartupCheck",
|
|
nowSeconds
|
|
);
|
|
try {
|
|
let secondsSinceLastOSRestart =
|
|
Services.startup.secondsSinceLastOSRestart;
|
|
let isColdStartup =
|
|
nowSeconds - secondsSinceLastOSRestart > lastCheckSeconds;
|
|
Glean.startup.isCold.set(isColdStartup);
|
|
Glean.startup.secondsSinceLastOsRestart.set(secondsSinceLastOSRestart);
|
|
} catch (ex) {
|
|
if (ex.name !== "NS_ERROR_NOT_IMPLEMENTED") {
|
|
console.error(ex);
|
|
}
|
|
}
|
|
},
|
|
|
|
contentBlocking() {
|
|
let tpEnabled = Services.prefs.getBoolPref(
|
|
"privacy.trackingprotection.enabled"
|
|
);
|
|
Glean.contentblocking.trackingProtectionEnabled[
|
|
tpEnabled ? "true" : "false"
|
|
].add();
|
|
|
|
let tpPBEnabled = Services.prefs.getBoolPref(
|
|
"privacy.trackingprotection.pbmode.enabled"
|
|
);
|
|
Glean.contentblocking.trackingProtectionPbmDisabled[
|
|
!tpPBEnabled ? "true" : "false"
|
|
].add();
|
|
|
|
let cookieBehavior = Services.prefs.getIntPref(
|
|
"network.cookie.cookieBehavior"
|
|
);
|
|
Glean.contentblocking.cookieBehavior.accumulateSingleSample(cookieBehavior);
|
|
|
|
let fpEnabled = Services.prefs.getBoolPref(
|
|
"privacy.trackingprotection.fingerprinting.enabled"
|
|
);
|
|
let cmEnabled = Services.prefs.getBoolPref(
|
|
"privacy.trackingprotection.cryptomining.enabled"
|
|
);
|
|
let categoryPref;
|
|
switch (
|
|
Services.prefs.getStringPref("browser.contentblocking.category", null)
|
|
) {
|
|
case "standard":
|
|
categoryPref = 0;
|
|
break;
|
|
case "strict":
|
|
categoryPref = 1;
|
|
break;
|
|
case "custom":
|
|
categoryPref = 2;
|
|
break;
|
|
default:
|
|
// Any other value is unsupported.
|
|
categoryPref = 3;
|
|
break;
|
|
}
|
|
|
|
Glean.contentblocking.fingerprintingBlockingEnabled.set(fpEnabled);
|
|
Glean.contentblocking.cryptominingBlockingEnabled.set(cmEnabled);
|
|
Glean.contentblocking.category.set(categoryPref);
|
|
},
|
|
|
|
dataSanitization() {
|
|
Glean.datasanitization.privacySanitizeSanitizeOnShutdown.set(
|
|
Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownCookies.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownHistory.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.history")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownFormdata.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.formdata")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownDownloads.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownCache.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.cache")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownSessions.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownOfflineApps.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownSiteSettings.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.siteSettings")
|
|
);
|
|
Glean.datasanitization.privacyClearOnShutdownOpenWindows.set(
|
|
Services.prefs.getBoolPref("privacy.clearOnShutdown.openWindows")
|
|
);
|
|
|
|
let exceptions = 0;
|
|
for (let permission of Services.perms.all) {
|
|
// We consider just permissions set for http, https and file URLs.
|
|
if (
|
|
permission.type == "cookie" &&
|
|
permission.capability == Ci.nsICookiePermission.ACCESS_SESSION &&
|
|
["http", "https", "file"].some(scheme =>
|
|
permission.principal.schemeIs(scheme)
|
|
)
|
|
) {
|
|
exceptions++;
|
|
}
|
|
}
|
|
Glean.datasanitization.sessionPermissionExceptions.set(exceptions);
|
|
},
|
|
|
|
httpsOnlyState() {
|
|
const PREF_ENABLED = "dom.security.https_only_mode";
|
|
const PREF_WAS_ENABLED = "dom.security.https_only_mode_ever_enabled";
|
|
const _checkHTTPSOnlyPref = async () => {
|
|
const enabled = Services.prefs.getBoolPref(PREF_ENABLED, false);
|
|
const was_enabled = Services.prefs.getBoolPref(PREF_WAS_ENABLED, false);
|
|
let value = 0;
|
|
if (enabled) {
|
|
value = 1;
|
|
Services.prefs.setBoolPref(PREF_WAS_ENABLED, true);
|
|
} else if (was_enabled) {
|
|
value = 2;
|
|
}
|
|
Glean.security.httpsOnlyModeEnabled.set(value);
|
|
};
|
|
|
|
Services.prefs.addObserver(PREF_ENABLED, _checkHTTPSOnlyPref);
|
|
_checkHTTPSOnlyPref();
|
|
|
|
const PREF_PBM_WAS_ENABLED =
|
|
"dom.security.https_only_mode_ever_enabled_pbm";
|
|
const PREF_PBM_ENABLED = "dom.security.https_only_mode_pbm";
|
|
|
|
const _checkHTTPSOnlyPBMPref = async () => {
|
|
const enabledPBM = Services.prefs.getBoolPref(PREF_PBM_ENABLED, false);
|
|
const was_enabledPBM = Services.prefs.getBoolPref(
|
|
PREF_PBM_WAS_ENABLED,
|
|
false
|
|
);
|
|
let valuePBM = 0;
|
|
if (enabledPBM) {
|
|
valuePBM = 1;
|
|
Services.prefs.setBoolPref(PREF_PBM_WAS_ENABLED, true);
|
|
} else if (was_enabledPBM) {
|
|
valuePBM = 2;
|
|
}
|
|
Glean.security.httpsOnlyModeEnabledPbm.set(valuePBM);
|
|
};
|
|
|
|
Services.prefs.addObserver(PREF_PBM_ENABLED, _checkHTTPSOnlyPBMPref);
|
|
_checkHTTPSOnlyPBMPref();
|
|
},
|
|
|
|
globalPrivacyControl() {
|
|
const FEATURE_PREF_ENABLED = "privacy.globalprivacycontrol.enabled";
|
|
const FUNCTIONALITY_PREF_ENABLED =
|
|
"privacy.globalprivacycontrol.functionality.enabled";
|
|
const PREF_WAS_ENABLED = "privacy.globalprivacycontrol.was_ever_enabled";
|
|
const _checkGPCPref = async () => {
|
|
const feature_enabled = Services.prefs.getBoolPref(
|
|
FEATURE_PREF_ENABLED,
|
|
false
|
|
);
|
|
const functionality_enabled = Services.prefs.getBoolPref(
|
|
FUNCTIONALITY_PREF_ENABLED,
|
|
false
|
|
);
|
|
const was_enabled = Services.prefs.getBoolPref(PREF_WAS_ENABLED, false);
|
|
let value = 0;
|
|
if (feature_enabled && functionality_enabled) {
|
|
value = 1;
|
|
Services.prefs.setBoolPref(PREF_WAS_ENABLED, true);
|
|
} else if (was_enabled) {
|
|
value = 2;
|
|
}
|
|
Glean.security.globalPrivacyControlEnabled.set(value);
|
|
};
|
|
|
|
Services.prefs.addObserver(FEATURE_PREF_ENABLED, _checkGPCPref);
|
|
Services.prefs.addObserver(FUNCTIONALITY_PREF_ENABLED, _checkGPCPref);
|
|
_checkGPCPref();
|
|
},
|
|
|
|
async pinningStatus() {
|
|
let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
|
|
Ci.nsIWindowsShellService
|
|
);
|
|
let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(
|
|
Ci.nsIWinTaskbar
|
|
);
|
|
|
|
try {
|
|
Glean.osEnvironment.isTaskbarPinned.set(
|
|
await shellService.isCurrentAppPinnedToTaskbarAsync(
|
|
winTaskbar.defaultGroupId
|
|
)
|
|
);
|
|
// Bug 1911343: Pinning regular browsing on MSIX
|
|
// causes false positives when checking for private
|
|
// browsing.
|
|
if (
|
|
AppConstants.platform === "win" &&
|
|
!Services.sysinfo.getProperty("hasWinPackageId")
|
|
) {
|
|
Glean.osEnvironment.isTaskbarPinnedPrivate.set(
|
|
await shellService.isCurrentAppPinnedToTaskbarAsync(
|
|
winTaskbar.defaultPrivateGroupId
|
|
)
|
|
);
|
|
}
|
|
} catch (ex) {
|
|
console.error(ex);
|
|
}
|
|
|
|
let classification;
|
|
let shortcut;
|
|
try {
|
|
shortcut = Services.appinfo.processStartupShortcut;
|
|
classification = shellService.classifyShortcut(shortcut);
|
|
} catch (ex) {
|
|
console.error(ex);
|
|
}
|
|
|
|
if (!classification) {
|
|
if (lazy.BrowserInitState.isLaunchOnLogin) {
|
|
classification = "Autostart";
|
|
} else if (shortcut) {
|
|
classification = "OtherShortcut";
|
|
} else {
|
|
classification = "Other";
|
|
}
|
|
}
|
|
// Because of how taskbar tabs work, it may be classifed as a taskbar
|
|
// shortcut, in which case we want to overwrite it.
|
|
if (lazy.BrowserInitState.isTaskbarTab) {
|
|
classification = "TaskbarTab";
|
|
}
|
|
Glean.osEnvironment.launchMethod.set(classification);
|
|
},
|
|
|
|
isDefaultHandler() {
|
|
// Report whether Firefox is the default handler for various files types
|
|
// and protocols, in particular, ".pdf" and "mailto"
|
|
[".pdf", "mailto"].every(x => {
|
|
Glean.osEnvironment.isDefaultHandler[x].set(
|
|
lazy.ShellService.isDefaultHandlerFor(x)
|
|
);
|
|
return true;
|
|
});
|
|
},
|
|
|
|
macDockStatus() {
|
|
// Report macOS Dock status
|
|
Glean.osEnvironment.isKeptInDock.set(
|
|
Cc["@mozilla.org/widget/macdocksupport;1"].getService(
|
|
Ci.nsIMacDockSupport
|
|
).isAppInDock
|
|
);
|
|
},
|
|
|
|
sslKeylogFile() {
|
|
Glean.sslkeylogging.enabled.set(Services.env.exists("SSLKEYLOGFILE"));
|
|
},
|
|
|
|
osAuthEnabled() {
|
|
// Manually read these prefs. This treats any non-empty-string
|
|
// value as "turned off", irrespective of whether it correctly
|
|
// decrypts to the correct value, because we cannot do the
|
|
// decryption if the primary password has not yet been provided,
|
|
// and for telemetry treating that situation as "turned off"
|
|
// seems reasonable.
|
|
const osAuthForCc = !Services.prefs.getStringPref(
|
|
lazy.FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
|
|
""
|
|
);
|
|
const osAuthForPw = !Services.prefs.getStringPref(
|
|
lazy.LoginHelper.OS_AUTH_FOR_PASSWORDS_PREF,
|
|
""
|
|
);
|
|
|
|
Glean.formautofill.osAuthEnabled.set(osAuthForCc);
|
|
Glean.pwmgr.osAuthEnabled.set(osAuthForPw);
|
|
},
|
|
|
|
primaryPasswordEnabled() {
|
|
let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
|
|
Ci.nsIPK11TokenDB
|
|
);
|
|
let token = tokenDB.getInternalKeyToken();
|
|
Glean.primaryPassword.enabled.set(token.hasPassword);
|
|
},
|
|
|
|
trustObjectCount() {
|
|
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
|
|
Ci.nsIX509CertDB
|
|
);
|
|
// countTrustObjects also logs the number of trust objects for telemetry purposes
|
|
certdb.countTrustObjects();
|
|
},
|
|
|
|
pipEnabled() {
|
|
const TOGGLE_ENABLED_PREF =
|
|
"media.videocontrols.picture-in-picture.video-toggle.enabled";
|
|
|
|
const observe = (subject, topic) => {
|
|
const enabled = Services.prefs.getBoolPref(TOGGLE_ENABLED_PREF, false);
|
|
Glean.pictureinpicture.toggleEnabled.set(enabled);
|
|
|
|
// Record events when preferences change
|
|
if (topic === "nsPref:changed") {
|
|
if (enabled) {
|
|
Glean.pictureinpictureSettings.enableSettings.record();
|
|
}
|
|
}
|
|
};
|
|
|
|
Services.prefs.addObserver(TOGGLE_ENABLED_PREF, observe);
|
|
observe();
|
|
},
|
|
};
|