tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

StartupTelemetry.sys.mjs (16747B)


      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/. */
      4 
      5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      6 
      7 let lazy = {};
      8 ChromeUtils.defineESModuleGetters(lazy, {
      9  BrowserInitState: "resource:///modules/BrowserGlue.sys.mjs",
     10  BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
     11  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
     12  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
     13  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     14  OsEnvironment: "resource://gre/modules/OsEnvironment.sys.mjs",
     15  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
     16  ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
     17  TelemetryReportingPolicy:
     18    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
     19  UsageReporting: "resource://gre/modules/UsageReporting.sys.mjs",
     20 });
     21 
     22 /**
     23 * Used to collect various bits of telemetry during browser startup.
     24 *
     25 */
     26 export let StartupTelemetry = {
     27  // Some tasks are expensive because they involve significant disk IO, and
     28  // may also write information to disk. If we submit the telemetry that may
     29  // happen anyway, but if we don't then this is undesirable, so those tasks are
     30  // only run if we will submit the results.
     31  // Why run any telemetry code at all if we don't submit the data? Because
     32  // local and autoland builds usually do not submit telemetry, but we still
     33  // want to be able to run automated tests to check the code _worked_.
     34  get _willUseExpensiveTelemetry() {
     35    return (
     36      AppConstants.MOZ_TELEMETRY_REPORTING &&
     37      Services.prefs.getBoolPref(
     38        "datareporting.healthreport.uploadEnabled",
     39        false
     40      )
     41    );
     42  },
     43 
     44  _runIdleTasks(tasks, profilerMarker) {
     45    for (let task of tasks) {
     46      ChromeUtils.idleDispatch(async () => {
     47        if (!Services.startup.shuttingDown) {
     48          let startTime = ChromeUtils.now();
     49          try {
     50            await task();
     51          } catch (ex) {
     52            console.error(ex);
     53          } finally {
     54            ChromeUtils.addProfilerMarker(
     55              profilerMarker,
     56              startTime,
     57              task.toSource()
     58            );
     59          }
     60        }
     61      });
     62    }
     63  },
     64 
     65  browserIdleStartup() {
     66    let tasks = [
     67      // FOG doesn't need to be initialized _too_ early because it has a pre-init buffer.
     68      () => this.initFOG(),
     69 
     70      () => this.contentBlocking(),
     71      () => this.dataSanitization(),
     72      () => this.pipEnabled(),
     73      () => this.sslKeylogFile(),
     74      () => this.osAuthEnabled(),
     75      () => this.startupConditions(),
     76      () => this.httpsOnlyState(),
     77      () => this.globalPrivacyControl(),
     78    ];
     79    if (this._willUseExpensiveTelemetry) {
     80      tasks.push(() => lazy.PlacesDBUtils.telemetry());
     81    }
     82    if (AppConstants.platform == "win") {
     83      tasks.push(
     84        () => this.pinningStatus(),
     85        () => this.isDefaultHandler()
     86      );
     87    } else if (AppConstants.platform == "macosx") {
     88      tasks.push(() => this.macDockStatus());
     89    }
     90 
     91    this._runIdleTasks(tasks, "startupTelemetryIdleTask");
     92  },
     93 
     94  /**
     95   * Use this function as an entry point to collect telemetry that we hope
     96   * to collect once per session, at any arbitrary point in time, and
     97   *
     98   * **which we are okay with sometimes not running at all.**
     99   *
    100   * See BrowserGlue.sys.mjs's _scheduleBestEffortUserIdleTasks for more
    101   * details.
    102   */
    103  bestEffortIdleStartup() {
    104    let tasks = [
    105      () => this.primaryPasswordEnabled(),
    106      () => lazy.OsEnvironment.reportAllowedAppSources(),
    107    ];
    108    if (AppConstants.platform == "win" && this._willUseExpensiveTelemetry) {
    109      tasks.push(
    110        () => lazy.BrowserUsageTelemetry.reportProfileCount(),
    111        () => lazy.BrowserUsageTelemetry.reportInstallationTelemetry()
    112      );
    113    }
    114    this._runIdleTasks(tasks, "startupTelemetryLateIdleTask");
    115  },
    116 
    117  /**
    118   * Initialize Firefox-on-Glean.
    119   *
    120   * This is at the top because it's a bit different from the other code here
    121   * which is strictly collecting specific metrics.
    122   */
    123  async initFOG() {
    124    // Handle Usage Profile ID.  Similar logic to what's happening in
    125    // `TelemetryControllerParent` for the client ID.  Must be done before
    126    // initializing FOG so that ping enabled/disabled states are correct
    127    // before Glean takes actions.
    128    await lazy.UsageReporting.ensureInitialized();
    129 
    130    // If needed, delay initializing FOG until policy interaction is
    131    // completed.  See comments in `TelemetryReportingPolicy`.
    132    await lazy.TelemetryReportingPolicy.ensureUserIsNotified();
    133 
    134    Services.fog.initializeFOG();
    135 
    136    // Register Glean to listen for experiment updates releated to the
    137    // "gleanInternalSdk" feature defined in the t/c/nimbus/FeatureManifest.yaml
    138    // This feature is intended for internal Glean use only. For features wishing
    139    // to set a remote metric configuration, please use the "glean" feature for
    140    // the purpose of setting the data-control-plane features via Server Knobs.
    141    lazy.NimbusFeatures.gleanInternalSdk.onUpdate(() => {
    142      let cfg = lazy.NimbusFeatures.gleanInternalSdk.getVariable(
    143        "gleanMetricConfiguration"
    144      );
    145      Services.fog.applyServerKnobsConfig(JSON.stringify(cfg));
    146    });
    147 
    148    // Register Glean to listen for experiment updates releated to the
    149    // "glean" feature defined in the t/c/nimbus/FeatureManifest.yaml
    150    lazy.NimbusFeatures.glean.onUpdate(() => {
    151      const enrollments = lazy.NimbusFeatures.glean.getAllEnrollments();
    152      for (const enrollment of enrollments) {
    153        const cfg = enrollment.value.gleanMetricConfiguration;
    154        if (typeof cfg === "object" && cfg !== null) {
    155          Services.fog.applyServerKnobsConfig(JSON.stringify(cfg));
    156        }
    157      }
    158    });
    159  },
    160 
    161  startupConditions() {
    162    let nowSeconds = Math.round(Date.now() / 1000);
    163    // Don't include cases where we don't have the pref. This rules out the first install
    164    // as well as the first run of a build since this was introduced. These could by some
    165    // definitions be referred to as "cold" startups, but probably not since we likely
    166    // just wrote many of the files we use to disk. This way we should approximate a lower
    167    // bound to the number of cold startups rather than an upper bound.
    168    let lastCheckSeconds = Services.prefs.getIntPref(
    169      "browser.startup.lastColdStartupCheck",
    170      nowSeconds
    171    );
    172    Services.prefs.setIntPref(
    173      "browser.startup.lastColdStartupCheck",
    174      nowSeconds
    175    );
    176    try {
    177      let secondsSinceLastOSRestart =
    178        Services.startup.secondsSinceLastOSRestart;
    179      let isColdStartup =
    180        nowSeconds - secondsSinceLastOSRestart > lastCheckSeconds;
    181      Glean.startup.isCold.set(isColdStartup);
    182      Glean.startup.secondsSinceLastOsRestart.set(secondsSinceLastOSRestart);
    183    } catch (ex) {
    184      if (ex.name !== "NS_ERROR_NOT_IMPLEMENTED") {
    185        console.error(ex);
    186      }
    187    }
    188  },
    189 
    190  contentBlocking() {
    191    let tpEnabled = Services.prefs.getBoolPref(
    192      "privacy.trackingprotection.enabled"
    193    );
    194    Glean.contentblocking.trackingProtectionEnabled[
    195      tpEnabled ? "true" : "false"
    196    ].add();
    197 
    198    let tpPBEnabled = Services.prefs.getBoolPref(
    199      "privacy.trackingprotection.pbmode.enabled"
    200    );
    201    Glean.contentblocking.trackingProtectionPbmDisabled[
    202      !tpPBEnabled ? "true" : "false"
    203    ].add();
    204 
    205    let cookieBehavior = Services.prefs.getIntPref(
    206      "network.cookie.cookieBehavior"
    207    );
    208    Glean.contentblocking.cookieBehavior.accumulateSingleSample(cookieBehavior);
    209 
    210    let fpEnabled = Services.prefs.getBoolPref(
    211      "privacy.trackingprotection.fingerprinting.enabled"
    212    );
    213    let cmEnabled = Services.prefs.getBoolPref(
    214      "privacy.trackingprotection.cryptomining.enabled"
    215    );
    216    let categoryPref;
    217    switch (
    218      Services.prefs.getStringPref("browser.contentblocking.category", null)
    219    ) {
    220      case "standard":
    221        categoryPref = 0;
    222        break;
    223      case "strict":
    224        categoryPref = 1;
    225        break;
    226      case "custom":
    227        categoryPref = 2;
    228        break;
    229      default:
    230        // Any other value is unsupported.
    231        categoryPref = 3;
    232        break;
    233    }
    234 
    235    Glean.contentblocking.fingerprintingBlockingEnabled.set(fpEnabled);
    236    Glean.contentblocking.cryptominingBlockingEnabled.set(cmEnabled);
    237    Glean.contentblocking.category.set(categoryPref);
    238  },
    239 
    240  dataSanitization() {
    241    Glean.datasanitization.privacySanitizeSanitizeOnShutdown.set(
    242      Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown")
    243    );
    244    Glean.datasanitization.privacyClearOnShutdownCookies.set(
    245      Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies")
    246    );
    247    Glean.datasanitization.privacyClearOnShutdownHistory.set(
    248      Services.prefs.getBoolPref("privacy.clearOnShutdown.history")
    249    );
    250    Glean.datasanitization.privacyClearOnShutdownFormdata.set(
    251      Services.prefs.getBoolPref("privacy.clearOnShutdown.formdata")
    252    );
    253    Glean.datasanitization.privacyClearOnShutdownDownloads.set(
    254      Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads")
    255    );
    256    Glean.datasanitization.privacyClearOnShutdownCache.set(
    257      Services.prefs.getBoolPref("privacy.clearOnShutdown.cache")
    258    );
    259    Glean.datasanitization.privacyClearOnShutdownSessions.set(
    260      Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions")
    261    );
    262    Glean.datasanitization.privacyClearOnShutdownOfflineApps.set(
    263      Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps")
    264    );
    265    Glean.datasanitization.privacyClearOnShutdownSiteSettings.set(
    266      Services.prefs.getBoolPref("privacy.clearOnShutdown.siteSettings")
    267    );
    268    Glean.datasanitization.privacyClearOnShutdownOpenWindows.set(
    269      Services.prefs.getBoolPref("privacy.clearOnShutdown.openWindows")
    270    );
    271 
    272    let exceptions = 0;
    273    for (let permission of Services.perms.all) {
    274      // We consider just permissions set for http, https and file URLs.
    275      if (
    276        permission.type == "cookie" &&
    277        permission.capability == Ci.nsICookiePermission.ACCESS_SESSION &&
    278        ["http", "https", "file"].some(scheme =>
    279          permission.principal.schemeIs(scheme)
    280        )
    281      ) {
    282        exceptions++;
    283      }
    284    }
    285    Glean.datasanitization.sessionPermissionExceptions.set(exceptions);
    286  },
    287 
    288  httpsOnlyState() {
    289    const PREF_ENABLED = "dom.security.https_only_mode";
    290    const PREF_WAS_ENABLED = "dom.security.https_only_mode_ever_enabled";
    291    const _checkHTTPSOnlyPref = async () => {
    292      const enabled = Services.prefs.getBoolPref(PREF_ENABLED, false);
    293      const was_enabled = Services.prefs.getBoolPref(PREF_WAS_ENABLED, false);
    294      let value = 0;
    295      if (enabled) {
    296        value = 1;
    297        Services.prefs.setBoolPref(PREF_WAS_ENABLED, true);
    298      } else if (was_enabled) {
    299        value = 2;
    300      }
    301      Glean.security.httpsOnlyModeEnabled.set(value);
    302    };
    303 
    304    Services.prefs.addObserver(PREF_ENABLED, _checkHTTPSOnlyPref);
    305    _checkHTTPSOnlyPref();
    306 
    307    const PREF_PBM_WAS_ENABLED =
    308      "dom.security.https_only_mode_ever_enabled_pbm";
    309    const PREF_PBM_ENABLED = "dom.security.https_only_mode_pbm";
    310 
    311    const _checkHTTPSOnlyPBMPref = async () => {
    312      const enabledPBM = Services.prefs.getBoolPref(PREF_PBM_ENABLED, false);
    313      const was_enabledPBM = Services.prefs.getBoolPref(
    314        PREF_PBM_WAS_ENABLED,
    315        false
    316      );
    317      let valuePBM = 0;
    318      if (enabledPBM) {
    319        valuePBM = 1;
    320        Services.prefs.setBoolPref(PREF_PBM_WAS_ENABLED, true);
    321      } else if (was_enabledPBM) {
    322        valuePBM = 2;
    323      }
    324      Glean.security.httpsOnlyModeEnabledPbm.set(valuePBM);
    325    };
    326 
    327    Services.prefs.addObserver(PREF_PBM_ENABLED, _checkHTTPSOnlyPBMPref);
    328    _checkHTTPSOnlyPBMPref();
    329  },
    330 
    331  globalPrivacyControl() {
    332    const FEATURE_PREF_ENABLED = "privacy.globalprivacycontrol.enabled";
    333    const FUNCTIONALITY_PREF_ENABLED =
    334      "privacy.globalprivacycontrol.functionality.enabled";
    335    const PREF_WAS_ENABLED = "privacy.globalprivacycontrol.was_ever_enabled";
    336    const _checkGPCPref = async () => {
    337      const feature_enabled = Services.prefs.getBoolPref(
    338        FEATURE_PREF_ENABLED,
    339        false
    340      );
    341      const functionality_enabled = Services.prefs.getBoolPref(
    342        FUNCTIONALITY_PREF_ENABLED,
    343        false
    344      );
    345      const was_enabled = Services.prefs.getBoolPref(PREF_WAS_ENABLED, false);
    346      let value = 0;
    347      if (feature_enabled && functionality_enabled) {
    348        value = 1;
    349        Services.prefs.setBoolPref(PREF_WAS_ENABLED, true);
    350      } else if (was_enabled) {
    351        value = 2;
    352      }
    353      Glean.security.globalPrivacyControlEnabled.set(value);
    354    };
    355 
    356    Services.prefs.addObserver(FEATURE_PREF_ENABLED, _checkGPCPref);
    357    Services.prefs.addObserver(FUNCTIONALITY_PREF_ENABLED, _checkGPCPref);
    358    _checkGPCPref();
    359  },
    360 
    361  // check if the launcher was used to open firefox
    362  isUsingLauncher() {
    363    if (Services.env.get("FIREFOX_LAUNCHED_BY_DESKTOP_LAUNCHER") == "TRUE") {
    364      return true;
    365    }
    366 
    367    return false;
    368  },
    369 
    370  async pinningStatus() {
    371    let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
    372      Ci.nsIWindowsShellService
    373    );
    374    let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(
    375      Ci.nsIWinTaskbar
    376    );
    377 
    378    try {
    379      Glean.osEnvironment.isTaskbarPinned.set(
    380        await shellService.isCurrentAppPinnedToTaskbarAsync(
    381          winTaskbar.defaultGroupId
    382        )
    383      );
    384      // Bug 1911343: Pinning regular browsing on MSIX
    385      // causes false positives when checking for private
    386      // browsing.
    387      if (
    388        AppConstants.platform === "win" &&
    389        !Services.sysinfo.getProperty("hasWinPackageId")
    390      ) {
    391        Glean.osEnvironment.isTaskbarPinnedPrivate.set(
    392          await shellService.isCurrentAppPinnedToTaskbarAsync(
    393            winTaskbar.defaultPrivateGroupId
    394          )
    395        );
    396      }
    397    } catch (ex) {
    398      console.error(ex);
    399    }
    400 
    401    let classification;
    402    let shortcut;
    403    try {
    404      shortcut = Services.appinfo.processStartupShortcut;
    405      classification = shellService.classifyShortcut(shortcut);
    406    } catch (ex) {
    407      console.error(ex);
    408    }
    409 
    410    if (!classification) {
    411      if (lazy.BrowserInitState.isLaunchOnLogin) {
    412        classification = "Autostart";
    413      } else if (shortcut) {
    414        classification = "OtherShortcut";
    415      } else if (this.isUsingLauncher()) {
    416        classification = "DesktopLauncher";
    417      } else {
    418        classification = "Other";
    419      }
    420    }
    421    // Because of how taskbar tabs work, it may be classifed as a taskbar
    422    // shortcut, in which case we want to overwrite it.
    423    if (lazy.BrowserInitState.isTaskbarTab) {
    424      classification = "TaskbarTab";
    425    }
    426    Glean.osEnvironment.launchMethod.set(classification);
    427  },
    428 
    429  isDefaultHandler() {
    430    // Report whether Firefox is the default handler for various files types
    431    // and protocols, in particular, ".pdf" and "mailto"
    432    [".pdf", "mailto"].every(x => {
    433      Glean.osEnvironment.isDefaultHandler[x].set(
    434        lazy.ShellService.isDefaultHandlerFor(x)
    435      );
    436      return true;
    437    });
    438  },
    439 
    440  macDockStatus() {
    441    // Report macOS Dock status
    442    Glean.osEnvironment.isKeptInDock.set(
    443      Cc["@mozilla.org/widget/macdocksupport;1"].getService(
    444        Ci.nsIMacDockSupport
    445      ).isAppInDock
    446    );
    447  },
    448 
    449  sslKeylogFile() {
    450    Glean.sslkeylogging.enabled.set(Services.env.exists("SSLKEYLOGFILE"));
    451  },
    452 
    453  osAuthEnabled() {
    454    const osAuthForCc = lazy.FormAutofillUtils.getOSAuthEnabled();
    455    const osAuthForPw = lazy.LoginHelper.getOSAuthEnabled();
    456 
    457    Glean.formautofill.osAuthEnabled.set(osAuthForCc);
    458    Glean.pwmgr.osAuthEnabled.set(osAuthForPw);
    459  },
    460 
    461  primaryPasswordEnabled() {
    462    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
    463      Ci.nsIPK11TokenDB
    464    );
    465    let token = tokenDB.getInternalKeyToken();
    466    Glean.primaryPassword.enabled.set(token.hasPassword);
    467  },
    468 
    469  pipEnabled() {
    470    const TOGGLE_ENABLED_PREF =
    471      "media.videocontrols.picture-in-picture.video-toggle.enabled";
    472 
    473    const observe = (subject, topic) => {
    474      const enabled = Services.prefs.getBoolPref(TOGGLE_ENABLED_PREF, false);
    475      Glean.pictureinpicture.toggleEnabled.set(enabled);
    476 
    477      // Record events when preferences change
    478      if (topic === "nsPref:changed") {
    479        if (enabled) {
    480          Glean.pictureinpictureSettings.enableSettings.record();
    481        }
    482      }
    483    };
    484 
    485    Services.prefs.addObserver(TOGGLE_ENABLED_PREF, observe);
    486    observe();
    487  },
    488 };