diff options
Diffstat (limited to 'toolkit/components/messaging-system/lib/SpecialMessageActions.jsm')
-rw-r--r-- | toolkit/components/messaging-system/lib/SpecialMessageActions.jsm | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm b/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm new file mode 100644 index 0000000000..81eec6822f --- /dev/null +++ b/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm @@ -0,0 +1,404 @@ +/* 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 EXPORTED_SYMBOLS = ["SpecialMessageActions"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +const DOH_DOORHANGER_DECISION_PREF = "doh-rollout.doorhanger-decision"; +const NETWORK_TRR_MODE_PREF = "network.trr.mode"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.jsm", + UITour: "resource:///modules/UITour.jsm", + FxAccounts: "resource://gre/modules/FxAccounts.jsm", + Spotlight: "resource://activity-stream/lib/Spotlight.jsm", + ColorwayClosetOpener: "resource:///modules/ColorwayClosetOpener.jsm", +}); + +const SpecialMessageActions = { + // This is overridden by ASRouter.init + blockMessageById() { + throw new Error("ASRouter not intialized yet"); + }, + + /** + * loadAddonIconInURLBar - load addons-notification icon by displaying + * box containing addons icon in urlbar. See Bug 1513882 + * + * @param {Browser} browser browser element for showing addons icon + */ + loadAddonIconInURLBar(browser) { + if (!browser) { + return; + } + const chromeDoc = browser.ownerDocument; + let notificationPopupBox = chromeDoc.getElementById( + "notification-popup-box" + ); + if (!notificationPopupBox) { + return; + } + if ( + notificationPopupBox.style.display === "none" || + notificationPopupBox.style.display === "" + ) { + notificationPopupBox.style.display = "block"; + } + }, + + /** + * + * @param {Browser} browser The revelant Browser + * @param {string} url URL to look up install location + * @param {string} telemetrySource Telemetry information to pass to getInstallForURL + */ + async installAddonFromURL(browser, url, telemetrySource = "amo") { + try { + this.loadAddonIconInURLBar(browser); + const aUri = Services.io.newURI(url); + const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + + // AddonManager installation source associated to the addons installed from activitystream's CFR + // and RTAMO (source is going to be "amo" if not configured explicitly in the message provider). + const telemetryInfo = { source: telemetrySource }; + const install = await lazy.AddonManager.getInstallForURL(aUri.spec, { + telemetryInfo, + }); + await lazy.AddonManager.installAddonFromWebpage( + "application/x-xpinstall", + browser, + systemPrincipal, + install + ); + } catch (e) { + Cu.reportError(e); + } + }, + + /** + * Pin Firefox to taskbar. + * + * @param {Window} window Reference to a window object + * @param {boolean} pin Private Browsing Mode if true + */ + pinFirefoxToTaskbar(window, privateBrowsing = false) { + return window.getShellService().pinToTaskbar(privateBrowsing); + }, + + /** + * Set browser as the operating system default browser. + * + * @param {Window} window Reference to a window object + */ + setDefaultBrowser(window) { + window.getShellService().setAsDefault(); + }, + + /** + * Reset browser homepage and newtab to default with a certain section configuration + * + * @param {"default"|null} home Value to set for browser homepage + * @param {"default"|null} newtab Value to set for browser newtab + * @param {obj} layout Configuration options for newtab sections + * @returns {undefined} + */ + configureHomepage({ homePage = null, newtab = null, layout = null }) { + // Homepage can be default, blank or a custom url + if (homePage === "default") { + Services.prefs.clearUserPref("browser.startup.homepage"); + } + // Newtab page can only be default or blank + if (newtab === "default") { + Services.prefs.clearUserPref("browser.newtabpage.enabled"); + } + if (layout) { + // Existing prefs that interact with the newtab page layout, we default to true + // or payload configuration + let newtabConfigurations = [ + [ + // controls the search bar + "browser.newtabpage.activity-stream.showSearch", + layout.search, + ], + [ + // controls the topsites + "browser.newtabpage.activity-stream.feeds.topsites", + layout.topsites, + // User can control number of topsite rows + ["browser.newtabpage.activity-stream.topSitesRows"], + ], + [ + // controls the highlights section + "browser.newtabpage.activity-stream.feeds.section.highlights", + layout.highlights, + // User can control number of rows and highlight sources + [ + "browser.newtabpage.activity-stream.section.highlights.rows", + "browser.newtabpage.activity-stream.section.highlights.includeVisited", + "browser.newtabpage.activity-stream.section.highlights.includePocket", + "browser.newtabpage.activity-stream.section.highlights.includeDownloads", + "browser.newtabpage.activity-stream.section.highlights.includeBookmarks", + ], + ], + [ + // controls the snippets section + "browser.newtabpage.activity-stream.feeds.snippets", + layout.snippets, + ], + [ + // controls the topstories section + "browser.newtabpage.activity-stream.feeds.system.topstories", + layout.topstories, + ], + ].filter( + // If a section has configs that the user changed we will skip that section + ([, , sectionConfigs]) => + !sectionConfigs || + sectionConfigs.every( + prefName => !Services.prefs.prefHasUserValue(prefName) + ) + ); + + for (let [prefName, prefValue] of newtabConfigurations) { + Services.prefs.setBoolPref(prefName, prefValue); + } + } + }, + + /** + * Set prefs with special message actions + * + * @param {Object} pref - A pref to be updated. + * @param {string} pref.name - The name of the pref to be updated + * @param {string} [pref.value] - The value of the pref to be updated. If not included, the pref will be reset. + */ + setPref(pref) { + // Array of prefs that are allowed to be edited by SET_PREF + const allowedPrefs = [ + "browser.dataFeatureRecommendations.enabled", + "browser.startup.homepage", + "browser.privateWindowSeparation.enabled", + "browser.firefox-view.feature-tour", + "browser.pdfjs.feature-tour", + ]; + + if (!allowedPrefs.includes(pref.name)) { + throw new Error( + `Special message action with type SET_PREF and pref of "${pref.name}" is unsupported.` + ); + } + // If pref has no value, reset it, otherwise set it to desired value + switch (typeof pref.value) { + case "object": + case "undefined": + Services.prefs.clearUserPref(pref.name); + break; + case "string": + Services.prefs.setStringPref(pref.name, pref.value); + break; + case "number": + Services.prefs.setIntPref(pref.name, pref.value); + break; + case "boolean": + Services.prefs.setBoolPref(pref.name, pref.value); + break; + default: + throw new Error( + `Special message action with type SET_PREF, pref of "${pref.name}" is an unsupported type.` + ); + } + }, + + /** + * Processes "Special Message Actions", which are definitions of behaviors such as opening tabs + * installing add-ons, or focusing the awesome bar that are allowed to can be triggered from + * Messaging System interactions. + * + * @param {{type: string, data?: any}} action User action defined in message JSON. + * @param browser {Browser} The browser most relevant to the message. + */ + async handleAction(action, browser) { + const window = browser.ownerGlobal; + switch (action.type) { + case "SHOW_MIGRATION_WIZARD": + Services.tm.dispatchToMainThread(() => + lazy.MigrationUtils.showMigrationWizard(window, { + entrypoint: lazy.MigrationUtils.MIGRATION_ENTRYPOINTS.NEWTAB, + migratorKey: action.data?.source, + }) + ); + break; + case "OPEN_PRIVATE_BROWSER_WINDOW": + // Forcefully open about:privatebrowsing + window.OpenBrowserWindow({ private: true }); + break; + case "OPEN_URL": + window.openLinkIn( + Services.urlFormatter.formatURL(action.data.args), + action.data.where || "current", + { + private: false, + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal( + {} + ), + csp: null, + } + ); + break; + case "OPEN_ABOUT_PAGE": + let aboutPageURL = new URL(`about:${action.data.args}`); + if (action.data.entrypoint) { + aboutPageURL.search = action.data.entrypoint; + } + window.openTrustedLinkIn( + aboutPageURL.toString(), + action.data.where || "tab" + ); + break; + case "OPEN_FIREFOX_VIEW": + window.FirefoxViewHandler.openTab(); + break; + case "OPEN_PREFERENCES_PAGE": + window.openPreferences( + action.data.category || action.data.args, + action.data.entrypoint && { + urlParams: { entrypoint: action.data.entrypoint }, + } + ); + break; + case "OPEN_APPLICATIONS_MENU": + lazy.UITour.showMenu(window, action.data.args); + break; + case "HIGHLIGHT_FEATURE": + const highlight = await lazy.UITour.getTarget(window, action.data.args); + if (highlight) { + await lazy.UITour.showHighlight(window, highlight, "none", { + autohide: true, + }); + } + break; + case "INSTALL_ADDON_FROM_URL": + await this.installAddonFromURL( + browser, + action.data.url, + action.data.telemetrySource + ); + break; + case "PIN_FIREFOX_TO_TASKBAR": + await this.pinFirefoxToTaskbar(window, action.data?.privatePin); + break; + case "PIN_AND_DEFAULT": + await this.pinFirefoxToTaskbar(window, action.data?.privatePin); + this.setDefaultBrowser(window); + break; + case "SET_DEFAULT_BROWSER": + this.setDefaultBrowser(window); + break; + case "PIN_CURRENT_TAB": + let tab = window.gBrowser.selectedTab; + window.gBrowser.pinTab(tab); + window.ConfirmationHint.show(tab, "confirmation-hint-pin-tab", { + descriptionId: "confirmation-hint-pin-tab-description", + }); + break; + case "SHOW_FIREFOX_ACCOUNTS": + if (!(await lazy.FxAccounts.canConnectAccount())) { + break; + } + const data = action.data; + const url = await lazy.FxAccounts.config.promiseConnectAccountURI( + (data && data.entrypoint) || "snippets", + (data && data.extraParams) || {} + ); + // Use location provided; if not specified, replace the current tab. + window.openLinkIn(url, data.where || "current", { + private: false, + triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal( + {} + ), + csp: null, + }); + break; + case "OPEN_PROTECTION_PANEL": + let { gProtectionsHandler } = window; + gProtectionsHandler.showProtectionsPopup({}); + break; + case "OPEN_PROTECTION_REPORT": + window.gProtectionsHandler.openProtections(); + break; + case "OPEN_AWESOME_BAR": + window.gURLBar.search(""); + break; + case "DISABLE_STP_DOORHANGERS": + await this.blockMessageById([ + "SOCIAL_TRACKING_PROTECTION", + "FINGERPRINTERS_PROTECTION", + "CRYPTOMINERS_PROTECTION", + ]); + break; + case "DISABLE_DOH": + Services.prefs.setStringPref( + DOH_DOORHANGER_DECISION_PREF, + "UIDisabled" + ); + Services.prefs.setIntPref(NETWORK_TRR_MODE_PREF, 5); + break; + case "ACCEPT_DOH": + Services.prefs.setStringPref(DOH_DOORHANGER_DECISION_PREF, "UIOk"); + break; + case "CANCEL": + // A no-op used by CFRs that minimizes the notification but does not + // trigger a dismiss or block (it keeps the notification around) + break; + case "CONFIGURE_HOMEPAGE": + this.configureHomepage(action.data); + break; + case "SHOW_SPOTLIGHT": + lazy.Spotlight.showSpotlightDialog(browser, action.data); + break; + case "BLOCK_MESSAGE": + await this.blockMessageById(action.data.id); + break; + case "SET_PREF": + this.setPref(action.data.pref); + break; + case "MULTI_ACTION": + await Promise.all( + action.data.actions.map(async action => { + try { + await this.handleAction(action, browser); + } catch (err) { + throw new Error(`Error in MULTI_ACTION event: ${err.message}`); + } + }) + ); + break; + default: + throw new Error( + `Special message action with type ${action.type} is unsupported.` + ); + case "CLICK_ELEMENT": + const clickElement = window.document.querySelector( + action.data.selector + ); + clickElement?.click(); + break; + case "OPEN_FIREFOX_VIEW_AND_COLORWAYS_MODAL": + window.FirefoxViewHandler.openTab(); + lazy.ColorwayClosetOpener.openModal({ + source: "firefoxview", + }); + break; + } + }, +}; |