summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/lib/MomentsPageHub.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/lib/MomentsPageHub.jsm')
-rw-r--r--browser/components/newtab/lib/MomentsPageHub.jsm174
1 files changed, 174 insertions, 0 deletions
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"];