diff options
Diffstat (limited to 'comm/mail/components/extensions/parent/ext-browserAction.js')
-rw-r--r-- | comm/mail/components/extensions/parent/ext-browserAction.js | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/comm/mail/components/extensions/parent/ext-browserAction.js b/comm/mail/components/extensions/parent/ext-browserAction.js new file mode 100644 index 0000000000..de07f9e3a2 --- /dev/null +++ b/comm/mail/components/extensions/parent/ext-browserAction.js @@ -0,0 +1,329 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* 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"; + +ChromeUtils.defineESModuleGetters(this, { + storeState: "resource:///modules/CustomizationState.mjs", + getState: "resource:///modules/CustomizationState.mjs", + registerExtension: "resource:///modules/CustomizableItems.sys.mjs", + unregisterExtension: "resource:///modules/CustomizableItems.sys.mjs", + EXTENSION_PREFIX: "resource:///modules/CustomizableItems.sys.mjs", +}); +var { ExtensionCommon } = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionCommon.sys.mjs" +); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +XPCOMUtils.defineLazyModuleGetters(this, { + ToolbarButtonAPI: "resource:///modules/ExtensionToolbarButtons.jsm", + getCachedAllowedSpaces: "resource:///modules/ExtensionToolbarButtons.jsm", + setCachedAllowedSpaces: "resource:///modules/ExtensionToolbarButtons.jsm", +}); + +var { makeWidgetId } = ExtensionCommon; + +const browserActionMap = new WeakMap(); + +this.browserAction = class extends ToolbarButtonAPI { + static for(extension) { + return browserActionMap.get(extension); + } + + /** + * A browser_action can be placed in the unified toolbar of the main window and + * in the XUL toolbar of the message window. We conditionally bypass XUL toolbar + * behavior by using the following custom method implementations. + */ + + paint(window) { + // Ignore XUL toolbar paint requests for the main window. + if (window.location.href != MAIN_WINDOW_URI) { + super.paint(window); + } + } + + unpaint(window) { + // Ignore XUL toolbar unpaint requests for the main window. + if (window.location.href != MAIN_WINDOW_URI) { + super.unpaint(window); + } + } + + /** + * Return the toolbar button if it is currently visible in the given window. + * + * @param window + * @returns {DOMElement} the toolbar button element, or null + */ + getToolbarButton(window) { + // Return the visible button from the unified toolbar, if this is the main window. + if (window.location.href == MAIN_WINDOW_URI) { + let buttonItem = window.document.querySelector( + `#unifiedToolbarContent [item-id="ext-${this.extension.id}"]` + ); + return ( + buttonItem && + !buttonItem.hidden && + window.document.querySelector( + `#unifiedToolbarContent [extension="${this.extension.id}"]` + ) + ); + } + return super.getToolbarButton(window); + } + + updateButton(button, tabData) { + if (button.applyTabData) { + // This is an extension-action-button customElement and therefore a button + // in the unified toolbar and needs special handling. + button.applyTabData(tabData); + } else { + super.updateButton(button, tabData); + } + } + + async onManifestEntry(entryName) { + await super.onManifestEntry(entryName); + browserActionMap.set(this.extension, this); + + // Check if a browser_action was added to the unified toolbar. + if (this.windowURLs.includes(MAIN_WINDOW_URI)) { + await registerExtension(this.extension.id, this.allowedSpaces); + const currentToolbarState = getState(); + const unifiedToolbarButtonId = `${EXTENSION_PREFIX}${this.extension.id}`; + + // Load the cached allowed spaces. Make sure there are no awaited promises + // before storing the updated allowed spaces, as it could have been changed + // elsewhere. + let cachedAllowedSpaces = getCachedAllowedSpaces(); + let priorAllowedSpaces = cachedAllowedSpaces.get(this.extension.id); + + // If the extension has set allowedSpaces to an empty array, the button needs + // to be added to all available spaces. + let allowedSpaces = + this.allowedSpaces.length == 0 + ? [ + "mail", + "addressbook", + "calendar", + "tasks", + "chat", + "settings", + "default", + ] + : this.allowedSpaces; + + // Manually add the button to all customized spaces, where it has not been + // allowed in the prior version of this add-on (if any). This automatically + // covers the install and the update case, including staged updates. + // Spaces which have not been customized will receive the button from + // getDefaultItemIdsForSpace() in CustomizableItems.sys.mjs. + let missingSpacesInState = allowedSpaces.filter( + space => + (!priorAllowedSpaces || !priorAllowedSpaces.includes(space)) && + space !== "default" && + currentToolbarState.hasOwnProperty(space) && + !currentToolbarState[space].includes(unifiedToolbarButtonId) + ); + for (const space of missingSpacesInState) { + currentToolbarState[space].push(unifiedToolbarButtonId); + } + + // Manually remove button from all customized spaces, if it is no longer + // allowed. This will remove its stored customized positioning information. + // If a space becomes allowed again later, the button will be added to the + // end of the space and not at its former customized location. + let invalidSpacesInState = []; + if (priorAllowedSpaces) { + invalidSpacesInState = priorAllowedSpaces.filter( + space => + space !== "default" && + !allowedSpaces.includes(space) && + currentToolbarState.hasOwnProperty(space) && + currentToolbarState[space].includes(unifiedToolbarButtonId) + ); + for (const space of invalidSpacesInState) { + currentToolbarState[space] = currentToolbarState[space].filter( + id => id != unifiedToolbarButtonId + ); + } + } + + // Update the cached values for the allowed spaces. + cachedAllowedSpaces.set(this.extension.id, allowedSpaces); + setCachedAllowedSpaces(cachedAllowedSpaces); + + if (missingSpacesInState.length || invalidSpacesInState.length) { + storeState(currentToolbarState); + } else { + Services.obs.notifyObservers(null, "unified-toolbar-state-change"); + } + } + } + + close() { + super.close(); + browserActionMap.delete(this.extension); + windowTracker.removeListener("TabSelect", this); + // Unregister the extension from the unified toolbar. + if (this.windowURLs.includes(MAIN_WINDOW_URI)) { + unregisterExtension(this.extension.id); + Services.obs.notifyObservers(null, "unified-toolbar-state-change"); + } + } + + constructor(extension) { + super(extension, global); + this.manifest_name = + extension.manifestVersion < 3 ? "browser_action" : "action"; + this.manifestName = + extension.manifestVersion < 3 ? "browserAction" : "action"; + this.manifest = extension.manifest[this.manifest_name]; + // browserAction was renamed to action in MV3, but its module name is + // still "browserAction" because that is the name used in ext-mail.json, + // independently from the manifest version. + this.moduleName = "browserAction"; + + this.windowURLs = []; + if (this.manifest.default_windows.includes("normal")) { + this.windowURLs.push(MAIN_WINDOW_URI); + } + if (this.manifest.default_windows.includes("messageDisplay")) { + this.windowURLs.push(MESSAGE_WINDOW_URI); + } + + this.toolboxId = "mail-toolbox"; + this.toolbarId = "mail-bar3"; + + this.allowedSpaces = + this.extension.manifest[this.manifest_name].allowed_spaces; + + windowTracker.addListener("TabSelect", this); + } + + static onUpdate(extensionId, manifest) { + // These manifest entries can exist and be null. + if (!manifest.browser_action && !manifest.action) { + this.#removeFromUnifiedToolbar(extensionId); + } + } + + static onUninstall(extensionId) { + let widgetId = makeWidgetId(extensionId); + let id = `${widgetId}-browserAction-toolbarbutton`; + + // Check all possible XUL toolbars and remove the toolbarbutton if found. + // Sadly we have to hardcode these values here, as the add-on is already + // shutdown when onUninstall is called. + let toolbars = ["mail-bar3", "toolbar-menubar"]; + for (let toolbar of toolbars) { + for (let setName of ["currentset", "extensionset"]) { + let set = Services.xulStore + .getValue(MESSAGE_WINDOW_URI, toolbar, setName) + .split(","); + let newSet = set.filter(e => e != id); + if (newSet.length < set.length) { + Services.xulStore.setValue( + MESSAGE_WINDOW_URI, + toolbar, + setName, + newSet.join(",") + ); + } + } + } + + this.#removeFromUnifiedToolbar(extensionId); + } + + static #removeFromUnifiedToolbar(extensionId) { + const currentToolbarState = getState(); + const unifiedToolbarButtonId = `${EXTENSION_PREFIX}${extensionId}`; + let modifiedState = false; + for (const space of Object.keys(currentToolbarState)) { + if (currentToolbarState[space].includes(unifiedToolbarButtonId)) { + currentToolbarState[space].splice( + currentToolbarState[space].indexOf(unifiedToolbarButtonId), + 1 + ); + modifiedState = true; + } + } + if (modifiedState) { + storeState(currentToolbarState); + } + + // Update cachedAllowedSpaces for the unified toolbar. + let cachedAllowedSpaces = getCachedAllowedSpaces(); + if (cachedAllowedSpaces.has(extensionId)) { + cachedAllowedSpaces.delete(extensionId); + setCachedAllowedSpaces(cachedAllowedSpaces); + } + } + + handleEvent(event) { + super.handleEvent(event); + let window = event.target.ownerGlobal; + + switch (event.type) { + case "popupshowing": + const menu = event.target; + if (menu.tagName != "menupopup") { + return; + } + + // This needs to work in normal window and message window. + let tab = tabTracker.activeTab; + let browser = tab.linkedBrowser || tab.getBrowser?.(); + + const trigger = menu.triggerNode; + const node = + window.document.getElementById(this.id) || + (this.windowURLs.includes(MAIN_WINDOW_URI) && + window.document.querySelector( + `#unifiedToolbarContent [item-id="${EXTENSION_PREFIX}${this.extension.id}"]` + )); + const contexts = [ + "toolbar-context-menu", + "customizationPanelItemContextMenu", + "unifiedToolbarMenu", + ]; + if (contexts.includes(menu.id) && node && node.contains(trigger)) { + const action = + this.extension.manifestVersion < 3 ? "onBrowserAction" : "onAction"; + global.actionContextMenu({ + tab, + pageUrl: browser?.currentURI?.spec, + extension: this.extension, + [action]: true, + menu, + }); + } + + if ( + menu.dataset.actionMenu == this.manifestName && + this.extension.id == menu.dataset.extensionId + ) { + const action = + this.extension.manifestVersion < 3 + ? "inBrowserActionMenu" + : "inActionMenu"; + global.actionContextMenu({ + tab, + pageUrl: browser?.currentURI?.spec, + extension: this.extension, + [action]: true, + menu, + }); + } + break; + } + } +}; + +global.browserActionFor = this.browserAction.for; |