tor-browser

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

MomentsPageHub.sys.mjs (4791B)


      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 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  setInterval: "resource://gre/modules/Timer.sys.mjs",
      9  clearInterval: "resource://gre/modules/Timer.sys.mjs",
     10 });
     11 
     12 // Frequency at which to check for new messages
     13 const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000;
     14 const HOMEPAGE_OVERRIDE_PREF = "browser.startup.homepage_override.once";
     15 
     16 export class _MomentsPageHub {
     17  constructor() {
     18    this.id = "moments-page-hub";
     19    this.state = {};
     20    this.checkHomepageOverridePref = this.checkHomepageOverridePref.bind(this);
     21    this._initialized = false;
     22  }
     23 
     24  async init(
     25    waitForInitialized,
     26    { handleMessageRequest, addImpression, blockMessageById, sendTelemetry }
     27  ) {
     28    if (this._initialized) {
     29      return;
     30    }
     31 
     32    this._initialized = true;
     33    this._handleMessageRequest = handleMessageRequest;
     34    this._addImpression = addImpression;
     35    this._blockMessageById = blockMessageById;
     36    this._sendTelemetry = sendTelemetry;
     37 
     38    // Need to wait for ASRouter to initialize before trying to fetch messages
     39    await waitForInitialized;
     40 
     41    this.messageRequest({
     42      triggerId: "momentsUpdate",
     43      template: "update_action",
     44    });
     45 
     46    const _intervalId = lazy.setInterval(
     47      () => this.checkHomepageOverridePref(),
     48      SYSTEM_TICK_INTERVAL
     49    );
     50    this.state = { _intervalId };
     51  }
     52 
     53  _sendPing(ping) {
     54    this._sendTelemetry({
     55      type: "MOMENTS_PAGE_TELEMETRY",
     56      data: { action: "moments_user_event", ...ping },
     57    });
     58  }
     59 
     60  sendUserEventTelemetry(message) {
     61    this._sendPing({
     62      message_id: message.id,
     63      bucket_id: message.id,
     64      event: "MOMENTS_PAGE_SET",
     65    });
     66  }
     67 
     68  /**
     69   * If we don't have `expire` defined with the message it could be because
     70   * it depends on user dependent parameters. Since the message matched
     71   * targeting we calculate `expire` based on the current timestamp and the
     72   * `expireDelta` which defines for how long it should be available.
     73   *
     74   * @param expireDelta {number} - Offset in milliseconds from the current date
     75   */
     76  getExpirationDate(expireDelta) {
     77    return Date.now() + expireDelta;
     78  }
     79 
     80  executeAction(message) {
     81    const { id, data } = message.content.action;
     82    switch (id) {
     83      case "moments-wnp": {
     84        const { url, expireDelta } = data;
     85        let { expire } = data;
     86        if (!expire) {
     87          expire = this.getExpirationDate(expireDelta);
     88        }
     89        // In order to reset this action we can dispatch a new message that
     90        // will overwrite the prev value with an expiration date from the past.
     91        Services.prefs.setStringPref(
     92          HOMEPAGE_OVERRIDE_PREF,
     93          JSON.stringify({ message_id: message.id, url, expire })
     94        );
     95        // Add impression and block immediately after taking the action
     96        this.sendUserEventTelemetry(message);
     97        this._addImpression(message);
     98        this._blockMessageById(message.id);
     99        break;
    100      }
    101    }
    102  }
    103 
    104  _recordReachEvent(message) {
    105    Glean.messagingExperiments.reachMomentsPage.record({
    106      value: message.experimentSlug,
    107      branches: message.branchSlug,
    108    });
    109  }
    110 
    111  async messageRequest({ triggerId, template }) {
    112    const timerId = Glean.messagingSystem.messageRequestTime.start();
    113    const messages = await this._handleMessageRequest({
    114      triggerId,
    115      template,
    116      returnAll: true,
    117    });
    118    Glean.messagingSystem.messageRequestTime.stopAndAccumulate(timerId);
    119 
    120    // Record the "reach" event for all the messages with `forReachEvent`,
    121    // only execute action for the first message without forReachEvent.
    122    const nonReachMessages = [];
    123    for (const message of messages) {
    124      if (message.forReachEvent) {
    125        if (!message.forReachEvent.sent) {
    126          this._recordReachEvent(message);
    127          message.forReachEvent.sent = true;
    128        }
    129      } else {
    130        nonReachMessages.push(message);
    131      }
    132    }
    133    if (nonReachMessages.length) {
    134      this.executeAction(nonReachMessages[0]);
    135    }
    136  }
    137 
    138  /**
    139   * Pref is set via Remote Settings message. We want to continously
    140   * monitor new messages that come in to ensure the one with the
    141   * highest priority is set.
    142   */
    143  checkHomepageOverridePref() {
    144    this.messageRequest({
    145      triggerId: "momentsUpdate",
    146      template: "update_action",
    147    });
    148  }
    149 
    150  uninit() {
    151    lazy.clearInterval(this.state._intervalId);
    152    this.state = {};
    153    this._initialized = false;
    154  }
    155 }
    156 
    157 /**
    158 * MomentsPageHub - singleton instance of _MomentsPageHub that can initiate
    159 * message requests and render messages.
    160 */
    161 export const MomentsPageHub = new _MomentsPageHub();