AboutMessagePreviewParent.sys.mjs (6345B)
1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 import { ASRouter } from "resource:///modules/asrouter/ASRouter.sys.mjs"; 7 import { JsonSchema } from "resource://gre/modules/JsonSchema.sys.mjs"; 8 9 const lazy = {}; 10 11 ChromeUtils.defineESModuleGetters(lazy, { 12 AddonManager: "resource://gre/modules/AddonManager.sys.mjs", 13 BookmarksBarButton: "resource:///modules/asrouter/BookmarksBarButton.sys.mjs", 14 CFRPageActions: "resource:///modules/asrouter/CFRPageActions.sys.mjs", 15 CustomizableUI: 16 "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", 17 FeatureCalloutBroker: 18 "resource:///modules/asrouter/FeatureCalloutBroker.sys.mjs", 19 InfoBar: "resource:///modules/asrouter/InfoBar.sys.mjs", 20 SpecialMessageActions: 21 "resource://messaging-system/lib/SpecialMessageActions.sys.mjs", 22 Spotlight: "resource:///modules/asrouter/Spotlight.sys.mjs", 23 }); 24 25 const SWITCH_THEMES = { 26 DARK: "firefox-compact-dark@mozilla.org", 27 LIGHT: "firefox-compact-light@mozilla.org", 28 }; 29 30 function dispatchCFRAction({ type, data }, browser) { 31 if (type === "USER_ACTION") { 32 lazy.SpecialMessageActions.handleAction(data, browser); 33 } 34 } 35 36 export class AboutMessagePreviewParent extends JSWindowActorParent { 37 constructor() { 38 super(); 39 40 const EXISTING_THEME = Services.prefs.getStringPref( 41 "extensions.activeThemeID" 42 ); 43 44 this._onUnload = () => { 45 lazy.AddonManager.getAddonByID(EXISTING_THEME).then(addon => 46 addon.enable() 47 ); 48 }; 49 } 50 51 didDestroy() { 52 this._onUnload(); 53 } 54 55 showInfoBar(message, browser) { 56 lazy.InfoBar.showInfoBarMessage(browser, message, dispatchCFRAction); 57 } 58 59 showSpotlight(message, browser) { 60 lazy.Spotlight.showSpotlightDialog(browser, message, () => {}); 61 } 62 63 showBookmarksBarButton(message, browser) { 64 //ensure the bookmarks bar is open 65 lazy.CustomizableUI.setToolbarVisibility( 66 lazy.CustomizableUI.AREA_BOOKMARKS, 67 true 68 ); 69 //and then send the message 70 lazy.BookmarksBarButton.showBookmarksBarButton(browser, message); 71 } 72 73 showCFR(message, browser) { 74 lazy.CFRPageActions.forceRecommendation( 75 browser, 76 message, 77 dispatchCFRAction 78 ); 79 } 80 81 showPrivateBrowsingMessage(message, browser) { 82 ASRouter.forcePBWindow(browser, message); 83 } 84 85 async showFeatureCallout(message, browser) { 86 // Clear the Feature Tour prefs used by some callouts, to ensure 87 // the behaviour of the message is correct 88 let tourPref = message.content.tour_pref_name; 89 if (tourPref) { 90 Services.prefs.clearUserPref(tourPref); 91 } 92 // For messagePreview, force the trigger && targeting to be something we can show. 93 message.trigger = { id: "nthTabClosed" }; 94 message.targeting = "true"; 95 // Check whether or not the callout is showing already, then 96 // modify the anchor property of the feature callout to 97 // ensure it's something we can show. 98 let showing = await lazy.FeatureCalloutBroker.showFeatureCallout( 99 browser, 100 message 101 ); 102 if (!showing) { 103 for (const screen of message.content.screens) { 104 let existingAnchors = screen.anchors; 105 let fallbackAnchor = { selector: "#star-button-box" }; 106 107 if (existingAnchors[0].hasOwnProperty("arrow_position")) { 108 fallbackAnchor.arrow_position = "top-center-arrow-end"; 109 } else { 110 fallbackAnchor.panel_position = { 111 anchor_attachment: "bottomcenter", 112 callout_attachment: "topright", 113 }; 114 } 115 116 screen.anchors = [...existingAnchors, fallbackAnchor]; 117 console.log("ANCHORS: ", screen.anchors); 118 } 119 // Try showing again 120 await lazy.FeatureCalloutBroker.showFeatureCallout(browser, message); 121 } 122 } 123 124 /** 125 * Chooses the appropriate messaging system function for showing 126 * the message, based on the template passed in data 127 * 128 * @param {string} data - a string containing the message JSON 129 * @param {boolean} validationEnabled - whether or not to run 130 * schema validation on the message JSON. Should be false in 131 * tests so that we don't have to pass real messages or call 132 * the validation function. 133 */ 134 135 async showMessage(data, validationEnabled = true) { 136 let message; 137 try { 138 message = JSON.parse(data); 139 } catch (e) { 140 console.error("Could not parse message", e); 141 return; 142 } 143 144 if (validationEnabled) { 145 const schema = await fetch( 146 "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", 147 { credentials: "omit" } 148 ).then(rsp => rsp.json()); 149 const result = JsonSchema.validate(message, schema); 150 if (!result.valid) { 151 console.error( 152 `Invalid message: ${JSON.stringify(result.errors, undefined, 2)}` 153 ); 154 } 155 } 156 157 const browser = 158 this.browsingContext.topChromeWindow.gBrowser.selectedBrowser; 159 switch (message.template) { 160 case "infobar": 161 this.showInfoBar(message, browser); 162 return; 163 case "spotlight": 164 this.showSpotlight(message, browser); 165 return; 166 case "cfr_doorhanger": 167 this.showCFR(message, browser); 168 return; 169 case "feature_callout": 170 this.showFeatureCallout(message, browser); 171 return; 172 case "bookmarks_bar_button": 173 this.showBookmarksBarButton(message, browser); 174 return; 175 case "pb_newtab": 176 this.showPrivateBrowsingMessage(message, browser); 177 return; 178 default: 179 console.error(`Unsupported message template ${message.template}`); 180 } 181 } 182 183 receiveMessage(message) { 184 // validationEnabled is used for testing 185 const { name, data, validationEnabled } = message; 186 187 switch (name) { 188 case "MessagePreview:SHOW_MESSAGE": 189 this.showMessage(data, validationEnabled); 190 return; 191 case "MessagePreview:CHANGE_THEME": { 192 const theme = data.isDark ? SWITCH_THEMES.LIGHT : SWITCH_THEMES.DARK; 193 lazy.AddonManager.getAddonByID(theme).then(addon => addon.enable()); 194 return; 195 } 196 default: 197 console.log(`Unexpected event ${name} was not handled.`); 198 } 199 } 200 }