From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- browser/components/newtab/lib/MomentsPageHub.jsm | 174 +++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 browser/components/newtab/lib/MomentsPageHub.jsm (limited to 'browser/components/newtab/lib/MomentsPageHub.jsm') diff --git a/browser/components/newtab/lib/MomentsPageHub.jsm b/browser/components/newtab/lib/MomentsPageHub.jsm new file mode 100644 index 0000000000..e37d1df8b1 --- /dev/null +++ b/browser/components/newtab/lib/MomentsPageHub.jsm @@ -0,0 +1,174 @@ +/* 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/. */ +"use strict"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + setInterval: "resource://gre/modules/Timer.sys.mjs", + clearInterval: "resource://gre/modules/Timer.sys.mjs", +}); + +// Frequency at which to check for new messages +const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000; +const HOMEPAGE_OVERRIDE_PREF = "browser.startup.homepage_override.once"; + +// For the "reach" event of Messaging Experiments +const REACH_EVENT_CATEGORY = "messaging_experiments"; +const REACH_EVENT_METHOD = "reach"; +// Note it's not "moments-page" as Telemetry Events only accepts understores +// for the event `object` +const REACH_EVENT_OBJECT = "moments_page"; + +class _MomentsPageHub { + constructor() { + this.id = "moments-page-hub"; + this.state = {}; + this.checkHomepageOverridePref = this.checkHomepageOverridePref.bind(this); + this._initialized = false; + } + + async init( + waitForInitialized, + { handleMessageRequest, addImpression, blockMessageById, sendTelemetry } + ) { + if (this._initialized) { + return; + } + + this._initialized = true; + this._handleMessageRequest = handleMessageRequest; + this._addImpression = addImpression; + this._blockMessageById = blockMessageById; + this._sendTelemetry = sendTelemetry; + + // Need to wait for ASRouter to initialize before trying to fetch messages + await waitForInitialized; + + this.messageRequest({ + triggerId: "momentsUpdate", + template: "update_action", + }); + + const _intervalId = lazy.setInterval( + () => this.checkHomepageOverridePref(), + SYSTEM_TICK_INTERVAL + ); + this.state = { _intervalId }; + } + + _sendPing(ping) { + this._sendTelemetry({ + type: "MOMENTS_PAGE_TELEMETRY", + data: { action: "moments_user_event", ...ping }, + }); + } + + sendUserEventTelemetry(message) { + this._sendPing({ + message_id: message.id, + bucket_id: message.id, + event: "MOMENTS_PAGE_SET", + }); + } + + /** + * If we don't have `expire` defined with the message it could be because + * it depends on user dependent parameters. Since the message matched + * targeting we calculate `expire` based on the current timestamp and the + * `expireDelta` which defines for how long it should be available. + * @param expireDelta {number} - Offset in milliseconds from the current date + */ + getExpirationDate(expireDelta) { + return Date.now() + expireDelta; + } + + executeAction(message) { + const { id, data } = message.content.action; + switch (id) { + case "moments-wnp": + const { url, expireDelta } = data; + let { expire } = data; + if (!expire) { + expire = this.getExpirationDate(expireDelta); + } + // In order to reset this action we can dispatch a new message that + // will overwrite the prev value with an expiration date from the past. + Services.prefs.setStringPref( + HOMEPAGE_OVERRIDE_PREF, + JSON.stringify({ message_id: message.id, url, expire }) + ); + // Add impression and block immediately after taking the action + this.sendUserEventTelemetry(message); + this._addImpression(message); + this._blockMessageById(message.id); + break; + } + } + + _recordReachEvent(message) { + const extra = { branches: message.branchSlug }; + Services.telemetry.recordEvent( + REACH_EVENT_CATEGORY, + REACH_EVENT_METHOD, + REACH_EVENT_OBJECT, + message.experimentSlug, + extra + ); + } + + async messageRequest({ triggerId, template }) { + const telemetryObject = { triggerId }; + TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject); + const messages = await this._handleMessageRequest({ + triggerId, + template, + returnAll: true, + }); + TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject); + + // Record the "reach" event for all the messages with `forReachEvent`, + // only execute action for the first message without forReachEvent. + const nonReachMessages = []; + for (const message of messages) { + if (message.forReachEvent) { + if (!message.forReachEvent.sent) { + this._recordReachEvent(message); + message.forReachEvent.sent = true; + } + } else { + nonReachMessages.push(message); + } + } + if (nonReachMessages.length) { + this.executeAction(nonReachMessages[0]); + } + } + + /** + * Pref is set via Remote Settings message. We want to continously + * monitor new messages that come in to ensure the one with the + * highest priority is set. + */ + checkHomepageOverridePref() { + this.messageRequest({ + triggerId: "momentsUpdate", + template: "update_action", + }); + } + + uninit() { + lazy.clearInterval(this.state._intervalId); + this.state = {}; + this._initialized = false; + } +} + +/** + * ToolbarBadgeHub - singleton instance of _ToolbarBadgeHub that can initiate + * message requests and render messages. + */ +const MomentsPageHub = new _MomentsPageHub(); + +const EXPORTED_SYMBOLS = ["_MomentsPageHub", "MomentsPageHub"]; -- cgit v1.2.3