ToastNotification.sys.mjs (4323B)
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 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 11 EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs", 12 RemoteL10n: "resource:///modules/asrouter/RemoteL10n.sys.mjs", 13 }); 14 15 XPCOMUtils.defineLazyServiceGetters(lazy, { 16 AlertsService: ["@mozilla.org/alerts-service;1", Ci.nsIAlertsService], 17 }); 18 19 export const ToastNotification = { 20 // Allow testing to stub the alerts service. 21 get AlertsService() { 22 return lazy.AlertsService; 23 }, 24 25 sendUserEventTelemetry(event, message, dispatch) { 26 const ping = { 27 message_id: message.id, 28 event, 29 }; 30 dispatch({ 31 type: "TOAST_NOTIFICATION_TELEMETRY", 32 data: { action: "toast_notification_user_event", ...ping }, 33 }); 34 }, 35 36 /** 37 * Show a toast notification. 38 * 39 * @param message Message containing content to show. 40 * @param dispatch A function to dispatch resulting actions. 41 * @return boolean value capturing if toast notification was displayed. 42 */ 43 async showToastNotification(message, dispatch) { 44 let { content } = message; 45 let title = await lazy.RemoteL10n.formatLocalizableText(content.title); 46 let body = await lazy.RemoteL10n.formatLocalizableText(content.body); 47 48 // The only link between background task message experiment and user 49 // re-engagement via the notification is the associated "tag". Said tag is 50 // usually controlled by the message content, but for message experiments, 51 // we want to avoid a missing tag and to ensure a deterministic tag for 52 // easier analysis, including across branches. 53 let { tag } = content; 54 55 let experimentMetadata = 56 lazy.NimbusFeatures.backgroundTaskMessage.getEnrollmentMetadata( 57 lazy.EnrollmentType.EXPERIMENT 58 ); 59 60 if (experimentMetadata) { 61 // Like `my-experiment:my-branch`. 62 tag = `${experimentMetadata.slug}:${experimentMetadata.branch}`; 63 } 64 65 // There are two events named `IMPRESSION` the first one refers to telemetry 66 // while the other refers to ASRouter impressions used for the frequency cap 67 this.sendUserEventTelemetry("IMPRESSION", message, dispatch); 68 dispatch({ type: "IMPRESSION", data: message }); 69 70 let alert = Cc["@mozilla.org/alert-notification;1"].createInstance( 71 Ci.nsIAlertNotification 72 ); 73 let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); 74 alert.init( 75 tag, 76 content.image_url 77 ? Services.urlFormatter.formatURL(content.image_url) 78 : content.image_url, 79 title, 80 body, 81 true /* aTextClickable */, 82 content.data, 83 null /* aDir */, 84 null /* aLang */, 85 null /* aData */, 86 systemPrincipal, 87 null /* aInPrivateBrowsing */, 88 content.requireInteraction 89 ); 90 91 if (content.actions) { 92 let actions = Cu.cloneInto(content.actions, {}); 93 for (let action of actions) { 94 if (action.title) { 95 action.title = await lazy.RemoteL10n.formatLocalizableText( 96 action.title 97 ); 98 } 99 if (action.launch_action) { 100 action.opaqueRelaunchData = JSON.stringify(action.launch_action); 101 delete action.launch_action; 102 } 103 } 104 alert.actions = actions; 105 } 106 107 // Populate `opaqueRelaunchData`, prefering `launch_action` if given, 108 // falling back to `launch_url` if given. 109 let relaunchAction = content.launch_action; 110 if (!relaunchAction && content.launch_url) { 111 relaunchAction = { 112 type: "OPEN_URL", 113 data: { 114 args: content.launch_url, 115 where: "tab", 116 }, 117 }; 118 } 119 if (relaunchAction) { 120 alert.opaqueRelaunchData = JSON.stringify(relaunchAction); 121 } 122 123 let shownPromise = Promise.withResolvers(); 124 let obs = (subject, topic) => { 125 if (topic === "alertshow") { 126 shownPromise.resolve(); 127 } 128 }; 129 130 this.AlertsService.showAlert(alert, obs); 131 132 await shownPromise; 133 134 return true; 135 }, 136 };