diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mail/modules/MailUtils.jsm | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/comm/mail/modules/MailUtils.jsm b/comm/mail/modules/MailUtils.jsm new file mode 100644 index 0000000000..7ba78005b1 --- /dev/null +++ b/comm/mail/modules/MailUtils.jsm @@ -0,0 +1,820 @@ +/* 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 EXPORTED_SYMBOLS = ["MailUtils"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; + +XPCOMUtils.defineLazyModuleGetters(lazy, { + MailConsts: "resource:///modules/MailConsts.jsm", + MailServices: "resource:///modules/MailServices.jsm", + MimeParser: "resource:///modules/mimeParser.jsm", + NetUtil: "resource://gre/modules/NetUtil.jsm", +}); + +ChromeUtils.defineESModuleGetters(lazy, { + PluralForm: "resource://gre/modules/PluralForm.sys.mjs", +}); + +/** + * This module has several utility functions for use by both core and + * third-party code. Some functions are aimed at code that doesn't have a + * window context, while others can be used anywhere. + */ +var MailUtils = { + /** + * Restarts the application, keeping it in + * safe mode if it is already in safe mode. + */ + restartApplication() { + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( + Ci.nsISupportsPRBool + ); + Services.obs.notifyObservers( + cancelQuit, + "quit-application-requested", + "restart" + ); + if (cancelQuit.data) { + return; + } + // If already in safe mode restart in safe mode. + if (Services.appinfo.inSafeMode) { + Services.startup.restartInSafeMode( + Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart + ); + return; + } + Services.startup.quit( + Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart + ); + }, + + /** + * Discover all folders. This is useful during startup, when you have code + * that deals with folders and that executes before the main 3pane window is + * open (the folder tree wouldn't have been initialized yet). + */ + discoverFolders() { + for (let server of lazy.MailServices.accounts.allServers) { + // Bug 466311 Sometimes this can throw file not found, we're unsure + // why, but catch it and log the fact. + try { + server.rootFolder.subFolders; + } catch (ex) { + Services.console.logStringMessage( + "Discovering folders for account failed with exception: " + ex + ); + } + } + }, + + /** + * Get the nsIMsgFolder corresponding to this file. This just looks at all + * folders and does a direct match. + * + * One of the places this is used is desktop search integration -- to open + * the search result corresponding to a mozeml/wdseml file, we need to figure + * out the folder using the file's path. + * + * @param aFile the nsIFile to convert to a folder + * @returns the nsIMsgFolder corresponding to aFile, or null if the folder + * isn't found + */ + getFolderForFileInProfile(aFile) { + for (let folder of lazy.MailServices.accounts.allFolders) { + if (folder.filePath.equals(aFile)) { + return folder; + } + } + return null; + }, + + /** + * Get the nsIMsgFolder corresponding to this URI. + * + * @param aFolderURI the URI of the target folder + * @returns {nsIMsgFolder} Folder corresponding to this URI, or null if + * the folder doesn't already exist. + */ + getExistingFolder(aFolderURI) { + let fls = Cc["@mozilla.org/mail/folder-lookup;1"].getService( + Ci.nsIFolderLookupService + ); + return fls.getFolderForURL(aFolderURI); + }, + + /** + * Get the nsIMsgFolder corresponding to this URI, or create a detached + * folder if it doesn't already exist. + * + * @param aFolderURI the URI of the target folder + * @returns {nsIMsgFolder} Folder corresponding to this URI. + */ + getOrCreateFolder(aFolderURI) { + let fls = Cc["@mozilla.org/mail/folder-lookup;1"].getService( + Ci.nsIFolderLookupService + ); + return fls.getOrCreateFolderForURL(aFolderURI); + }, + + /** + * Display this message header in a new tab, a new window or an existing + * window, depending on the preference and whether a 3pane or standalone + * window is already open. This function should be called when you'd like to + * display a message to the user according to the pref set. + * + * @note Do not use this if you want to open multiple messages at once. Use + * |displayMessages| instead. + * + * @param {nsIMsgHdr} aMsgHdr - The message header to display. + * @param {DBViewWrapper} [aViewWrapperToClone] - A view wrapper to clone. + * If null or not given, the message header's folder's default view will + * be used. + * @param {Element} [aTabmail] - A tabmail element to use in case we need to + * open tabs. If null or not given: + * - if one or more 3pane windows are open, the most recent one's tabmail + * is used, and the window is brought to the front + * - if no 3pane windows are open, a standalone window is opened instead + * of a tab + */ + displayMessage(aMsgHdr, aViewWrapperToClone, aTabmail) { + this.displayMessages([aMsgHdr], aViewWrapperToClone, aTabmail); + }, + + /** + * Display the warning if the number of messages to be displayed is greater than + * the limit set in preferences. + * + * @param aNumMessages: number of messages to be displayed + * @param aConfirmTitle: title ID + * @param aConfirmMsg: message ID + * @param aLiitingPref: the name of the pref to retrieve the limit from + */ + confirmAction(aNumMessages, aConfirmTitle, aConfirmMsg, aLimitingPref) { + let openWarning = Services.prefs.getIntPref(aLimitingPref); + if (openWarning > 1 && aNumMessages >= openWarning) { + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/messenger.properties" + ); + let title = bundle.GetStringFromName(aConfirmTitle); + let message = lazy.PluralForm.get( + aNumMessages, + bundle.GetStringFromName(aConfirmMsg) + ).replace("#1", aNumMessages); + if (!Services.prompt.confirm(null, title, message)) { + return true; + } + } + return false; + }, + /** + * Display these message headers in new tabs, new windows or existing + * windows, depending on the preference, the number of messages, and whether + * a 3pane or standalone window is already open. This function should be + * called when you'd like to display multiple messages to the user according + * to the pref set. + * + * @param {nsIMsgHdr[]} aMsgHdrs - An array containing the message headers to + * display. The array should contain at least one message header. + * @param {DBViewWrapper} [aViewWrapperToClone] - A DB view wrapper to clone + * for each of the tabs or windows. + * @param {Element} [aTabmail] - A tabmail element to use in case we need to + * open tabs. If given, the window containing the tabmail is assumed to be + * in front. If null or not given: + * - if one or more 3pane windows are open, the most recent one's tabmail + * is used, and the window is brought to the front + * - if no 3pane windows are open, a standalone window is opened instead + * of a tab + */ + displayMessages( + aMsgHdrs, + aViewWrapperToClone, + aTabmail, + useBackgroundPref = false + ) { + let openMessageBehavior = Services.prefs.getIntPref( + "mail.openMessageBehavior" + ); + + if (openMessageBehavior == lazy.MailConsts.OpenMessageBehavior.NEW_WINDOW) { + this.openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone); + } else if ( + openMessageBehavior == lazy.MailConsts.OpenMessageBehavior.EXISTING_WINDOW + ) { + // Try reusing an existing window. If we can't, fall back to opening new + // windows + if ( + aMsgHdrs.length > 1 || + !this.openMessageInExistingWindow(aMsgHdrs[0]) + ) { + this.openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone); + } + } else if ( + openMessageBehavior == lazy.MailConsts.OpenMessageBehavior.NEW_TAB + ) { + let mail3PaneWindow = null; + if (!aTabmail) { + // Try opening new tabs in a 3pane window + mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + if (mail3PaneWindow) { + aTabmail = mail3PaneWindow.document.getElementById("tabmail"); + } + } + + if (aTabmail) { + if ( + this.confirmAction( + aMsgHdrs.length, + "openTabWarningTitle", + "openTabWarningConfirmation", + "mailnews.open_tab_warning" + ) + ) { + return; + } + const loadInBackground = useBackgroundPref + ? Services.prefs.getBoolPref("mail.tabs.loadInBackground") + : false; + + // Open all the tabs in the background, except for the last one + for (let [i, msgHdr] of aMsgHdrs.entries()) { + aTabmail.openTab("mailMessageTab", { + messageURI: msgHdr.folder.getUriForMsg(msgHdr), + viewWrapper: aViewWrapperToClone, + background: i < aMsgHdrs.length - 1 || loadInBackground, + disregardOpener: aMsgHdrs.length > 1, + }); + } + + if (mail3PaneWindow) { + mail3PaneWindow.focus(); + } + } else { + // We still haven't found a tabmail, so we'll need to open new windows + this.openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone); + } + } + }, + + /** + * Show this message in an existing window. + * + * @param {nsIMsgHdr} aMsgHdr - The message header to display. + * @param {DBViewWrapper} [aViewWrapperToClone] - A DB view wrapper to clone + * for the message window. + * @returns {boolean} true if an existing window was found and the message + * header was displayed, false otherwise. + */ + openMessageInExistingWindow(aMsgHdr, aViewWrapperToClone) { + let messageWindow = Services.wm.getMostRecentWindow("mail:messageWindow"); + if (messageWindow) { + messageWindow.displayMessage(aMsgHdr, aViewWrapperToClone); + return true; + } + return false; + }, + + /** + * Open a new standalone message window with this header. + * + * @param {nsIMsgHdr} aMsgHdr the message header to display + * @param {DBViewWrapper} [aViewWrapperToClone] - A DB view wrapper to clone + * for the message window. + * @returns {DOMWindow} the opened window + */ + openMessageInNewWindow(aMsgHdr, aViewWrapperToClone) { + // It sucks that we have to go through XPCOM for this. + let args = { msgHdr: aMsgHdr, viewWrapperToClone: aViewWrapperToClone }; + args.wrappedJSObject = args; + + return Services.ww.openWindow( + null, + "chrome://messenger/content/messageWindow.xhtml", + "", + "all,chrome,dialog=no,status,toolbar", + args + ); + }, + + /** + * Open new standalone message windows for these headers. This will prompt + * for confirmation if the number of windows to be opened is greater than the + * value of the mailnews.open_window_warning preference. + * + * @param {nsIMsgHdr[]} aMsgHdrs - An array containing the message headers + * to display. + * @param {DBViewWrapper} [aViewWrapperToClone] - A DB view wrapper to clone + * for each message window. + */ + openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone) { + if ( + this.confirmAction( + aMsgHdrs.length, + "openWindowWarningTitle", + "openWindowWarningConfirmation", + "mailnews.open_window_warning" + ) + ) { + return; + } + + for (let msgHdr of aMsgHdrs) { + this.openMessageInNewWindow(msgHdr, aViewWrapperToClone); + } + }, + + /** + * Display the given folder in the 3pane of the most recent 3pane window. + * + * @param {string} folderURI - The URI of the folder to display + */ + displayFolderIn3Pane(folderURI) { + // Try opening new tabs in a 3pane window + let win = Services.wm.getMostRecentWindow("mail:3pane"); + let tabmail = win.document.getElementById("tabmail"); + if (!tabmail.currentAbout3Pane) { + tabmail.switchToTab(tabmail.tabInfo[0]); + tabmail.updateCurrentTab(); + } + tabmail.currentAbout3Pane.displayFolder(folderURI); + win.focus(); + }, + + /** + * Display this message header in a folder tab in a 3pane window. This is + * useful when the message needs to be displayed in the context of its folder + * or thread. + * + * @param {nsIMsgHdr} msgHdr - The message header to display. + * @param {boolean} [openIfMessagePaneHidden] - If true, and the folder tab's + * message pane is hidden, opens the message in a new tab or window. + * Otherwise uses the folder tab. + */ + displayMessageInFolderTab(msgHdr, openIfMessagePaneHidden) { + // Try opening new tabs in a 3pane window + let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + if (mail3PaneWindow) { + if (openIfMessagePaneHidden) { + let tab = mail3PaneWindow.document.getElementById("tabmail").tabInfo[0]; + if (!tab.chromeBrowser.contentWindow.paneLayout.messagePaneVisible) { + this.displayMessage(msgHdr); + return; + } + } + + mail3PaneWindow.MsgDisplayMessageInFolderTab(msgHdr); + if (Ci.nsIMessengerWindowsIntegration) { + Cc["@mozilla.org/messenger/osintegration;1"] + .getService(Ci.nsIMessengerWindowsIntegration) + .showWindow(mail3PaneWindow); + } + mail3PaneWindow.focus(); + } else { + let args = { msgHdr }; + args.wrappedJSObject = args; + Services.ww.openWindow( + null, + "chrome://messenger/content/messenger.xhtml", + "", + "all,chrome,dialog=no,status,toolbar", + args + ); + } + }, + + /** + * Open a message from a message id. + * + * @param {string} msgId - The message id string without the brackets. + */ + openMessageByMessageId(msgId) { + let msgHdr = this.getMsgHdrForMsgId(msgId); + if (msgHdr) { + this.displayMessage(msgHdr); + return; + } + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/messenger.properties" + ); + let errorTitle = bundle.GetStringFromName( + "errorOpenMessageForMessageIdTitle" + ); + let errorMessage = bundle.formatStringFromName( + "errorOpenMessageForMessageIdMessage", + [msgId] + ); + Services.prompt.alert(null, errorTitle, errorMessage); + }, + + /** + * Open the given .eml file. + * + * @param {DOMWindow} win - The window which the file is being opened within. + * @param {nsIFile} aFile - The file being opened. + * @param {nsIURL} aURL - The full file URL. + */ + openEMLFile(win, aFile, aURL) { + let url = aURL + .mutate() + .setQuery("type=application/x-message-display") + .finalize(); + + let fstream = null; + let headers = new Map(); + // Read this eml and extract its headers to check for X-Unsent. + try { + fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(aFile, -1, 0, 0); + let data = lazy.NetUtil.readInputStreamToString( + fstream, + fstream.available() + ); + headers = lazy.MimeParser.extractHeaders(data); + } catch (e) { + // Ignore errors on reading the eml or extracting its headers. The test for + // the X-Unsent header below will fail and the message window will take care + // of any error handling. + } finally { + if (fstream) { + fstream.close(); + } + } + + if (headers.get("X-Unsent") == "1") { + let msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance( + Ci.nsIMsgWindow + ); + lazy.MailServices.compose.OpenComposeWindow( + null, + {}, + url.spec, + Ci.nsIMsgCompType.Draft, + Ci.nsIMsgCompFormat.Default, + null, + headers.get("from"), + msgWindow + ); + } else if ( + Services.prefs.getIntPref("mail.openMessageBehavior") == + lazy.MailConsts.OpenMessageBehavior.NEW_TAB && + win.document.getElementById("tabmail") + ) { + win.document + .getElementById("tabmail") + .openTab("mailMessageTab", { messageURI: url.spec }); + } else { + win.openDialog( + "chrome://messenger/content/messageWindow.xhtml", + "_blank", + "all,chrome,dialog=no,status,toolbar", + url + ); + } + }, + + /** + * The number of milliseconds to wait between loading of folders in + * |takeActionOnFolderAndDescendents|. We wait at all because + * opening msf databases is a potentially expensive synchronous operation that + * can approach the order of a second in pathological cases like gmail's + * all mail folder. + * + * If we did not use a timer or otherwise spin the event loop we would + * completely lock up the UI. In theory we would still maintain some degree + * of UI responsiveness if we just used postMessage to break up our work so + * that the event loop still got a chance to run between our folder openings. + * The use of any delay between processing folders is to try and avoid causing + * system-wide interactivity problems from dominating the system's available + * disk seeks to such an extent that other applications start experiencing + * non-trivial I/O waits. + * + * The specific choice of delay remains an arbitrary one to maintain app + * and system responsiveness per the above while also processing as many + * folders as quickly as possible. + * + * This is exposed primarily to allow unit tests to set this to 0 to minimize + * throttling. + */ + INTER_FOLDER_PROCESSING_DELAY_MS: 10, + + /** + * Set a string property on a folder and all of its descendents, taking care + * to avoid locking up the main thread and to avoid leaving folder databases + * open. To avoid locking up the main thread we operate in an asynchronous + * fashion; we invoke a callback when we have completed our work. + * + * Using this function will write the value into the folder cache + * as well as the folder itself. Hopefully you want this; if + * you do not, keep in mind that the only way to avoid that is to retrieve + * the nsIMsgDatabase and then the nsIDbFolderInfo. You would want to avoid + * that as much as possible because once those are exposed to you, XPConnect + * is going to hold onto them creating a situation where you are going to be + * in severe danger of extreme memory bloat unless you force garbage + * collections after every time you close a database. + * + * @param {nsIMsgFolder} folder - The parent folder; we take action on it and all + * of its descendents. + * @param {Function} action - the function to call on each folder. + */ + async takeActionOnFolderAndDescendents(folder, action) { + // We need to add the base folder as it is not included by .descendants. + let allFolders = [folder, ...folder.descendants]; + + // - worker function + function* folderWorker() { + for (let folder of allFolders) { + action(folder); + yield undefined; + } + } + let worker = folderWorker(); + + return new Promise((resolve, reject) => { + // - driver logic + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + function folderDriver() { + try { + if (worker.next().done) { + timer.cancel(); + resolve(); + } + } catch (ex) { + // Any type of exception kills the generator. + timer.cancel(); + reject(ex); + } + } + // make sure there is at least 100 ms of not us between doing things. + timer.initWithCallback( + folderDriver, + this.INTER_FOLDER_PROCESSING_DELAY_MS, + Ci.nsITimer.TYPE_REPEATING_SLACK + ); + }); + }, + + /** + * Get the identity that most likely is the best one to use, given the hint. + * + * @param {nsIMsgIdentity[]} identities - The candidates to pick from. + * @param {string} [optionalHint] - String containing comma separated mailboxes. + * @param {boolean} useDefault - If true, use the default identity of the + * account as last choice. This is useful when all default account as last + * choice. This is useful when all identities are passed in. Otherwise, use + * the first entity in the list. + * @returns {Array} - An array of two elements, [identity, matchingHint]. + * identity is an nsIMsgIdentity and matchingHint is a string. + */ + getBestIdentity(identities, optionalHint, useDefault = false) { + let identityCount = identities.length; + if (identityCount < 1) { + return [null, null]; + } + + // If we have a hint to help us pick one identity, search for a match. + // Even if we only have one identity, check which hint might match. + if (optionalHint) { + let hints = + lazy.MailServices.headerParser.makeFromDisplayAddress(optionalHint); + + for (let hint of hints) { + for (let identity of identities.filter(i => i.email)) { + if (hint.email.toLowerCase() == identity.email.toLowerCase()) { + return [identity, hint]; + } + } + } + + // Lets search again, this time for a match from catchAll. + for (let hint of hints) { + for (let identity of identities.filter( + i => i.email && i.catchAll && i.catchAllHint + )) { + for (let caHint of identity.catchAllHint.toLowerCase().split(",")) { + // If the hint started with *@, it applies to the whole domain. In + // this case return the hint so it can be used for replying. + // If the hint was for a more specific hint, don't return a hint + // so that the normal from address for the identity is used. + let wholeDomain = caHint.trim().startsWith("*@"); + caHint = caHint.trim().replace(/^\*/, ""); // Remove initial star. + if (hint.email.toLowerCase().includes(caHint)) { + return wholeDomain ? [identity, hint] : [identity, null]; + } + } + } + } + } + + // Still no matches? Give up and pick the default or the first one. + if (useDefault) { + let defaultAccount = lazy.MailServices.accounts.defaultAccount; + if (defaultAccount && defaultAccount.defaultIdentity) { + return [defaultAccount.defaultIdentity, null]; + } + } + + return [identities[0], null]; + }, + + getIdentityForServer(server, optionalHint) { + let identities = lazy.MailServices.accounts.getIdentitiesForServer(server); + return this.getBestIdentity(identities, optionalHint); + }, + + /** + * Get the identity for the given header. + * + * @param {nsIMsgHdr} hdr - Message header. + * @param {nsIMsgCompType} type - Compose type the identity is used for. + * @returns {Array} - An array of two elements, [identity, matchingHint]. + * identity is an nsIMsgIdentity and matchingHint is a string. + */ + getIdentityForHeader(hdr, type, hint = "") { + let server = null; + let identity = null; + let matchingHint = null; + let folder = hdr.folder; + if (folder) { + server = folder.server; + identity = folder.customIdentity; + if (identity) { + return [identity, null]; + } + } + + if (!server) { + let accountKey = hdr.accountKey; + if (accountKey) { + let account = lazy.MailServices.accounts.getAccount(accountKey); + if (account) { + server = account.incomingServer; + } + } + } + + let hintForIdentity = ""; + if (type == Ci.nsIMsgCompType.ReplyToList) { + hintForIdentity = hint; + } else if ( + type == Ci.nsIMsgCompType.Template || + type == Ci.nsIMsgCompType.EditTemplate || + type == Ci.nsIMsgCompType.EditAsNew + ) { + hintForIdentity = hdr.author; + } else { + hintForIdentity = hdr.recipients + "," + hdr.ccList + "," + hint; + } + + if (server) { + [identity, matchingHint] = this.getIdentityForServer( + server, + hintForIdentity + ); + } + + if (!identity) { + [identity, matchingHint] = this.getBestIdentity( + lazy.MailServices.accounts.allIdentities, + hintForIdentity, + true + ); + } + return [identity, matchingHint]; + }, + + getInboxFolder(server) { + try { + var rootMsgFolder = server.rootMsgFolder; + + // Now find the Inbox. + return rootMsgFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox); + } catch (ex) { + dump(ex + "\n"); + } + return null; + }, + + /** + * Finds a mailing list anywhere in the address books. + * + * @param {string} entryName - Value against which dirName is checked. + * @returns {nsIAbDirectory|null} - Found list or null. + */ + findListInAddressBooks(entryName) { + for (let abDir of lazy.MailServices.ab.directories) { + if (abDir.supportsMailingLists) { + for (let dir of abDir.childNodes) { + if (dir.isMailList && dir.dirName == entryName) { + return dir; + } + } + } + } + return null; + }, + + /** + * Recursively search for message id in a given folder and its subfolders, + * return the first one found. + * + * @param {string} msgId - The message id to find. + * @param {nsIMsgFolder} folder - The folder to check. + * @returns {nsIMsgDBHdr} + */ + findMsgIdInFolder(msgId, folder) { + let msgHdr; + + // Search in folder. + if (!folder.isServer) { + try { + msgHdr = folder.msgDatabase.getMsgHdrForMessageID(msgId); + if (msgHdr) { + return msgHdr; + } + folder.closeDBIfFolderNotOpen(true); + } catch (ex) { + console.error(`Database for ${folder.name} not accessible`); + } + } + + // Search subfolders recursively. + for (let currentFolder of folder.subFolders) { + msgHdr = this.findMsgIdInFolder(msgId, currentFolder); + if (msgHdr) { + return msgHdr; + } + } + return null; + }, + + /** + * Recursively search for message id in all msg folders, return the first one + * found. + * + * @param {string} msgId - The message id to search for. + * @param {nsIMsgIncomingServer} [startServer] - The server to check first. + * @returns {nsIMsgDBHdr} + */ + getMsgHdrForMsgId(msgId, startServer) { + let allServers = lazy.MailServices.accounts.allServers; + if (startServer) { + allServers = [startServer].concat( + allServers.filter(s => s.key != startServer.key) + ); + } + for (let server of allServers) { + if (server && server.canSearchMessages && !server.isDeferredTo) { + let msgHdr = this.findMsgIdInFolder(msgId, server.rootFolder); + if (msgHdr) { + return msgHdr; + } + } + } + return null; + }, +}; + +/** + * A class that listens to notifications about folders, and deals with them + * appropriately. + * @implements {nsIObserver} + */ +class FolderNotificationManager { + QueryInterface = ChromeUtils.generateQI(["nsIObserver"]); + + static #manager = null; + + static init() { + if (FolderNotificationManager.#manager) { + return; + } + FolderNotificationManager.#manager = new FolderNotificationManager(); + } + + constructor() { + Services.obs.addObserver(this, "profile-before-change"); + Services.obs.addObserver(this, "folder-attention"); + } + + observe(subject, topic, data) { + switch (topic) { + case "profile-before-change": + Services.obs.removeObserver(this, "profile-before-change"); + Services.obs.removeObserver(this, "folder-attention"); + return; + case "folder-attention": + MailUtils.displayFolderIn3Pane( + subject.QueryInterface(Ci.nsIMsgFolder).URI + ); + } + } +} +FolderNotificationManager.init(); |