This commit uses a "pull" approach, where the experiment details are fished at presentation-time and the content `tag` updated. A few notes: - With this approach, the update function is pushed down to the leaf node (the toast notification presentation layer). It would be nice to do this at the experiment layer, but that layer doesn't resolve the presentation layer at this time, so it would perhaps violate the abstraction to lift the work higher. - No effort has been made to mark `tag` as invalid in the messaging experiment schemas. At this time, there's no provision for fields accepted at the presentation layer (`ToastNotification.schema.json`) but not at the experiment layer aggregating presentations (`BackgroundTaskMessagingExperiment.schema.json`, `MessagingExperiment.schema.json`). It's likely possible to arrange this but not worth the effort at this time. - The actual tag displayed is not captured in the message as it flows through ASRouter. This is not likely to pose a problem. - The actual tag displayed might be `optin-...`, potentially complicating data analysis. Since it's essentially impossible for regular users to opt-in to _background task_ messages, that's not a pressing concern. Differential Revision: https://phabricator.services.mozilla.com/D164508
119 lines
3.6 KiB
JavaScript
119 lines
3.6 KiB
JavaScript
/* 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 { XPCOMUtils } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
|
);
|
|
|
|
const lazy = {};
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
|
ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm",
|
|
RemoteL10n: "resource://activity-stream/lib/RemoteL10n.jsm",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
|
AlertsService: ["@mozilla.org/alerts-service;1", "nsIAlertsService"],
|
|
});
|
|
|
|
const ToastNotification = {
|
|
// Allow testing to stub the alerts service.
|
|
get AlertsService() {
|
|
return lazy.AlertsService;
|
|
},
|
|
|
|
sendUserEventTelemetry(event, message, dispatch) {
|
|
const ping = {
|
|
message_id: message.id,
|
|
event,
|
|
};
|
|
dispatch({
|
|
type: "TOAST_NOTIFICATION_TELEMETRY",
|
|
data: { action: "toast_notification_user_event", ...ping },
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Show a toast notification.
|
|
* @param message Message containing content to show.
|
|
* @param dispatch A function to dispatch resulting actions.
|
|
* @return boolean value capturing if toast notification was displayed.
|
|
*/
|
|
async showToastNotification(message, dispatch) {
|
|
let { content } = message;
|
|
let title = await lazy.RemoteL10n.formatLocalizableText(content.title);
|
|
let body = await lazy.RemoteL10n.formatLocalizableText(content.body);
|
|
|
|
// The only link between background task message experiment and user
|
|
// re-engagement via the notification is the associated "tag". Said tag is
|
|
// usually controlled by the message content, but for message experiments,
|
|
// we want to avoid a missing tag and to ensure a deterministic tag for
|
|
// easier analysis, including across branches.
|
|
let { tag } = content;
|
|
|
|
let experimentMetadata =
|
|
lazy.ExperimentAPI.getExperimentMetaData({
|
|
featureId: "backgroundTaskMessage",
|
|
}) || {};
|
|
|
|
if (
|
|
experimentMetadata?.active &&
|
|
experimentMetadata?.slug &&
|
|
experimentMetadata?.branch?.slug
|
|
) {
|
|
// Like `my-experiment:my-branch`.
|
|
tag = `${experimentMetadata?.slug}:${experimentMetadata?.branch?.slug}`;
|
|
}
|
|
|
|
// There are two events named `IMPRESSION` the first one refers to telemetry
|
|
// while the other refers to ASRouter impressions used for the frequency cap
|
|
this.sendUserEventTelemetry("IMPRESSION", message, dispatch);
|
|
dispatch({ type: "IMPRESSION", data: message });
|
|
|
|
let alert = Cc["@mozilla.org/alert-notification;1"].createInstance(
|
|
Ci.nsIAlertNotification
|
|
);
|
|
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
|
alert.init(
|
|
tag,
|
|
content.image_url
|
|
? Services.urlFormatter.formatURL(content.image_url)
|
|
: content.image_url,
|
|
title,
|
|
body,
|
|
true /* aTextClickable */,
|
|
content.data,
|
|
null /* aDir */,
|
|
null /* aLang */,
|
|
null /* aData */,
|
|
systemPrincipal,
|
|
null /* aInPrivateBrowsing */,
|
|
content.requireInteraction
|
|
);
|
|
|
|
if (content.actions) {
|
|
let actions = Cu.cloneInto(content.actions, {});
|
|
for (let action of actions) {
|
|
if (action.title) {
|
|
action.title = await lazy.RemoteL10n.formatLocalizableText(
|
|
action.title
|
|
);
|
|
}
|
|
}
|
|
alert.actions = actions;
|
|
}
|
|
|
|
if (content.launch_url) {
|
|
alert.launchURL = Services.urlFormatter.formatURL(content.launch_url);
|
|
}
|
|
|
|
this.AlertsService.showAlert(alert);
|
|
|
|
return true;
|
|
},
|
|
};
|
|
|
|
const EXPORTED_SYMBOLS = ["ToastNotification"];
|