ASRouterTelemetry.sys.mjs (9153B)
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 // We use importESModule here instead of static import so that the Karma test 6 // environment won't choke on these module. This is because the Karma test 7 // environment already stubs out XPCOMUtils, AppConstants and RemoteSettings, 8 // and overrides importESModule to be a no-op (which can't be done for a static 9 // import statement). 10 11 // eslint-disable-next-line mozilla/use-static-import 12 const { XPCOMUtils } = ChromeUtils.importESModule( 13 "resource://gre/modules/XPCOMUtils.sys.mjs" 14 ); 15 16 // eslint-disable-next-line mozilla/use-static-import 17 const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.importESModule( 18 "resource:///modules/asrouter/ActorConstants.mjs" 19 ); 20 21 const lazy = {}; 22 23 ChromeUtils.defineESModuleGetters(lazy, { 24 ClientID: "resource://gre/modules/ClientID.sys.mjs", 25 AboutWelcomeTelemetry: 26 "resource:///modules/aboutwelcome/AboutWelcomeTelemetry.sys.mjs", 27 EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs", 28 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 29 TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs", 30 UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs", 31 }); 32 ChromeUtils.defineLazyGetter( 33 lazy, 34 "Telemetry", 35 () => new lazy.AboutWelcomeTelemetry() 36 ); 37 38 ChromeUtils.defineLazyGetter( 39 lazy, 40 "browserSessionId", 41 () => lazy.TelemetrySession.getMetadata("").sessionId 42 ); 43 44 export const PREF_IMPRESSION_ID = 45 "browser.newtabpage.activity-stream.impressionId"; 46 47 export class ASRouterTelemetry { 48 constructor() { 49 this._impressionId = this.getOrCreateImpressionId(); 50 XPCOMUtils.defineLazyPreferenceGetter( 51 this, 52 "telemetryEnabled", 53 "browser.newtabpage.activity-stream.telemetry", 54 false 55 ); 56 } 57 58 get telemetryClientId() { 59 Object.defineProperty(this, "telemetryClientId", { 60 value: lazy.ClientID.getClientID(), 61 }); 62 return this.telemetryClientId; 63 } 64 65 getOrCreateImpressionId() { 66 let impressionId = Services.prefs.getCharPref(PREF_IMPRESSION_ID, ""); 67 if (!impressionId) { 68 impressionId = String(Services.uuid.generateUUID()); 69 Services.prefs.setCharPref(PREF_IMPRESSION_ID, impressionId); 70 } 71 return impressionId; 72 } 73 /** 74 * Check if it is in the CFR experiment cohort by querying against the 75 * experiment manager of Messaging System 76 * 77 * @return {bool} 78 */ 79 get isInCFRCohort() { 80 return !!lazy.NimbusFeatures.cfr.getEnrollmentMetadata( 81 lazy.EnrollmentType.EXPERIMENT 82 ); 83 } 84 85 /** 86 * Create a ping for AS router event. The client_id is set to "n/a" by default, 87 * different component can override this by its own telemetry collection policy. 88 */ 89 async createASRouterEvent(action) { 90 let event = { 91 ...action.data, 92 addon_version: Services.appinfo.appBuildID, 93 locale: Services.locale.appLocaleAsBCP47, 94 }; 95 96 if (event.event_context && typeof event.event_context === "object") { 97 event.event_context = JSON.stringify(event.event_context); 98 } 99 switch (event.action) { 100 case "cfr_user_event": 101 event = await this.applyCFRPolicy(event); 102 break; 103 case "badge_user_event": 104 event = await this.applyToolbarBadgePolicy(event); 105 break; 106 case "infobar_user_event": 107 event = await this.applyInfoBarPolicy(event); 108 break; 109 case "spotlight_user_event": 110 event = await this.applySpotlightPolicy(event); 111 break; 112 case "toast_notification_user_event": 113 event = await this.applyToastNotificationPolicy(event); 114 break; 115 case "moments_user_event": 116 event = await this.applyMomentsPolicy(event); 117 break; 118 case "menu_message_user_event": 119 event = await this.applyMenuMessagePolicy(event); 120 break; 121 case "asrouter_undesired_event": 122 event = this.applyUndesiredEventPolicy(event); 123 break; 124 case "newtab_message_user_event": 125 event = await this.applyNewtabMessagePolicy(event); 126 break; 127 default: 128 event = { ping: event }; 129 break; 130 } 131 return event; 132 } 133 134 /** 135 * Per Bug 1484035, CFR metrics comply with following policies: 136 * 1). In release, it collects impression_id and bucket_id 137 * 2). In prerelease, it collects client_id and message_id 138 * 3). In shield experiments conducted in release, it collects client_id and message_id 139 * 4). In Private Browsing windows, unless in experiment, collects impression_id and bucket_id 140 */ 141 async applyCFRPolicy(ping) { 142 if ( 143 (lazy.UpdateUtils.getUpdateChannel(true) === "release" || 144 ping.is_private) && 145 !this.isInCFRCohort 146 ) { 147 ping.message_id = "n/a"; 148 ping.impression_id = this._impressionId; 149 } else { 150 ping.client_id = await this.telemetryClientId; 151 } 152 delete ping.action; 153 delete ping.is_private; 154 return { ping, pingType: "cfr" }; 155 } 156 157 /** 158 * Per Bug 1482134, all the metrics for What's New panel use client_id in 159 * all the release channels 160 */ 161 async applyToolbarBadgePolicy(ping) { 162 ping.client_id = await this.telemetryClientId; 163 ping.browser_session_id = lazy.browserSessionId; 164 // Attach page info to `event_context` if there is a session associated with this ping 165 delete ping.action; 166 return { ping, pingType: "toolbar-badge" }; 167 } 168 169 async applyInfoBarPolicy(ping) { 170 ping.client_id = await this.telemetryClientId; 171 ping.browser_session_id = lazy.browserSessionId; 172 delete ping.action; 173 return { ping, pingType: "infobar" }; 174 } 175 176 async applySpotlightPolicy(ping) { 177 ping.client_id = await this.telemetryClientId; 178 ping.browser_session_id = lazy.browserSessionId; 179 delete ping.action; 180 return { ping, pingType: "spotlight" }; 181 } 182 183 async applyToastNotificationPolicy(ping) { 184 ping.client_id = await this.telemetryClientId; 185 ping.browser_session_id = lazy.browserSessionId; 186 delete ping.action; 187 return { ping, pingType: "toast_notification" }; 188 } 189 190 async applyMenuMessagePolicy(ping) { 191 ping.client_id = await this.telemetryClientId; 192 ping.browser_session_id = lazy.browserSessionId; 193 delete ping.action; 194 return { ping, pingType: "menu" }; 195 } 196 197 /** 198 * Per Bug 1484035, Moments metrics comply with following policies: 199 * 1). In release, it collects impression_id, and treats bucket_id as message_id 200 * 2). In prerelease, it collects client_id and message_id 201 * 3). In shield experiments conducted in release, it collects client_id and message_id 202 */ 203 async applyMomentsPolicy(ping) { 204 if ( 205 lazy.UpdateUtils.getUpdateChannel(true) === "release" && 206 !this.isInCFRCohort 207 ) { 208 ping.message_id = "n/a"; 209 ping.impression_id = this._impressionId; 210 } else { 211 ping.client_id = await this.telemetryClientId; 212 } 213 delete ping.action; 214 return { ping, pingType: "moments" }; 215 } 216 217 async applyNewtabMessagePolicy(ping) { 218 ping.client_id = await this.telemetryClientId; 219 ping.browser_session_id = lazy.browserSessionId; 220 ping.addon_version = Services.appinfo.appBuildID; 221 ping.locale = Services.locale.appLocaleAsBCP47; 222 delete ping.action; 223 return { ping, pingType: "newtab_message" }; 224 } 225 226 applyUndesiredEventPolicy(ping) { 227 ping.impression_id = this._impressionId; 228 delete ping.action; 229 return { ping, pingType: "undesired-events" }; 230 } 231 232 async handleASRouterUserEvent(action) { 233 const { ping, pingType } = await this.createASRouterEvent(action); 234 if (!pingType) { 235 console.error("Unknown ping type for ASRouter telemetry"); 236 return; 237 } 238 239 // Now that the action has become a ping, we can echo it to Glean. 240 if (this.telemetryEnabled) { 241 lazy.Telemetry.submitGleanPingForPing({ ...ping, pingType }); 242 } 243 } 244 245 /** 246 * This function is used by ActivityStreamStorage to report errors 247 * trying to access IndexedDB. 248 */ 249 SendASRouterUndesiredEvent(data) { 250 this.handleASRouterUserEvent({ 251 data: { ...data, action: "asrouter_undesired_event" }, 252 }); 253 } 254 255 onAction(action) { 256 switch (action.type) { 257 // The remaining action types come from ASRouter, which doesn't use 258 // Actions from Actions.mjs, but uses these other custom strings. 259 case msg.TOOLBAR_BADGE_TELEMETRY: 260 // Intentional fall-through 261 case msg.TOOLBAR_PANEL_TELEMETRY: 262 // Intentional fall-through 263 case msg.MOMENTS_PAGE_TELEMETRY: 264 // Intentional fall-through 265 case msg.DOORHANGER_TELEMETRY: 266 // Intentional fall-through 267 case msg.INFOBAR_TELEMETRY: 268 // Intentional fall-through 269 case msg.SPOTLIGHT_TELEMETRY: 270 // Intentional fall-through 271 case msg.TOAST_NOTIFICATION_TELEMETRY: 272 // Intentional fall-through 273 case msg.MENU_MESSAGE_TELEMETRY: 274 // Intentional fall-through 275 case msg.NEWTAB_MESSAGE_TELEMETRY: 276 // Intentional fall-through 277 case msg.AS_ROUTER_TELEMETRY_USER_EVENT: 278 this.handleASRouterUserEvent(action); 279 break; 280 } 281 } 282 }