diff options
Diffstat (limited to 'comm/mail/components/extensions/parent/ext-mailTabs.js')
-rw-r--r-- | comm/mail/components/extensions/parent/ext-mailTabs.js | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/comm/mail/components/extensions/parent/ext-mailTabs.js b/comm/mail/components/extensions/parent/ext-mailTabs.js new file mode 100644 index 0000000000..9cf0bc0844 --- /dev/null +++ b/comm/mail/components/extensions/parent/ext-mailTabs.js @@ -0,0 +1,485 @@ +/* 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/. */ + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + QuickFilterManager: "resource:///modules/QuickFilterManager.jsm", + MailServices: "resource:///modules/MailServices.jsm", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "gDynamicPaneConfig", + "mail.pane_config.dynamic", + 0 +); + +const LAYOUTS = ["standard", "wide", "vertical"]; +// From nsIMsgDBView.idl +const SORT_TYPE_MAP = new Map( + Object.keys(Ci.nsMsgViewSortType).map(key => { + // Change "byFoo" to "foo". + let shortKey = key[2].toLowerCase() + key.substring(3); + return [Ci.nsMsgViewSortType[key], shortKey]; + }) +); +const SORT_ORDER_MAP = new Map( + Object.keys(Ci.nsMsgViewSortOrder).map(key => [ + Ci.nsMsgViewSortOrder[key], + key, + ]) +); + +/** + * Converts a mail tab to a simple object for use in messages. + * + * @returns {object} + */ +function convertMailTab(tab, context) { + let mailTabObject = { + id: tab.id, + windowId: tab.windowId, + active: tab.active, + sortType: null, + sortOrder: null, + viewType: null, + layout: LAYOUTS[gDynamicPaneConfig], + folderPaneVisible: null, + messagePaneVisible: null, + }; + + let about3Pane = tab.nativeTab.chromeBrowser.contentWindow; + let { gViewWrapper, paneLayout } = about3Pane; + mailTabObject.folderPaneVisible = paneLayout.folderPaneVisible; + mailTabObject.messagePaneVisible = paneLayout.messagePaneVisible; + mailTabObject.sortType = SORT_TYPE_MAP.get(gViewWrapper?.primarySortType); + mailTabObject.sortOrder = SORT_ORDER_MAP.get(gViewWrapper?.primarySortOrder); + if (gViewWrapper?.showGroupedBySort) { + mailTabObject.viewType = "groupedBySortType"; + } else if (gViewWrapper?.showThreaded) { + mailTabObject.viewType = "groupedByThread"; + } else { + mailTabObject.viewType = "ungrouped"; + } + if (context.extension.hasPermission("accountsRead")) { + mailTabObject.displayedFolder = convertFolder(about3Pane.gFolder); + } + return mailTabObject; +} + +/** + * Listens for changes in the UI to fire events. + */ +var uiListener = new (class extends EventEmitter { + constructor() { + super(); + this.listenerCount = 0; + this.handleEvent = this.handleEvent.bind(this); + this.lastSelected = new WeakMap(); + } + + handleEvent(event) { + let browser = event.target.browsingContext.embedderElement; + let tabmail = browser.ownerGlobal.top.document.getElementById("tabmail"); + let nativeTab = tabmail.tabInfo.find( + t => + t.chromeBrowser == browser || + t.chromeBrowser == browser.browsingContext.parent.embedderElement + ); + + if (nativeTab.mode.name != "mail3PaneTab") { + return; + } + + let tabId = tabTracker.getId(nativeTab); + let tab = tabTracker.getTab(tabId); + + if (event.type == "folderURIChanged") { + let folderURI = event.detail; + let folder = MailServices.folderLookup.getFolderForURL(folderURI); + if (this.lastSelected.get(tab) == folder) { + return; + } + this.lastSelected.set(tab, folder); + this.emit("folder-changed", tab, folder); + } else if (event.type == "messageURIChanged") { + let messages = + nativeTab.chromeBrowser.contentWindow.gDBView?.getSelectedMsgHdrs(); + if (messages) { + this.emit("messages-changed", tab, messages); + } + } + } + + incrementListeners() { + this.listenerCount++; + if (this.listenerCount == 1) { + windowTracker.addListener("folderURIChanged", this); + windowTracker.addListener("messageURIChanged", this); + } + } + decrementListeners() { + this.listenerCount--; + if (this.listenerCount == 0) { + windowTracker.removeListener("folderURIChanged", this); + windowTracker.removeListener("messageURIChanged", this); + this.lastSelected = new WeakMap(); + } + } +})(); + +this.mailTabs = class extends ExtensionAPIPersistent { + PERSISTENT_EVENTS = { + // For primed persistent events (deactivated background), the context is only + // available after fire.wakeup() has fulfilled (ensuring the convert() function + // has been called). + + onDisplayedFolderChanged({ context, fire }) { + const { extension } = this; + const { tabManager } = extension; + async function listener(event, tab, folder) { + if (fire.wakeup) { + await fire.wakeup(); + } + fire.sync(tabManager.convert(tab), convertFolder(folder)); + } + uiListener.on("folder-changed", listener); + uiListener.incrementListeners(); + return { + unregister: () => { + uiListener.off("folder-changed", listener); + uiListener.decrementListeners(); + }, + convert(newFire, extContext) { + fire = newFire; + context = extContext; + }, + }; + }, + onSelectedMessagesChanged({ context, fire }) { + const { extension } = this; + const { tabManager } = extension; + async function listener(event, tab, messages) { + if (fire.wakeup) { + await fire.wakeup(); + } + let page = await messageListTracker.startList(messages, extension); + fire.sync(tabManager.convert(tab), page); + } + uiListener.on("messages-changed", listener); + uiListener.incrementListeners(); + return { + unregister: () => { + uiListener.off("messages-changed", listener); + uiListener.decrementListeners(); + }, + convert(newFire, extContext) { + fire = newFire; + context = extContext; + }, + }; + }, + }; + + getAPI(context) { + let { extension } = context; + let { tabManager } = extension; + + /** + * Gets the tab for the given tab id, or the active tab if the id is null. + * + * @param {?Integer} tabId - The tab id to get + * @returns {Tab} The matching tab, or the active tab + */ + async function getTabOrActive(tabId) { + let tab; + if (tabId) { + tab = tabManager.get(tabId); + } else { + tab = tabManager.wrapTab(tabTracker.activeTab); + tabId = tab.id; + } + + if (tab && tab.type == "mail") { + let windowId = windowTracker.getId(getTabWindow(tab.nativeTab)); + // Before doing anything with the mail tab, ensure its outer window is + // fully loaded. + await getNormalWindowReady(context, windowId); + return tab; + } + throw new ExtensionError(`Invalid mail tab ID: ${tabId}`); + } + + /** + * Set the currently displayed folder in the given tab. + * + * @param {NativeTabInfo} nativeTabInfo + * @param {nsIMsgFolder} folder + * @param {boolean} restorePreviousSelection - Select the previously selected + * messages of the folder, after it has been set. + */ + async function setFolder(nativeTabInfo, folder, restorePreviousSelection) { + let about3Pane = nativeTabInfo.chromeBrowser.contentWindow; + if (!nativeTabInfo.folder || nativeTabInfo.folder.URI != folder.URI) { + await new Promise(resolve => { + let listener = event => { + if (event.detail == folder.URI) { + about3Pane.removeEventListener("folderURIChanged", listener); + resolve(); + } + }; + about3Pane.addEventListener("folderURIChanged", listener); + if (restorePreviousSelection) { + about3Pane.restoreState({ + folderURI: folder.URI, + }); + } else { + about3Pane.threadPane.forgetSelection(folder.URI); + nativeTabInfo.folder = folder; + } + }); + } + } + + return { + mailTabs: { + async query({ active, currentWindow, lastFocusedWindow, windowId }) { + await getNormalWindowReady(); + return Array.from( + tabManager.query( + { + active, + currentWindow, + lastFocusedWindow, + mailTab: true, + windowId, + + // All of these are needed for tabManager to return every tab we want. + cookieStoreId: null, + index: null, + screen: null, + title: null, + url: null, + windowType: null, + }, + context + ), + tab => convertMailTab(tab, context) + ); + }, + + async get(tabId) { + let tab = await getTabOrActive(tabId); + return convertMailTab(tab, context); + }, + async getCurrent() { + try { + let tab = await getTabOrActive(); + return convertMailTab(tab, context); + } catch (e) { + // Do not throw, if the active tab is not a mail tab, but return undefined. + return undefined; + } + }, + + async update(tabId, args) { + let tab = await getTabOrActive(tabId); + let { nativeTab } = tab; + let about3Pane = nativeTab.chromeBrowser.contentWindow; + + let { + displayedFolder, + layout, + folderPaneVisible, + messagePaneVisible, + sortOrder, + sortType, + viewType, + } = args; + + if (displayedFolder) { + if (!extension.hasPermission("accountsRead")) { + throw new ExtensionError( + 'Updating the displayed folder requires the "accountsRead" permission' + ); + } + + let folderUri = folderPathToURI( + displayedFolder.accountId, + displayedFolder.path + ); + let folder = MailServices.folderLookup.getFolderForURL(folderUri); + if (!folder) { + throw new ExtensionError( + `Folder "${displayedFolder.path}" for account ` + + `"${displayedFolder.accountId}" not found.` + ); + } + await setFolder(nativeTab, folder, true); + } + + if (sortType) { + // Change "foo" to "byFoo". + sortType = "by" + sortType[0].toUpperCase() + sortType.substring(1); + if ( + sortType in Ci.nsMsgViewSortType && + sortOrder && + sortOrder in Ci.nsMsgViewSortOrder + ) { + about3Pane.gViewWrapper.sort( + Ci.nsMsgViewSortType[sortType], + Ci.nsMsgViewSortOrder[sortOrder] + ); + } + } + + switch (viewType) { + case "groupedBySortType": + about3Pane.gViewWrapper.showGroupedBySort = true; + break; + case "groupedByThread": + about3Pane.gViewWrapper.showThreaded = true; + break; + case "ungrouped": + about3Pane.gViewWrapper.showUnthreaded = true; + break; + } + + // Layout applies to all folder tabs. + if (layout) { + Services.prefs.setIntPref( + "mail.pane_config.dynamic", + LAYOUTS.indexOf(layout) + ); + } + + if (typeof folderPaneVisible == "boolean") { + about3Pane.paneLayout.folderPaneVisible = folderPaneVisible; + } + if (typeof messagePaneVisible == "boolean") { + about3Pane.paneLayout.messagePaneVisible = messagePaneVisible; + } + }, + + async getSelectedMessages(tabId) { + let tab = await getTabOrActive(tabId); + let dbView = tab.nativeTab.chromeBrowser.contentWindow?.gDBView; + let messageList = dbView ? dbView.getSelectedMsgHdrs() : []; + return messageListTracker.startList(messageList, extension); + }, + + async setSelectedMessages(tabId, messageIds) { + if ( + !extension.hasPermission("messagesRead") || + !extension.hasPermission("accountsRead") + ) { + throw new ExtensionError( + 'Using mailTabs.setSelectedMessages() requires the "accountsRead" and the "messagesRead" permission' + ); + } + + let tab = await getTabOrActive(tabId); + let refFolder, refMsgId; + let msgHdrs = []; + for (let messageId of messageIds) { + let msgHdr = messageTracker.getMessage(messageId); + if (!refFolder) { + refFolder = msgHdr.folder; + refMsgId = messageId; + } + if (msgHdr.folder == refFolder) { + msgHdrs.push(msgHdr); + } else { + throw new ExtensionError( + `Message ${refMsgId} and message ${messageId} are not in the same folder, cannot select them both.` + ); + } + } + + if (refFolder) { + await setFolder(tab.nativeTab, refFolder, false); + } + let about3Pane = tab.nativeTab.chromeBrowser.contentWindow; + const selectedIndices = msgHdrs.map( + about3Pane.gViewWrapper.getViewIndexForMsgHdr, + about3Pane.gViewWrapper + ); + about3Pane.threadTree.selectedIndices = selectedIndices; + if (selectedIndices.length) { + about3Pane.threadTree.scrollToIndex(selectedIndices[0], true); + } + }, + + async setQuickFilter(tabId, state) { + let tab = await getTabOrActive(tabId); + let nativeTab = tab.nativeTab; + let about3Pane = nativeTab.chromeBrowser.contentWindow; + + let filterer = about3Pane.quickFilterBar.filterer; + filterer.clear(); + + // Map of QuickFilter state names to possible WebExtensions state names. + let stateMap = { + unread: "unread", + starred: "flagged", + addrBook: "contact", + attachment: "attachment", + }; + + filterer.visible = state.show !== false; + for (let [key, name] of Object.entries(stateMap)) { + filterer.setFilterValue(key, state[name]); + } + + if (state.tags) { + filterer.filterValues.tags = { + mode: "OR", + tags: {}, + }; + for (let tag of MailServices.tags.getAllTags()) { + filterer.filterValues.tags[tag.key] = null; + } + if (typeof state.tags == "object") { + filterer.filterValues.tags.mode = + state.tags.mode == "any" ? "OR" : "AND"; + for (let [key, value] of Object.entries(state.tags.tags)) { + filterer.filterValues.tags.tags[key] = value; + } + } + } + if (state.text) { + filterer.filterValues.text = { + states: { + recipients: state.text.recipients || false, + sender: state.text.author || false, + subject: state.text.subject || false, + body: state.text.body || false, + }, + text: state.text.text, + }; + } + + about3Pane.quickFilterBar.updateSearch(); + }, + + onDisplayedFolderChanged: new EventManager({ + context, + module: "mailTabs", + event: "onDisplayedFolderChanged", + extensionApi: this, + }).api(), + + onSelectedMessagesChanged: new EventManager({ + context, + module: "mailTabs", + event: "onSelectedMessagesChanged", + extensionApi: this, + }).api(), + }, + }; + } +}; |