AboutWelcomeChild.sys.mjs (11408B)
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 AboutWelcomeDefaults: 11 "resource:///modules/aboutwelcome/AboutWelcomeDefaults.sys.mjs", 12 EnrollmentType: "resource://nimbus/ExperimentAPI.sys.mjs", 13 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 14 }); 15 16 ChromeUtils.defineLazyGetter(lazy, "log", () => { 17 const { Logger } = ChromeUtils.importESModule( 18 "resource://messaging-system/lib/Logger.sys.mjs" 19 ); 20 return new Logger("AboutWelcomeChild"); 21 }); 22 23 const DID_SEE_FINAL_SCREEN_PREF = "browser.aboutwelcome.didSeeFinalScreen"; 24 25 XPCOMUtils.defineLazyPreferenceGetter( 26 lazy, 27 "toolbarEntrypoint", 28 "browser.aboutwelcome.entrypoint", 29 "" 30 ); 31 32 export class AboutWelcomeChild extends JSWindowActorChild { 33 // Can be used to avoid accesses to the document/contentWindow after it's 34 // destroyed, which may throw unhandled exceptions. 35 _destroyed = false; 36 37 didDestroy() { 38 this._destroyed = true; 39 } 40 41 actorCreated() { 42 this.exportFunctions(); 43 } 44 45 /** 46 * Send event that can be handled by the page 47 * 48 * @param {{type: string, data?: any}} action 49 */ 50 sendToPage(action) { 51 lazy.log.debug(`Sending to page: ${action.type}`); 52 const win = this.document.defaultView; 53 const event = new win.CustomEvent("AboutWelcomeChromeToContent", { 54 detail: Cu.cloneInto(action, win), 55 }); 56 win.dispatchEvent(event); 57 } 58 59 /** 60 * Export functions that can be called by page js 61 */ 62 exportFunctions() { 63 let window = this.contentWindow; 64 65 Cu.exportFunction(this.AWAddScreenImpression.bind(this), window, { 66 defineAs: "AWAddScreenImpression", 67 }); 68 69 Cu.exportFunction(this.AWGetFeatureConfig.bind(this), window, { 70 defineAs: "AWGetFeatureConfig", 71 }); 72 73 Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, { 74 defineAs: "AWGetFxAMetricsFlowURI", 75 }); 76 77 Cu.exportFunction(this.AWGetSelectedTheme.bind(this), window, { 78 defineAs: "AWGetSelectedTheme", 79 }); 80 81 Cu.exportFunction(this.AWSelectTheme.bind(this), window, { 82 defineAs: "AWSelectTheme", 83 }); 84 85 Cu.exportFunction(this.AWEvaluateScreenTargeting.bind(this), window, { 86 defineAs: "AWEvaluateScreenTargeting", 87 }); 88 89 Cu.exportFunction(this.AWEvaluateAttributeTargeting.bind(this), window, { 90 defineAs: "AWEvaluateAttributeTargeting", 91 }); 92 93 Cu.exportFunction(this.AWSendEventTelemetry.bind(this), window, { 94 defineAs: "AWSendEventTelemetry", 95 }); 96 97 Cu.exportFunction(this.AWSendToParent.bind(this), window, { 98 defineAs: "AWSendToParent", 99 }); 100 101 Cu.exportFunction(this.AWWaitForMigrationClose.bind(this), window, { 102 defineAs: "AWWaitForMigrationClose", 103 }); 104 105 Cu.exportFunction(this.AWFinish.bind(this), window, { 106 defineAs: "AWFinish", 107 }); 108 109 Cu.exportFunction(this.AWGetInstalledAddons.bind(this), window, { 110 defineAs: "AWGetInstalledAddons", 111 }); 112 113 Cu.exportFunction(this.AWEnsureAddonInstalled.bind(this), window, { 114 defineAs: "AWEnsureAddonInstalled", 115 }); 116 117 Cu.exportFunction(this.AWEnsureLangPackInstalled.bind(this), window, { 118 defineAs: "AWEnsureLangPackInstalled", 119 }); 120 121 Cu.exportFunction( 122 this.AWNegotiateLangPackForLanguageMismatch.bind(this), 123 window, 124 { 125 defineAs: "AWNegotiateLangPackForLanguageMismatch", 126 } 127 ); 128 129 Cu.exportFunction(this.AWSetRequestedLocales.bind(this), window, { 130 defineAs: "AWSetRequestedLocales", 131 }); 132 133 Cu.exportFunction(this.AWSendToDeviceEmailsSupported.bind(this), window, { 134 defineAs: "AWSendToDeviceEmailsSupported", 135 }); 136 137 Cu.exportFunction(this.AWNewScreen.bind(this), window, { 138 defineAs: "AWNewScreen", 139 }); 140 141 Cu.exportFunction(this.AWGetUnhandledCampaignAction.bind(this), window, { 142 defineAs: "AWGetUnhandledCampaignAction", 143 }); 144 145 Cu.exportFunction( 146 this.AWFindBackupsInWellKnownLocations.bind(this), 147 window, 148 { defineAs: "AWFindBackupsInWellKnownLocations" } 149 ); 150 151 Cu.exportFunction(this.RPMGetFormatURLPref.bind(this), window, { 152 defineAs: "RPMGetFormatURLPref", 153 }); 154 } 155 156 /** 157 * Wrap a promise so content can use Promise methods. 158 */ 159 wrapPromise(promise) { 160 return new this.contentWindow.Promise((resolve, reject) => 161 promise.then(resolve, reject) 162 ); 163 } 164 165 /** 166 * Clones the result of the query into the content window. 167 */ 168 sendQueryAndCloneForContent(...sendQueryArgs) { 169 return this.wrapPromise( 170 (async () => { 171 return Cu.cloneInto( 172 await this.sendQuery(...sendQueryArgs), 173 this.contentWindow 174 ); 175 })() 176 ); 177 } 178 179 AWSelectTheme(data) { 180 return this.wrapPromise( 181 this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase()) 182 ); 183 } 184 185 AWEvaluateScreenTargeting(data) { 186 return this.sendQueryAndCloneForContent( 187 "AWPage:EVALUATE_SCREEN_TARGETING", 188 data 189 ); 190 } 191 192 AWEvaluateAttributeTargeting(data) { 193 return this.wrapPromise( 194 this.sendQuery("AWPage:EVALUATE_ATTRIBUTE_TARGETING", data) 195 ); 196 } 197 198 AWAddScreenImpression(screen) { 199 return this.wrapPromise( 200 this.sendQuery("AWPage:ADD_SCREEN_IMPRESSION", screen) 201 ); 202 } 203 204 AWFindBackupsInWellKnownLocations(data) { 205 return this.sendQueryAndCloneForContent( 206 "AWPage:BACKUP_FIND_WELL_KNOWN", 207 data 208 ); 209 } 210 211 /** 212 * Send initial data to page including experiment information 213 */ 214 async getAWContent() { 215 let attributionData = await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA"); 216 217 let experimentMetadata = 218 lazy.NimbusFeatures.aboutwelcome.getEnrollmentMetadata( 219 lazy.EnrollmentType.EXPERIMENT 220 ) ?? {}; 221 222 lazy.log.debug( 223 `Loading about:welcome with ${ 224 experimentMetadata?.slug ?? "no" 225 } experiment` 226 ); 227 228 let featureConfig = lazy.NimbusFeatures.aboutwelcome.getAllVariables(); 229 featureConfig.needDefault = await this.sendQuery("AWPage:NEED_DEFAULT"); 230 featureConfig.needPin = await this.sendQuery("AWPage:DOES_APP_NEED_PIN"); 231 if (featureConfig.languageMismatchEnabled) { 232 featureConfig.appAndSystemLocaleInfo = await this.sendQuery( 233 "AWPage:GET_APP_AND_SYSTEM_LOCALE_INFO" 234 ); 235 } 236 237 // FeatureConfig (from experiments) has higher precendence 238 // to defaults. But the `screens` property isn't defined we shouldn't 239 // override the default with `null` 240 let defaults = lazy.AboutWelcomeDefaults.getDefaults(); 241 242 const content = await lazy.AboutWelcomeDefaults.prepareContentForReact({ 243 ...attributionData, 244 ...experimentMetadata, 245 ...defaults, 246 ...featureConfig, 247 screens: featureConfig.screens ?? defaults.screens, 248 backdrop: featureConfig.backdrop ?? defaults.backdrop, 249 }); 250 251 return Cu.cloneInto(content, this.contentWindow); 252 } 253 254 AWGetFeatureConfig() { 255 return this.wrapPromise(this.getAWContent()); 256 } 257 258 AWGetFxAMetricsFlowURI() { 259 return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI")); 260 } 261 262 AWGetSelectedTheme() { 263 return this.wrapPromise(this.sendQuery("AWPage:GET_SELECTED_THEME")); 264 } 265 266 /** 267 * Send Event Telemetry 268 * 269 * @param {object} eventData 270 */ 271 AWSendEventTelemetry(eventData) { 272 if (lazy.toolbarEntrypoint) { 273 eventData.event_context.entrypoint = lazy.toolbarEntrypoint; 274 } 275 this.AWSendToParent("TELEMETRY_EVENT", { 276 ...eventData, 277 event_context: { 278 ...eventData.event_context, 279 }, 280 }); 281 } 282 283 /** 284 * Send message that can be handled by AboutWelcomeParent.sys.mjs 285 * 286 * @param {string} type 287 * @param {any=} data 288 * @returns {Promise<unknown>} 289 */ 290 AWSendToParent(type, data) { 291 return this.sendQueryAndCloneForContent(`AWPage:${type}`, data); 292 } 293 294 AWWaitForMigrationClose() { 295 return this.wrapPromise(this.sendQuery("AWPage:WAIT_FOR_MIGRATION_CLOSE")); 296 } 297 298 setDidSeeFinalScreen() { 299 this.AWSendToParent("SPECIAL_ACTION", { 300 type: "SET_PREF", 301 data: { 302 pref: { 303 name: DID_SEE_FINAL_SCREEN_PREF, 304 value: true, 305 }, 306 }, 307 }); 308 } 309 310 focusUrlBar() { 311 this.AWSendToParent("SPECIAL_ACTION", { 312 type: "FOCUS_URLBAR", 313 }); 314 } 315 316 AWFinish() { 317 this.setDidSeeFinalScreen(); 318 319 this.contentWindow.location.href = "about:home"; 320 this.focusUrlBar(); 321 } 322 323 AWEnsureAddonInstalled(addonId) { 324 return this.wrapPromise( 325 this.sendQuery("AWPage:ENSURE_ADDON_INSTALLED", addonId) 326 ); 327 } 328 329 AWGetInstalledAddons() { 330 return this.wrapPromise( 331 this.sendQueryAndCloneForContent("AWPage:GET_INSTALLED_ADDONS") 332 ); 333 } 334 335 AWEnsureLangPackInstalled(negotiated, screenContent) { 336 const content = Cu.cloneInto(screenContent, {}); 337 return this.wrapPromise( 338 this.sendQuery( 339 "AWPage:ENSURE_LANG_PACK_INSTALLED", 340 negotiated.langPack 341 ).then(() => { 342 const formatting = []; 343 const l10n = new Localization( 344 ["branding/brand.ftl", "browser/newtab/onboarding.ftl"], 345 false, 346 undefined, 347 // Use the system-ish then app then default locale. 348 [...negotiated.requestSystemLocales, "en-US"] 349 ); 350 351 // Add the negotiated language name as args. 352 function addMessageArgsAndUseLangPack(obj) { 353 for (const value of Object.values(obj)) { 354 if (value?.string_id) { 355 value.args = { 356 ...value.args, 357 negotiatedLanguage: negotiated.langPackDisplayName, 358 }; 359 360 // Expose fluent strings wanting lang pack as raw. 361 if (value.useLangPack) { 362 formatting.push( 363 l10n.formatValue(value.string_id, value.args).then(raw => { 364 delete value.string_id; 365 value.raw = raw; 366 }) 367 ); 368 } 369 } 370 } 371 } 372 addMessageArgsAndUseLangPack(content.languageSwitcher); 373 addMessageArgsAndUseLangPack(content); 374 return Promise.all(formatting).then(() => 375 Cu.cloneInto(content, this.contentWindow) 376 ); 377 }) 378 ); 379 } 380 381 AWSetRequestedLocales(requestSystemLocales) { 382 return this.sendQueryAndCloneForContent( 383 "AWPage:SET_REQUESTED_LOCALES", 384 requestSystemLocales 385 ); 386 } 387 388 AWNegotiateLangPackForLanguageMismatch(appAndSystemLocaleInfo) { 389 return this.sendQueryAndCloneForContent( 390 "AWPage:NEGOTIATE_LANGPACK", 391 appAndSystemLocaleInfo 392 ); 393 } 394 395 AWSendToDeviceEmailsSupported() { 396 return this.wrapPromise( 397 this.sendQuery("AWPage:SEND_TO_DEVICE_EMAILS_SUPPORTED") 398 ); 399 } 400 401 AWNewScreen(screenId) { 402 return this.wrapPromise(this.sendQuery("AWPage:NEW_SCREEN", screenId)); 403 } 404 405 AWGetUnhandledCampaignAction() { 406 return this.sendQueryAndCloneForContent( 407 "AWPage:GET_UNHANDLED_CAMPAIGN_ACTION" 408 ); 409 } 410 411 RPMGetFormatURLPref(formatURL) { 412 return Services.urlFormatter.formatURLPref(formatURL); 413 } 414 415 /** 416 * @param {{type: string, detail?: any}} event 417 * @override 418 */ 419 handleEvent(event) { 420 lazy.log.debug(`Received page event ${event.type}`); 421 } 422 }