tor-browser

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

ASRouterTelemetry.sys.mjs (9153B)


      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 // We use importESModule here instead of static import so that the Karma test
      6 // environment won't choke on these module. This is because the Karma test
      7 // environment already stubs out XPCOMUtils, AppConstants and RemoteSettings,
      8 // and overrides importESModule to be a no-op (which can't be done for a static
      9 // import statement).
     10 
     11 // eslint-disable-next-line mozilla/use-static-import
     12 const { XPCOMUtils } = ChromeUtils.importESModule(
     13  "resource://gre/modules/XPCOMUtils.sys.mjs"
     14 );
     15 
     16 // eslint-disable-next-line mozilla/use-static-import
     17 const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.importESModule(
     18  "resource:///modules/asrouter/ActorConstants.mjs"
     19 );
     20 
     21 const lazy = {};
     22 
     23 ChromeUtils.defineESModuleGetters(lazy, {
     24  ClientID: "resource://gre/modules/ClientID.sys.mjs",
     25  AboutWelcomeTelemetry:
     26    "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs",
     27  EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs",
     28  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     29  TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
     30  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
     31 });
     32 ChromeUtils.defineLazyGetter(
     33  lazy,
     34  "Telemetry",
     35  () => new lazy.AboutWelcomeTelemetry()
     36 );
     37 
     38 ChromeUtils.defineLazyGetter(
     39  lazy,
     40  "browserSessionId",
     41  () => lazy.TelemetrySession.getMetadata("").sessionId
     42 );
     43 
     44 export const PREF_IMPRESSION_ID =
     45  "browser.newtabpage.activity-stream.impressionId";
     46 
     47 export class ASRouterTelemetry {
     48  constructor() {
     49    this._impressionId = this.getOrCreateImpressionId();
     50    XPCOMUtils.defineLazyPreferenceGetter(
     51      this,
     52      "telemetryEnabled",
     53      "browser.newtabpage.activity-stream.telemetry",
     54      false
     55    );
     56  }
     57 
     58  get telemetryClientId() {
     59    Object.defineProperty(this, "telemetryClientId", {
     60      value: lazy.ClientID.getClientID(),
     61    });
     62    return this.telemetryClientId;
     63  }
     64 
     65  getOrCreateImpressionId() {
     66    let impressionId = Services.prefs.getCharPref(PREF_IMPRESSION_ID, "");
     67    if (!impressionId) {
     68      impressionId = String(Services.uuid.generateUUID());
     69      Services.prefs.setCharPref(PREF_IMPRESSION_ID, impressionId);
     70    }
     71    return impressionId;
     72  }
     73  /**
     74   *  Check if it is in the CFR experiment cohort by querying against the
     75   *  experiment manager of Messaging System
     76   *
     77   *  @return {bool}
     78   */
     79  get isInCFRCohort() {
     80    return !!lazy.NimbusFeatures.cfr.getEnrollmentMetadata(
     81      lazy.EnrollmentType.EXPERIMENT
     82    );
     83  }
     84 
     85  /**
     86   * Create a ping for AS router event. The client_id is set to "n/a" by default,
     87   * different component can override this by its own telemetry collection policy.
     88   */
     89  async createASRouterEvent(action) {
     90    let event = {
     91      ...action.data,
     92      addon_version: Services.appinfo.appBuildID,
     93      locale: Services.locale.appLocaleAsBCP47,
     94    };
     95 
     96    if (event.event_context && typeof event.event_context === "object") {
     97      event.event_context = JSON.stringify(event.event_context);
     98    }
     99    switch (event.action) {
    100      case "cfr_user_event":
    101        event = await this.applyCFRPolicy(event);
    102        break;
    103      case "badge_user_event":
    104        event = await this.applyToolbarBadgePolicy(event);
    105        break;
    106      case "infobar_user_event":
    107        event = await this.applyInfoBarPolicy(event);
    108        break;
    109      case "spotlight_user_event":
    110        event = await this.applySpotlightPolicy(event);
    111        break;
    112      case "toast_notification_user_event":
    113        event = await this.applyToastNotificationPolicy(event);
    114        break;
    115      case "moments_user_event":
    116        event = await this.applyMomentsPolicy(event);
    117        break;
    118      case "menu_message_user_event":
    119        event = await this.applyMenuMessagePolicy(event);
    120        break;
    121      case "asrouter_undesired_event":
    122        event = this.applyUndesiredEventPolicy(event);
    123        break;
    124      case "newtab_message_user_event":
    125        event = await this.applyNewtabMessagePolicy(event);
    126        break;
    127      default:
    128        event = { ping: event };
    129        break;
    130    }
    131    return event;
    132  }
    133 
    134  /**
    135   * Per Bug 1484035, CFR metrics comply with following policies:
    136   * 1). In release, it collects impression_id and bucket_id
    137   * 2). In prerelease, it collects client_id and message_id
    138   * 3). In shield experiments conducted in release, it collects client_id and message_id
    139   * 4). In Private Browsing windows, unless in experiment, collects impression_id and bucket_id
    140   */
    141  async applyCFRPolicy(ping) {
    142    if (
    143      (lazy.UpdateUtils.getUpdateChannel(true) === "release" ||
    144        ping.is_private) &&
    145      !this.isInCFRCohort
    146    ) {
    147      ping.message_id = "n/a";
    148      ping.impression_id = this._impressionId;
    149    } else {
    150      ping.client_id = await this.telemetryClientId;
    151    }
    152    delete ping.action;
    153    delete ping.is_private;
    154    return { ping, pingType: "cfr" };
    155  }
    156 
    157  /**
    158   * Per Bug 1482134, all the metrics for What's New panel use client_id in
    159   * all the release channels
    160   */
    161  async applyToolbarBadgePolicy(ping) {
    162    ping.client_id = await this.telemetryClientId;
    163    ping.browser_session_id = lazy.browserSessionId;
    164    // Attach page info to `event_context` if there is a session associated with this ping
    165    delete ping.action;
    166    return { ping, pingType: "toolbar-badge" };
    167  }
    168 
    169  async applyInfoBarPolicy(ping) {
    170    ping.client_id = await this.telemetryClientId;
    171    ping.browser_session_id = lazy.browserSessionId;
    172    delete ping.action;
    173    return { ping, pingType: "infobar" };
    174  }
    175 
    176  async applySpotlightPolicy(ping) {
    177    ping.client_id = await this.telemetryClientId;
    178    ping.browser_session_id = lazy.browserSessionId;
    179    delete ping.action;
    180    return { ping, pingType: "spotlight" };
    181  }
    182 
    183  async applyToastNotificationPolicy(ping) {
    184    ping.client_id = await this.telemetryClientId;
    185    ping.browser_session_id = lazy.browserSessionId;
    186    delete ping.action;
    187    return { ping, pingType: "toast_notification" };
    188  }
    189 
    190  async applyMenuMessagePolicy(ping) {
    191    ping.client_id = await this.telemetryClientId;
    192    ping.browser_session_id = lazy.browserSessionId;
    193    delete ping.action;
    194    return { ping, pingType: "menu" };
    195  }
    196 
    197  /**
    198   * Per Bug 1484035, Moments metrics comply with following policies:
    199   * 1). In release, it collects impression_id, and treats bucket_id as message_id
    200   * 2). In prerelease, it collects client_id and message_id
    201   * 3). In shield experiments conducted in release, it collects client_id and message_id
    202   */
    203  async applyMomentsPolicy(ping) {
    204    if (
    205      lazy.UpdateUtils.getUpdateChannel(true) === "release" &&
    206      !this.isInCFRCohort
    207    ) {
    208      ping.message_id = "n/a";
    209      ping.impression_id = this._impressionId;
    210    } else {
    211      ping.client_id = await this.telemetryClientId;
    212    }
    213    delete ping.action;
    214    return { ping, pingType: "moments" };
    215  }
    216 
    217  async applyNewtabMessagePolicy(ping) {
    218    ping.client_id = await this.telemetryClientId;
    219    ping.browser_session_id = lazy.browserSessionId;
    220    ping.addon_version = Services.appinfo.appBuildID;
    221    ping.locale = Services.locale.appLocaleAsBCP47;
    222    delete ping.action;
    223    return { ping, pingType: "newtab_message" };
    224  }
    225 
    226  applyUndesiredEventPolicy(ping) {
    227    ping.impression_id = this._impressionId;
    228    delete ping.action;
    229    return { ping, pingType: "undesired-events" };
    230  }
    231 
    232  async handleASRouterUserEvent(action) {
    233    const { ping, pingType } = await this.createASRouterEvent(action);
    234    if (!pingType) {
    235      console.error("Unknown ping type for ASRouter telemetry");
    236      return;
    237    }
    238 
    239    // Now that the action has become a ping, we can echo it to Glean.
    240    if (this.telemetryEnabled) {
    241      lazy.Telemetry.submitGleanPingForPing({ ...ping, pingType });
    242    }
    243  }
    244 
    245  /**
    246   * This function is used by ActivityStreamStorage to report errors
    247   * trying to access IndexedDB.
    248   */
    249  SendASRouterUndesiredEvent(data) {
    250    this.handleASRouterUserEvent({
    251      data: { ...data, action: "asrouter_undesired_event" },
    252    });
    253  }
    254 
    255  onAction(action) {
    256    switch (action.type) {
    257      // The remaining action types come from ASRouter, which doesn't use
    258      // Actions from Actions.mjs, but uses these other custom strings.
    259      case msg.TOOLBAR_BADGE_TELEMETRY:
    260      // Intentional fall-through
    261      case msg.TOOLBAR_PANEL_TELEMETRY:
    262      // Intentional fall-through
    263      case msg.MOMENTS_PAGE_TELEMETRY:
    264      // Intentional fall-through
    265      case msg.DOORHANGER_TELEMETRY:
    266      // Intentional fall-through
    267      case msg.INFOBAR_TELEMETRY:
    268      // Intentional fall-through
    269      case msg.SPOTLIGHT_TELEMETRY:
    270      // Intentional fall-through
    271      case msg.TOAST_NOTIFICATION_TELEMETRY:
    272      // Intentional fall-through
    273      case msg.MENU_MESSAGE_TELEMETRY:
    274      // Intentional fall-through
    275      case msg.NEWTAB_MESSAGE_TELEMETRY:
    276      // Intentional fall-through
    277      case msg.AS_ROUTER_TELEMETRY_USER_EVENT:
    278        this.handleASRouterUserEvent(action);
    279        break;
    280    }
    281  }
    282 }