summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/mailWindowOverlay.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/content/mailWindowOverlay.js')
-rw-r--r--comm/mail/base/content/mailWindowOverlay.js2177
1 files changed, 2177 insertions, 0 deletions
diff --git a/comm/mail/base/content/mailWindowOverlay.js b/comm/mail/base/content/mailWindowOverlay.js
new file mode 100644
index 0000000000..449a92de07
--- /dev/null
+++ b/comm/mail/base/content/mailWindowOverlay.js
@@ -0,0 +1,2177 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* global gSpacesToolbar */
+
+/* import-globals-from ../../../mailnews/extensions/newsblog/newsblogOverlay.js */
+/* import-globals-from contentAreaClick.js */
+/* import-globals-from mail3PaneWindowCommands.js */
+/* import-globals-from mailCommands.js */
+/* import-globals-from mailCore.js */
+
+/* import-globals-from utilityOverlay.js */
+
+/* globals messenger */ // From messageWindow.js
+/* globals GetSelectedMsgFolders */ // From messenger.js
+/* globals MailOfflineMgr */ // From mail-offline.js
+
+/* globals OnTagsChange, currentHeaderData */ // TODO: these aren't real.
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+
+ BrowserToolboxLauncher:
+ "resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs",
+});
+XPCOMUtils.defineLazyModuleGetters(this, {
+ MailUtils: "resource:///modules/MailUtils.jsm",
+ MimeParser: "resource:///modules/mimeParser.jsm",
+ UIDensity: "resource:///modules/UIDensity.jsm",
+ UIFontSize: "resource:///modules/UIFontSize.jsm",
+});
+
+Object.defineProperty(this, "BrowserConsoleManager", {
+ get() {
+ let { loader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ return loader.require("devtools/client/webconsole/browser-console-manager")
+ .BrowserConsoleManager;
+ },
+ configurable: true,
+ enumerable: true,
+});
+
+// the user preference,
+// if HTML is not allowed. I assume, that the user could have set this to a
+// value > 1 in his prefs.js or user.js, but that the value will not
+// change during runtime other than through the MsgBody*() functions below.
+var gDisallow_classes_no_html = 1;
+
+/**
+ * Disable the new account menu item if the account preference is locked.
+ * The other affected areas are the account central, the account manager
+ * dialog, and the account provisioner window.
+ */
+function menu_new_init() {
+ // If the account provisioner is pref'd off, we shouldn't display the menu
+ // item.
+ ShowMenuItem(
+ "newCreateEmailAccountMenuItem",
+ Services.prefs.getBoolPref("mail.provider.enabled")
+ );
+
+ // If we don't have a folder, just get out of here and leave the menu as it is.
+ let folder = document.getElementById("tabmail")?.currentTabInfo.folder;
+ if (!folder) {
+ return;
+ }
+
+ if (Services.prefs.prefIsLocked("mail.disable_new_account_addition")) {
+ document
+ .getElementById("newNewsgroupAccountMenuItem")
+ .setAttribute("disabled", "true");
+ document
+ .getElementById("appmenu_newNewsgroupAccountMenuItem")
+ .setAttribute("disabled", "true");
+ }
+
+ var isInbox = folder.isSpecialFolder(Ci.nsMsgFolderFlags.Inbox);
+ var showNew =
+ (folder.canCreateSubfolders ||
+ (isInbox && !folder.getFlag(Ci.nsMsgFolderFlags.Virtual))) &&
+ document.getElementById("cmd_newFolder").getAttribute("disabled") != "true";
+ ShowMenuItem("menu_newFolder", showNew);
+ ShowMenuItem("menu_newVirtualFolder", showNew);
+ ShowMenuItem("newAccountPopupMenuSeparator", showNew);
+
+ EnableMenuItem(
+ "menu_newFolder",
+ folder.server.type != "imap" || MailOfflineMgr.isOnline()
+ );
+ if (showNew) {
+ var bundle = document.getElementById("bundle_messenger");
+ // Change "New Folder..." menu according to the context.
+ SetMenuItemLabel(
+ "menu_newFolder",
+ bundle.getString(
+ folder.isServer || isInbox
+ ? "newFolderMenuItem"
+ : "newSubfolderMenuItem"
+ )
+ );
+ }
+
+ goUpdateCommand("cmd_newMessage");
+}
+
+function goUpdateMailMenuItems(commandset) {
+ for (var i = 0; i < commandset.children.length; i++) {
+ var commandID = commandset.children[i].getAttribute("id");
+ if (commandID) {
+ goUpdateCommand(commandID);
+ }
+ }
+
+ updateCheckedStateForIgnoreAndWatchThreadCmds();
+}
+
+/**
+ * Update the ignore (sub)thread, and watch thread commands so the menus
+ * using them get the checked state set up properly.
+ */
+function updateCheckedStateForIgnoreAndWatchThreadCmds() {
+ let message;
+
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (["mail3PaneTab", "mailMessageTab"].includes(tab?.mode.name)) {
+ message = tab.message;
+ }
+
+ let folder = message?.folder;
+
+ let killThreadItem = document.getElementById("cmd_killThread");
+ if (folder?.msgDatabase.isIgnored(message.messageKey)) {
+ killThreadItem.setAttribute("checked", "true");
+ } else {
+ killThreadItem.removeAttribute("checked");
+ }
+ let killSubthreadItem = document.getElementById("cmd_killSubthread");
+ if (folder && message.flags & Ci.nsMsgMessageFlags.Ignored) {
+ killSubthreadItem.setAttribute("checked", "true");
+ } else {
+ killSubthreadItem.removeAttribute("checked");
+ }
+ let watchThreadItem = document.getElementById("cmd_watchThread");
+ if (folder?.msgDatabase.isWatched(message.messageKey)) {
+ watchThreadItem.setAttribute("checked", "true");
+ } else {
+ watchThreadItem.removeAttribute("checked");
+ }
+}
+
+function file_init() {
+ document.commandDispatcher.updateCommands("create-menu-file");
+}
+
+/**
+ * Update the menu items visibility in the Edit submenu.
+ */
+function InitEditMessagesMenu() {
+ document.commandDispatcher.updateCommands("create-menu-edit");
+
+ let chromeBrowser, folderTreeActive, folder, folderIsNewsgroup;
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (tab?.mode.name == "mail3PaneTab") {
+ chromeBrowser = tab.chromeBrowser;
+ folderTreeActive =
+ chromeBrowser.contentDocument.activeElement.id == "folderTree";
+ folder = chromeBrowser.contentWindow.gFolder;
+ folderIsNewsgroup = folder?.server.type == "nntp";
+ } else if (tab?.mode.name == "mailMessageTab") {
+ chromeBrowser = tab.chromeBrowser;
+ } else {
+ chromeBrowser = document.getElementById("messageBrowser");
+ }
+
+ let deleteController = getEnabledControllerForCommand("cmd_delete");
+ // If the controller is a JS object, it must be one we've implemented,
+ // not the built-in controller for textboxes.
+
+ let dbView = chromeBrowser?.contentWindow.gDBView;
+ let numSelected = dbView?.numSelected;
+
+ let deleteMenuItem = document.getElementById("menu_delete");
+ if (deleteController?.wrappedJSObject && folderTreeActive) {
+ let value = folderIsNewsgroup
+ ? "menu-edit-unsubscribe-newsgroup"
+ : "menu-edit-delete-folder";
+ document.l10n.setAttributes(deleteMenuItem, value);
+ } else if (deleteController?.wrappedJSObject && numSelected) {
+ let message = dbView?.hdrForFirstSelectedMessage;
+ let value;
+ if (message && message.flags & Ci.nsMsgMessageFlags.IMAPDeleted) {
+ value = "menu-edit-undelete-messages";
+ } else {
+ value = "menu-edit-delete-messages";
+ }
+ document.l10n.setAttributes(deleteMenuItem, value, { count: numSelected });
+ } else {
+ document.l10n.setAttributes(deleteMenuItem, "text-action-delete");
+ }
+
+ // Initialize the Favorite Folder checkbox in the Edit menu.
+ let favoriteFolderMenu = document.getElementById("menu_favoriteFolder");
+ if (folder?.getFlag(Ci.nsMsgFolderFlags.Favorite)) {
+ favoriteFolderMenu.setAttribute("checked", "true");
+ } else {
+ favoriteFolderMenu.removeAttribute("checked");
+ }
+
+ let propertiesController = getEnabledControllerForCommand("cmd_properties");
+ let propertiesMenuItem = document.getElementById("menu_properties");
+ if (tab?.mode.name == "mail3PaneTab" && propertiesController) {
+ let value = folderIsNewsgroup
+ ? "menu-edit-newsgroup-properties"
+ : "menu-edit-folder-properties";
+ document.l10n.setAttributes(propertiesMenuItem, value);
+ } else {
+ document.l10n.setAttributes(propertiesMenuItem, "menu-edit-properties");
+ }
+}
+
+/**
+ * Update the menu items visibility in the Find submenu.
+ */
+function initSearchMessagesMenu() {
+ // Show 'Global Search' menu item only when global search is enabled.
+ let glodaEnabled = Services.prefs.getBoolPref(
+ "mailnews.database.global.indexer.enabled"
+ );
+ document.getElementById("glodaSearchCmd").hidden = !glodaEnabled;
+}
+
+function InitGoMessagesMenu() {
+ document.commandDispatcher.updateCommands("create-menu-go");
+}
+
+/**
+ * This is called every time the view menu popup is displayed (in the main menu
+ * bar or in the appmenu). It is responsible for updating the menu items'
+ * state to reflect reality.
+ */
+function view_init(event) {
+ if (event && event.target.id != "menu_View_Popup") {
+ return;
+ }
+
+ let accountCentralVisible;
+ let folderPaneVisible;
+ let message;
+ let messagePaneVisible;
+ let quickFilterBarVisible;
+ let threadPaneHeaderVisible;
+
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (tab?.mode.name == "mail3PaneTab") {
+ let chromeBrowser;
+ ({ chromeBrowser, message } = tab);
+ let { paneLayout, quickFilterBar } = chromeBrowser.contentWindow;
+ ({ accountCentralVisible, folderPaneVisible, messagePaneVisible } =
+ paneLayout);
+ quickFilterBarVisible = quickFilterBar.filterer.visible;
+ threadPaneHeaderVisible = true;
+ } else if (tab?.mode.name == "mailMessageTab") {
+ message = tab.message;
+ messagePaneVisible = true;
+ threadPaneHeaderVisible = false;
+ }
+
+ let isFeed = FeedUtils.isFeedMessage(message);
+
+ let qfbMenuItem = document.getElementById(
+ "view_toolbars_popup_quickFilterBar"
+ );
+ if (qfbMenuItem) {
+ qfbMenuItem.setAttribute("checked", quickFilterBarVisible);
+ }
+
+ let qfbAppMenuItem = document.getElementById("appmenu_quickFilterBar");
+ if (qfbAppMenuItem) {
+ if (quickFilterBarVisible) {
+ qfbAppMenuItem.setAttribute("checked", "true");
+ } else {
+ qfbAppMenuItem.removeAttribute("checked");
+ }
+ }
+
+ let messagePaneMenuItem = document.getElementById("menu_showMessage");
+ if (!messagePaneMenuItem.hidden) {
+ // Hidden in the standalone msg window.
+ messagePaneMenuItem.setAttribute(
+ "checked",
+ accountCentralVisible ? false : messagePaneVisible
+ );
+ messagePaneMenuItem.disabled = accountCentralVisible;
+ }
+
+ let messagePaneAppMenuItem = document.getElementById("appmenu_showMessage");
+ if (messagePaneAppMenuItem && !messagePaneAppMenuItem.hidden) {
+ // Hidden in the standalone msg window.
+ messagePaneAppMenuItem.setAttribute(
+ "checked",
+ accountCentralVisible ? false : messagePaneVisible
+ );
+ messagePaneAppMenuItem.disabled = accountCentralVisible;
+ }
+
+ let folderPaneMenuItem = document.getElementById("menu_showFolderPane");
+ if (!folderPaneMenuItem.hidden) {
+ // Hidden in the standalone msg window.
+ folderPaneMenuItem.setAttribute("checked", folderPaneVisible);
+ }
+
+ let folderPaneAppMenuItem = document.getElementById("appmenu_showFolderPane");
+ if (!folderPaneAppMenuItem.hidden) {
+ // Hidden in the standalone msg window.
+ folderPaneAppMenuItem.setAttribute("checked", folderPaneVisible);
+ }
+
+ let threadPaneMenuItem = document.getElementById(
+ "menu_toggleThreadPaneHeader"
+ );
+ threadPaneMenuItem.setAttribute("disabled", !threadPaneHeaderVisible);
+
+ let threadPaneAppMenuItem = document.getElementById(
+ "appmenu_toggleThreadPaneHeader"
+ );
+ threadPaneAppMenuItem.toggleAttribute("disabled", !threadPaneHeaderVisible);
+
+ // Disable some menus if account manager is showing
+ document.getElementById("viewSortMenu").disabled = accountCentralVisible;
+
+ document.getElementById("viewMessageViewMenu").disabled =
+ accountCentralVisible;
+
+ document.getElementById("viewMessagesMenu").disabled = accountCentralVisible;
+
+ // Hide the "View > Messages" menu item if the user doesn't have the "Views"
+ // (aka "Mail Views") toolbar button in the main toolbar. (See bug 1563789.)
+ var viewsToolbarButton = window.ViewPickerBinding?.isVisible;
+ document.getElementById("viewMessageViewMenu").hidden = !viewsToolbarButton;
+
+ // Initialize the Message Body menuitem
+ document.getElementById("viewBodyMenu").hidden = isFeed;
+
+ // Initialize the Show Feed Summary menu
+ let viewFeedSummary = document.getElementById("viewFeedSummary");
+ viewFeedSummary.hidden = !isFeed;
+
+ let viewRssMenuItemIds = [
+ "bodyFeedGlobalWebPage",
+ "bodyFeedGlobalSummary",
+ "bodyFeedPerFolderPref",
+ ];
+ let checked = FeedMessageHandler.onSelectPref;
+ for (let [index, id] of viewRssMenuItemIds.entries()) {
+ document.getElementById(id).setAttribute("checked", index == checked);
+ }
+
+ // Initialize the View Attachment Inline menu
+ var viewAttachmentInline = Services.prefs.getBoolPref(
+ "mail.inline_attachments"
+ );
+ document
+ .getElementById("viewAttachmentsInlineMenuitem")
+ .setAttribute("checked", viewAttachmentInline);
+
+ document.commandDispatcher.updateCommands("create-menu-view");
+
+ // No need to do anything if we don't have a spaces toolbar like in standalone
+ // windows or another non tabmail window.
+ let spacesToolbarMenu = document.getElementById("appmenu_spacesToolbar");
+ if (spacesToolbarMenu) {
+ // Update the spaces toolbar menu items.
+ let isSpacesVisible = !gSpacesToolbar.isHidden;
+ spacesToolbarMenu.checked = isSpacesVisible;
+ document
+ .getElementById("viewToolbarsPopupSpacesToolbar")
+ .setAttribute("checked", isSpacesVisible);
+ }
+}
+
+function initUiDensityMenu(event) {
+ // Prevent submenus from unnecessarily triggering onViewToolbarsPopupShowing
+ // via bubbling of events.
+ event.stopImmediatePropagation();
+
+ // Apply the correct mode attribute to the various items.
+ document.getElementById("uiDensityCompact").mode = UIDensity.MODE_COMPACT;
+ document.getElementById("uiDensityNormal").mode = UIDensity.MODE_NORMAL;
+ document.getElementById("uiDensityTouch").mode = UIDensity.MODE_TOUCH;
+
+ // Fetch the currently active identity.
+ let currentDensity = UIDensity.prefValue;
+
+ for (let item of event.target.querySelectorAll("menuitem")) {
+ if (item.mode == currentDensity) {
+ item.setAttribute("checked", "true");
+ break;
+ }
+ }
+}
+
+/**
+ * Assign the proper mode to the UI density controls in the App Menu and set
+ * the correct checked state based on the current density.
+ */
+function initUiDensityAppMenu() {
+ // Apply the correct mode attribute to the various items.
+ document.getElementById("appmenu_uiDensityCompact").mode =
+ UIDensity.MODE_COMPACT;
+ document.getElementById("appmenu_uiDensityNormal").mode =
+ UIDensity.MODE_NORMAL;
+ document.getElementById("appmenu_uiDensityTouch").mode = UIDensity.MODE_TOUCH;
+
+ // Fetch the currently active identity.
+ let currentDensity = UIDensity.prefValue;
+
+ for (let item of document.querySelectorAll(
+ "#appMenu-uiDensity-controls > toolbarbutton"
+ )) {
+ if (item.mode == currentDensity) {
+ item.setAttribute("checked", "true");
+ } else {
+ item.removeAttribute("checked");
+ }
+ }
+}
+
+function InitViewLayoutStyleMenu(event, appmenu) {
+ // Prevent submenus from unnecessarily triggering onViewToolbarsPopupShowing
+ // via bubbling of events.
+ event.stopImmediatePropagation();
+ let paneConfig = Services.prefs.getIntPref("mail.pane_config.dynamic");
+
+ let parent = appmenu
+ ? event.target.querySelector(".panel-subview-body")
+ : event.target;
+
+ let layoutStyleMenuitem = parent.children[paneConfig];
+ if (layoutStyleMenuitem) {
+ layoutStyleMenuitem.setAttribute("checked", "true");
+ }
+
+ if (
+ Services.xulStore.getValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "threadPaneHeader",
+ "hidden"
+ ) !== "true"
+ ) {
+ parent
+ .querySelector(`[name="threadheader"]`)
+ .setAttribute("checked", "true");
+ } else {
+ parent.querySelector(`[name="threadheader"]`).removeAttribute("checked");
+ }
+}
+
+/**
+ * Called when showing the menu_viewSortPopup menupopup, so it should always
+ * be up-to-date.
+ */
+function InitViewSortByMenu() {
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (tab?.mode.name != "mail3PaneTab") {
+ return;
+ }
+
+ let { gViewWrapper, threadPane } = tab.chromeBrowser.contentWindow;
+ if (!gViewWrapper?.dbView) {
+ return;
+ }
+
+ let { primarySortType, primarySortOrder, showGroupedBySort, showThreaded } =
+ gViewWrapper;
+ let hiddenColumns = threadPane.columns
+ .filter(c => c.hidden)
+ .map(c => c.sortKey);
+
+ let isSortTypeValidForGrouping = [
+ Ci.nsMsgViewSortType.byAccount,
+ Ci.nsMsgViewSortType.byAttachments,
+ Ci.nsMsgViewSortType.byAuthor,
+ Ci.nsMsgViewSortType.byCorrespondent,
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortType.byFlagged,
+ Ci.nsMsgViewSortType.byLocation,
+ Ci.nsMsgViewSortType.byPriority,
+ Ci.nsMsgViewSortType.byReceived,
+ Ci.nsMsgViewSortType.byRecipient,
+ Ci.nsMsgViewSortType.byStatus,
+ Ci.nsMsgViewSortType.bySubject,
+ Ci.nsMsgViewSortType.byTags,
+ Ci.nsMsgViewSortType.byCustom,
+ ].includes(primarySortType);
+
+ let setSortItemAttrs = function (id, sortKey) {
+ let menuItem = document.getElementById(id);
+ menuItem.setAttribute(
+ "checked",
+ primarySortType == Ci.nsMsgViewSortType[sortKey]
+ );
+ if (hiddenColumns.includes(sortKey)) {
+ menuItem.setAttribute("disabled", "true");
+ } else {
+ menuItem.removeAttribute("disabled");
+ }
+ };
+
+ setSortItemAttrs("sortByDateMenuitem", "byDate");
+ setSortItemAttrs("sortByReceivedMenuitem", "byReceived");
+ setSortItemAttrs("sortByFlagMenuitem", "byFlagged");
+ setSortItemAttrs("sortByOrderReceivedMenuitem", "byId");
+ setSortItemAttrs("sortByPriorityMenuitem", "byPriority");
+ setSortItemAttrs("sortBySizeMenuitem", "bySize");
+ setSortItemAttrs("sortByStatusMenuitem", "byStatus");
+ setSortItemAttrs("sortBySubjectMenuitem", "bySubject");
+ setSortItemAttrs("sortByUnreadMenuitem", "byUnread");
+ setSortItemAttrs("sortByTagsMenuitem", "byTags");
+ setSortItemAttrs("sortByJunkStatusMenuitem", "byJunkStatus");
+ setSortItemAttrs("sortByFromMenuitem", "byAuthor");
+ setSortItemAttrs("sortByRecipientMenuitem", "byRecipient");
+ setSortItemAttrs("sortByAttachmentsMenuitem", "byAttachments");
+ setSortItemAttrs("sortByCorrespondentMenuitem", "byCorrespondent");
+
+ document
+ .getElementById("sortAscending")
+ .setAttribute(
+ "checked",
+ primarySortOrder == Ci.nsMsgViewSortOrder.ascending
+ );
+ document
+ .getElementById("sortDescending")
+ .setAttribute(
+ "checked",
+ primarySortOrder == Ci.nsMsgViewSortOrder.descending
+ );
+
+ document.getElementById("sortThreaded").setAttribute("checked", showThreaded);
+ document
+ .getElementById("sortUnthreaded")
+ .setAttribute("checked", !showThreaded && !showGroupedBySort);
+
+ let groupBySortOrderMenuItem = document.getElementById("groupBySort");
+ groupBySortOrderMenuItem.setAttribute(
+ "disabled",
+ !isSortTypeValidForGrouping
+ );
+ groupBySortOrderMenuItem.setAttribute("checked", showGroupedBySort);
+}
+
+function InitViewMessagesMenu() {
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (!["mail3PaneTab", "mailMessageTab"].includes(tab?.mode.name)) {
+ return;
+ }
+
+ let viewWrapper = tab.chromeBrowser.contentWindow.gViewWrapper;
+
+ document
+ .getElementById("viewAllMessagesMenuItem")
+ .setAttribute(
+ "checked",
+ !viewWrapper || (!viewWrapper.showUnreadOnly && !viewWrapper.specialView)
+ );
+
+ document
+ .getElementById("viewUnreadMessagesMenuItem")
+ .setAttribute("checked", !!viewWrapper?.showUnreadOnly);
+
+ document
+ .getElementById("viewThreadsWithUnreadMenuItem")
+ .setAttribute("checked", !!viewWrapper?.specialViewThreadsWithUnread);
+
+ document
+ .getElementById("viewWatchedThreadsWithUnreadMenuItem")
+ .setAttribute(
+ "checked",
+ !!viewWrapper?.specialViewWatchedThreadsWithUnread
+ );
+
+ document
+ .getElementById("viewIgnoredThreadsMenuItem")
+ .setAttribute("checked", !!viewWrapper?.showIgnored);
+}
+
+function InitMessageMenu() {
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ let message, folder;
+ let isDummy;
+ if (["mail3PaneTab", "mailMessageTab"].includes(tab?.mode.name)) {
+ ({ message, folder } = tab);
+ isDummy = message && !folder;
+ } else {
+ message = document.getElementById("messageBrowser")?.contentWindow.gMessage;
+ isDummy = !message?.folder;
+ }
+
+ let isNews = message?.folder?.flags & Ci.nsMsgFolderFlags.Newsgroup;
+ let isFeed = message && FeedUtils.isFeedMessage(message);
+
+ // We show reply to Newsgroups only for news messages.
+ document.getElementById("replyNewsgroupMainMenu").hidden = !isNews;
+
+ // For mail messages we say reply. For news we say ReplyToSender.
+ document.getElementById("replyMainMenu").hidden = isNews;
+ document.getElementById("replySenderMainMenu").hidden = !isNews;
+
+ document.getElementById("menu_cancel").hidden =
+ !isNews || !getEnabledControllerForCommand("cmd_cancel");
+
+ // Disable the move menu if there are no messages selected or if
+ // the message is a dummy - e.g. opening a message in the standalone window.
+ let messageStoredInternally = message && !isDummy;
+ // Disable the move menu if we can't delete msgs from the folder.
+ let canMove =
+ messageStoredInternally && !isNews && message.folder.canDeleteMessages;
+
+ document.getElementById("moveMenu").disabled = !canMove;
+
+ document.getElementById("copyMenu").disabled = !message;
+
+ initMoveToFolderAgainMenu(document.getElementById("moveToFolderAgain"));
+
+ // Disable the Forward As menu item if no message is selected.
+ document.getElementById("forwardAsMenu").disabled = !message;
+
+ // Disable the Attachments menu if no message is selected and we don't have
+ // any attachment.
+ let aboutMessage =
+ document.getElementById("tabmail")?.currentAboutMessage ||
+ document.getElementById("messageBrowser")?.contentWindow;
+ document.getElementById("msgAttachmentMenu").disabled =
+ !message || !aboutMessage?.currentAttachments.length;
+
+ // Disable the Tag menu item if no message is selected or when we're
+ // not in a folder.
+ document.getElementById("tagMenu").disabled = !messageStoredInternally;
+
+ // Show "Edit Draft Message" menus only in a drafts folder; otherwise hide them.
+ showCommandInSpecialFolder("cmd_editDraftMsg", Ci.nsMsgFolderFlags.Drafts);
+ // Show "New Message from Template" and "Edit Template" menus only in a
+ // templates folder; otherwise hide them.
+ showCommandInSpecialFolder(
+ ["cmd_newMsgFromTemplate", "cmd_editTemplateMsg"],
+ Ci.nsMsgFolderFlags.Templates
+ );
+
+ // Initialize the Open Message menuitem
+ var winType = document.documentElement.getAttribute("windowtype");
+ if (winType == "mail:3pane") {
+ document.getElementById("openMessageWindowMenuitem").hidden = isFeed;
+ }
+
+ // Initialize the Open Feed Message handler menu
+ let index = FeedMessageHandler.onOpenPref;
+ document
+ .getElementById("menu_openFeedMessage")
+ .children[index].setAttribute("checked", true);
+
+ let openRssMenu = document.getElementById("openFeedMessage");
+ openRssMenu.hidden = !isFeed;
+ if (winType != "mail:3pane") {
+ openRssMenu.hidden = true;
+ }
+
+ // Disable mark menu when we're not in a folder.
+ document.getElementById("markMenu").disabled = !folder || folder.isServer;
+
+ document.commandDispatcher.updateCommands("create-menu-message");
+
+ for (let id of ["killThread", "killSubthread", "watchThread"]) {
+ let item = document.getElementById(id);
+ let command = document.getElementById(item.getAttribute("command"));
+ if (command.hasAttribute("checked")) {
+ item.setAttribute("checked", command.getAttribute("checked"));
+ } else {
+ item.removeAttribute("checked");
+ }
+ }
+}
+
+/**
+ * Show folder-specific menu items only for messages in special folders, e.g.
+ * show 'cmd_editDraftMsg' in Drafts folder, or
+ * show 'cmd_newMsgFromTemplate' in Templates folder.
+ *
+ * aCommandIds single ID string of command or array of IDs of commands
+ * to be shown in folders having aFolderFlag
+ * aFolderFlag the nsMsgFolderFlag that the folder must have to show the command
+ */
+function showCommandInSpecialFolder(aCommandIds, aFolderFlag) {
+ let folder, message;
+
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (["mail3PaneTab", "mailMessageTab"].includes(tab?.mode.name)) {
+ ({ message, folder } = tab);
+ } else if (tab?.mode.tabType.name == "mail") {
+ ({ displayedFolder: folder, selectedMessage: message } = tab.folderDisplay);
+ }
+
+ let inSpecialFolder =
+ message?.folder?.isSpecialFolder(aFolderFlag, true) ||
+ (folder && folder.getFlag(aFolderFlag));
+ if (typeof aCommandIds === "string") {
+ aCommandIds = [aCommandIds];
+ }
+
+ aCommandIds.forEach(cmdId =>
+ document.getElementById(cmdId).setAttribute("hidden", !inSpecialFolder)
+ );
+}
+
+/**
+ * Initializes the menu item aMenuItem to show either "Move" or "Copy" to
+ * folder again, based on the value of mail.last_msg_movecopy_target_uri.
+ * The menu item label and accesskey are adjusted to include the folder name.
+ *
+ * @param aMenuItem the menu item to adjust
+ */
+function initMoveToFolderAgainMenu(aMenuItem) {
+ let lastFolderURI = Services.prefs.getStringPref(
+ "mail.last_msg_movecopy_target_uri"
+ );
+
+ if (!lastFolderURI) {
+ return;
+ }
+ let destMsgFolder = MailUtils.getExistingFolder(lastFolderURI);
+ if (!destMsgFolder) {
+ return;
+ }
+ let bundle = document.getElementById("bundle_messenger");
+ let isMove = Services.prefs.getBoolPref("mail.last_msg_movecopy_was_move");
+ let stringName = isMove ? "moveToFolderAgain" : "copyToFolderAgain";
+ aMenuItem.label = bundle.getFormattedString(
+ stringName,
+ [destMsgFolder.prettyName],
+ 1
+ );
+ // This gives us moveToFolderAgainAccessKey and copyToFolderAgainAccessKey.
+ aMenuItem.accesskey = bundle.getString(stringName + "AccessKey");
+}
+
+/**
+ * Update the "Show Header" menu items to reflect the current pref.
+ */
+function InitViewHeadersMenu() {
+ let dt = Ci.nsMimeHeaderDisplayTypes;
+ let headerchoice = Services.prefs.getIntPref("mail.show_headers");
+ document
+ .getElementById("cmd_viewAllHeader")
+ .setAttribute("checked", headerchoice == dt.AllHeaders);
+ document
+ .getElementById("cmd_viewNormalHeader")
+ .setAttribute("checked", headerchoice == dt.NormalHeaders);
+ document.commandDispatcher.updateCommands("create-menu-mark");
+}
+
+function InitViewBodyMenu() {
+ let message;
+
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (["mail3PaneTab", "mailMessageTab"].includes(tab?.mode.name)) {
+ message = tab.message;
+ }
+
+ // Separate render prefs not implemented for feeds, bug 458606. Show the
+ // checked item for feeds as for the regular pref.
+ // let html_as = Services.prefs.getIntPref("rss.display.html_as");
+ // let prefer_plaintext = Services.prefs.getBoolPref("rss.display.prefer_plaintext");
+ // let disallow_classes = Services.prefs.getIntPref("rss.display.disallow_mime_handlers");
+ let html_as = Services.prefs.getIntPref("mailnews.display.html_as");
+ let prefer_plaintext = Services.prefs.getBoolPref(
+ "mailnews.display.prefer_plaintext"
+ );
+ let disallow_classes = Services.prefs.getIntPref(
+ "mailnews.display.disallow_mime_handlers"
+ );
+ let isFeed = FeedUtils.isFeedMessage(message);
+ const defaultIDs = [
+ "bodyAllowHTML",
+ "bodySanitized",
+ "bodyAsPlaintext",
+ "bodyAllParts",
+ ];
+ const rssIDs = [
+ "bodyFeedSummaryAllowHTML",
+ "bodyFeedSummarySanitized",
+ "bodyFeedSummaryAsPlaintext",
+ ];
+ let menuIDs = isFeed ? rssIDs : defaultIDs;
+
+ if (disallow_classes > 0) {
+ gDisallow_classes_no_html = disallow_classes;
+ }
+ // else gDisallow_classes_no_html keeps its initial value (see top)
+
+ let AllowHTML_menuitem = document.getElementById(menuIDs[0]);
+ let Sanitized_menuitem = document.getElementById(menuIDs[1]);
+ let AsPlaintext_menuitem = document.getElementById(menuIDs[2]);
+ let AllBodyParts_menuitem = menuIDs[3]
+ ? document.getElementById(menuIDs[3])
+ : null;
+
+ document.getElementById("bodyAllParts").hidden = !Services.prefs.getBoolPref(
+ "mailnews.display.show_all_body_parts_menu"
+ );
+
+ if (
+ !prefer_plaintext &&
+ !html_as &&
+ !disallow_classes &&
+ AllowHTML_menuitem
+ ) {
+ AllowHTML_menuitem.setAttribute("checked", true);
+ } else if (
+ !prefer_plaintext &&
+ html_as == 3 &&
+ disallow_classes > 0 &&
+ Sanitized_menuitem
+ ) {
+ Sanitized_menuitem.setAttribute("checked", true);
+ } else if (
+ prefer_plaintext &&
+ html_as == 1 &&
+ disallow_classes > 0 &&
+ AsPlaintext_menuitem
+ ) {
+ AsPlaintext_menuitem.setAttribute("checked", true);
+ } else if (
+ !prefer_plaintext &&
+ html_as == 4 &&
+ !disallow_classes &&
+ AllBodyParts_menuitem
+ ) {
+ AllBodyParts_menuitem.setAttribute("checked", true);
+ }
+ // else (the user edited prefs/user.js) check none of the radio menu items
+
+ if (isFeed) {
+ AllowHTML_menuitem.hidden = !FeedMessageHandler.gShowSummary;
+ Sanitized_menuitem.hidden = !FeedMessageHandler.gShowSummary;
+ AsPlaintext_menuitem.hidden = !FeedMessageHandler.gShowSummary;
+ document.getElementById("viewFeedSummarySeparator").hidden =
+ !gShowFeedSummary;
+ }
+}
+
+function ShowMenuItem(id, showItem) {
+ document.getElementById(id).hidden = !showItem;
+}
+
+function EnableMenuItem(id, enableItem) {
+ document.getElementById(id).disabled = !enableItem;
+}
+
+function SetMenuItemLabel(menuItemId, customLabel) {
+ var menuItem = document.getElementById(menuItemId);
+ if (menuItem) {
+ menuItem.setAttribute("label", customLabel);
+ }
+}
+
+/**
+ * Refresh the contents of the tag popup menu/panel.
+ * Used for example for appmenu/Message/Tag panel.
+ *
+ * @param {Element} parent - Parent element that will contain the menu items.
+ * @param {string} [elementName] - Type of menu item, e.g. "menuitem", "toolbarbutton".
+ * @param {string} [classes] - Classes to set on the menu items.
+ */
+function InitMessageTags(parent, elementName = "menuitem", classes) {
+ function SetMessageTagLabel(menuitem, index, name) {
+ // if a <key> is defined for this tag, use its key as the accesskey
+ // (the key for the tag at index n needs to have the id key_tag<n>)
+ let shortcutkey = document.getElementById("key_tag" + index);
+ let accesskey = shortcutkey ? shortcutkey.getAttribute("key") : " ";
+ if (accesskey != " ") {
+ menuitem.setAttribute("accesskey", accesskey);
+ menuitem.setAttribute("acceltext", accesskey);
+ }
+ let label = document
+ .getElementById("bundle_messenger")
+ .getFormattedString("mailnews.tags.format", [accesskey, name]);
+ menuitem.setAttribute("label", label);
+ }
+
+ let message;
+
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ if (["mail3PaneTab", "mailMessageTab"].includes(tab?.mode.name)) {
+ message = tab.message;
+ } else {
+ message = document.getElementById("messageBrowser")?.contentWindow.gMessage;
+ }
+
+ const tagArray = MailServices.tags.getAllTags();
+ const elementNameUpperCase = elementName.toUpperCase();
+
+ // Remove any existing non-static items (clear tags list before rebuilding it).
+ // There is a separator element above the dynamically added tag elements, so
+ // remove dynamically added elements below the separator.
+ while (
+ parent.lastElementChild.tagName.toUpperCase() == elementNameUpperCase
+ ) {
+ parent.lastChild.remove();
+ }
+
+ // Create label and accesskey for the static "remove all tags" item.
+ const tagRemoveLabel = document
+ .getElementById("bundle_messenger")
+ .getString("mailnews.tags.remove");
+ SetMessageTagLabel(
+ parent.lastElementChild.previousElementSibling,
+ 0,
+ tagRemoveLabel
+ );
+
+ // Rebuild the list.
+ const curKeys = message.getStringProperty("keywords");
+
+ tagArray.forEach((tagInfo, index) => {
+ const removeKey = ` ${curKeys} `.includes(` ${tagInfo.key} `);
+
+ if (tagInfo.ordinal.includes("~AUTOTAG") && !removeKey) {
+ return;
+ }
+ // TODO We want to either remove or "check" the tags that already exist.
+ let item = parent.ownerDocument.createXULElement(elementName);
+ SetMessageTagLabel(item, index + 1, tagInfo.tag);
+
+ if (removeKey) {
+ item.setAttribute("checked", "true");
+ }
+ item.setAttribute("value", tagInfo.key);
+ item.setAttribute("type", "checkbox");
+ item.addEventListener("command", function (event) {
+ goDoCommand("cmd_toggleTag", event);
+ });
+
+ if (tagInfo.color) {
+ item.setAttribute("style", `color: ${tagInfo.color};`);
+ }
+ if (classes) {
+ item.setAttribute("class", classes);
+ }
+ parent.appendChild(item);
+ });
+}
+
+function getMsgToolbarMenu_init() {
+ document.commandDispatcher.updateCommands("create-menu-getMsgToolbar");
+}
+
+function InitMessageMark() {
+ let tab = document.getElementById("tabmail")?.currentTabInfo;
+ let flaggedItem = document.getElementById("markFlaggedMenuItem");
+ if (tab?.message?.isFlagged) {
+ flaggedItem.setAttribute("checked", "true");
+ } else {
+ flaggedItem.removeAttribute("checked");
+ }
+
+ document.commandDispatcher.updateCommands("create-menu-mark");
+}
+
+function GetFirstSelectedMsgFolder() {
+ try {
+ var selectedFolders = GetSelectedMsgFolders();
+ } catch (e) {
+ console.error(e);
+ }
+ return selectedFolders.length > 0 ? selectedFolders[0] : null;
+}
+
+function GetMessagesForInboxOnServer(server) {
+ var inboxFolder = MailUtils.getInboxFolder(server);
+
+ // If the server doesn't support an inbox it could be an RSS server or some
+ // other server type. Just use the root folder and the server implementation
+ // can figure out what to do.
+ if (!inboxFolder) {
+ inboxFolder = server.rootFolder;
+ }
+
+ GetNewMsgs(server, inboxFolder);
+}
+
+function MsgGetMessage(folders) {
+ // if offline, prompt for getting messages
+ if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
+ GetFolderMessages(folders);
+ }
+}
+
+function MsgPauseUpdates(selectedFolders = GetSelectedMsgFolders(), pause) {
+ // Pause single feed folder subscription updates, or all account updates if
+ // folder is the account folder.
+ let folder = selectedFolders.length ? selectedFolders[0] : null;
+ if (!FeedUtils.isFeedFolder(folder)) {
+ return;
+ }
+
+ FeedUtils.pauseFeedFolderUpdates(folder, pause, true);
+ Services.obs.notifyObservers(folder, "folder-properties-changed");
+}
+
+function MsgGetMessagesForAllServers(defaultServer) {
+ // now log into any server
+ try {
+ // Array of arrays of servers for a particular folder.
+ var pop3DownloadServersArray = [];
+ // Parallel array of folders to download to...
+ var localFoldersToDownloadTo = [];
+ var pop3Server;
+ for (let server of MailServices.accounts.allServers) {
+ if (server.protocolInfo.canLoginAtStartUp && server.loginAtStartUp) {
+ if (
+ defaultServer &&
+ defaultServer.equals(server) &&
+ !defaultServer.isDeferredTo &&
+ defaultServer.rootFolder == defaultServer.rootMsgFolder
+ ) {
+ // skip, already opened
+ } else if (server.type == "pop3" && server.downloadOnBiff) {
+ CoalesceGetMsgsForPop3ServersByDestFolder(
+ server,
+ pop3DownloadServersArray,
+ localFoldersToDownloadTo
+ );
+ pop3Server = server.QueryInterface(Ci.nsIPop3IncomingServer);
+ } else {
+ // Check to see if there are new messages on the server
+ server.performBiff(msgWindow);
+ }
+ }
+ }
+ for (let i = 0; i < pop3DownloadServersArray.length; ++i) {
+ // Any ol' pop3Server will do - the serversArray specifies which servers
+ // to download from.
+ pop3Server.downloadMailFromServers(
+ pop3DownloadServersArray[i],
+ msgWindow,
+ localFoldersToDownloadTo[i],
+ null
+ );
+ }
+ } catch (ex) {
+ dump(ex + "\n");
+ }
+}
+
+/**
+ * Get messages for all those accounts which have the capability
+ * of getting messages and have session password available i.e.,
+ * currently logged in accounts.
+ * if offline, prompt for getting messages.
+ */
+function MsgGetMessagesForAllAuthenticatedAccounts() {
+ if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
+ GetMessagesForAllAuthenticatedAccounts();
+ }
+}
+
+/**
+ * Get messages for the account selected from Menu dropdowns.
+ * if offline, prompt for getting messages.
+ *
+ * @param aFolder (optional) a folder in the account for which messages should
+ * be retrieved. If null, all accounts will be used.
+ */
+function MsgGetMessagesForAccount(aFolder) {
+ if (!aFolder) {
+ goDoCommand("cmd_getNewMessages");
+ return;
+ }
+
+ if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
+ var server = aFolder.server;
+ GetMessagesForInboxOnServer(server);
+ }
+}
+
+// if offline, prompt for getNextNMessages
+function MsgGetNextNMessages() {
+ if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
+ GetNextNMessages(GetFirstSelectedMsgFolder());
+ }
+}
+
+function MsgNewMessage(event) {
+ let msgFolder = document.getElementById("tabmail")?.currentTabInfo.folder;
+
+ if (event?.shiftKey) {
+ ComposeMessage(
+ Ci.nsIMsgCompType.New,
+ Ci.nsIMsgCompFormat.OppositeOfDefault,
+ msgFolder,
+ []
+ );
+ } else {
+ ComposeMessage(
+ Ci.nsIMsgCompType.New,
+ Ci.nsIMsgCompFormat.Default,
+ msgFolder,
+ []
+ );
+ }
+}
+
+/** Open subscribe window. */
+function MsgSubscribe(folder) {
+ var preselectedFolder = folder || GetFirstSelectedMsgFolder();
+
+ if (FeedUtils.isFeedFolder(preselectedFolder)) {
+ // Open feed subscription dialog.
+ openSubscriptionsDialog(preselectedFolder);
+ } else {
+ // Open IMAP/NNTP subscription dialog.
+ Subscribe(preselectedFolder);
+ }
+}
+
+/**
+ * Show a confirmation dialog - check if the user really want to unsubscribe
+ * from the given newsgroup/s.
+ *
+ * @folders an array of newsgroup folders to unsubscribe from
+ * @returns true if the user said it's ok to unsubscribe
+ */
+function ConfirmUnsubscribe(folders) {
+ var bundle = document.getElementById("bundle_messenger");
+ var titleMsg = bundle.getString("confirmUnsubscribeTitle");
+ var dialogMsg =
+ folders.length == 1
+ ? bundle.getFormattedString(
+ "confirmUnsubscribeText",
+ [folders[0].name],
+ 1
+ )
+ : bundle.getString("confirmUnsubscribeManyText");
+
+ return Services.prompt.confirm(window, titleMsg, dialogMsg);
+}
+
+/**
+ * Unsubscribe from selected or passed in newsgroup/s.
+ * @param {nsIMsgFolder[]} selectedFolders - The folders to unsubscribe.
+ */
+function MsgUnsubscribe(folders) {
+ if (!ConfirmUnsubscribe(folders)) {
+ return;
+ }
+
+ for (let i = 0; i < folders.length; i++) {
+ let subscribableServer = folders[i].server.QueryInterface(
+ Ci.nsISubscribableServer
+ );
+ subscribableServer.unsubscribe(folders[i].name);
+ subscribableServer.commitSubscribeChanges();
+ }
+}
+
+function MsgOpenNewWindowForFolder(folderURI, msgKeyToSelect) {
+ window.openDialog(
+ "chrome://messenger/content/messenger.xhtml",
+ "_blank",
+ "chrome,all,dialog=no",
+ folderURI,
+ msgKeyToSelect
+ );
+}
+
+/**
+ * UI-triggered command to open the currently selected folder(s) in new tabs.
+ *
+ * @param {nsIMsgFolder[]} folders - Folders to open in new tabs.
+ * @param {object} [tabParams] - Parameters to pass to the new tabs.
+ */
+function MsgOpenNewTabForFolders(folders, tabParams = {}) {
+ if (tabParams.background === undefined) {
+ tabParams.background = Services.prefs.getBoolPref(
+ "mail.tabs.loadInBackground"
+ );
+ if (tabParams.event?.shiftKey) {
+ tabParams.background = !tabParams.background;
+ }
+ }
+
+ let tabmail = document.getElementById("tabmail");
+ for (let i = 0; i < folders.length; i++) {
+ tabmail.openTab("mail3PaneTab", {
+ ...tabParams,
+ folderURI: folders[i].URI,
+ });
+ }
+}
+
+function MsgOpenFromFile() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+
+ var bundle = document.getElementById("bundle_messenger");
+ var filterLabel = bundle.getString("EMLFiles");
+ var windowTitle = bundle.getString("OpenEMLFiles");
+
+ fp.init(window, windowTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(filterLabel, "*.eml");
+
+ // Default or last filter is "All Files".
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+
+ fp.open(rv => {
+ if (rv != Ci.nsIFilePicker.returnOK || !fp.file) {
+ return;
+ }
+ MailUtils.openEMLFile(window, fp.file, fp.fileURL);
+ });
+}
+
+function MsgOpenNewWindowForMessage(aMsgHdr, aView) {
+ // We need to tell the window about our current view so that it can clone it.
+ // This enables advancing through the messages, etc.
+ return window.openDialog(
+ "chrome://messenger/content/messageWindow.xhtml",
+ "_blank",
+ "all,chrome,dialog=no,status,toolbar",
+ aMsgHdr,
+ aView
+ );
+}
+
+/**
+ * Display the given message in an existing folder tab.
+ *
+ * @param aMsgHdr The message header to display.
+ */
+function MsgDisplayMessageInFolderTab(aMsgHdr) {
+ let tabmail = document.getElementById("tabmail");
+ tabmail.switchToTab(0);
+ tabmail.currentAbout3Pane.selectMessage(aMsgHdr);
+}
+
+function MsgMarkAllRead(folders) {
+ for (let i = 0; i < folders.length; i++) {
+ folders[i].markAllMessagesRead(msgWindow);
+ }
+}
+
+/**
+ * Go through each selected server and mark all its folders read.
+ *
+ * @param {nsIMsgFolder[]} selectedFolders - Folders in the servers to be
+ * marked as read.
+ */
+function MsgMarkAllFoldersRead(selectedFolders) {
+ let selectedServers = selectedFolders.filter(folder => folder.isServer);
+ if (!selectedServers.length) {
+ return;
+ }
+
+ let bundle = document.getElementById("bundle_messenger");
+ if (
+ !Services.prompt.confirm(
+ window,
+ bundle.getString("confirmMarkAllFoldersReadTitle"),
+ bundle.getString("confirmMarkAllFoldersReadMessage")
+ )
+ ) {
+ return;
+ }
+
+ selectedServers.forEach(function (server) {
+ for (let folder of server.rootFolder.descendants) {
+ folder.markAllMessagesRead(msgWindow);
+ }
+ });
+}
+
+/**
+ * Opens the filter list.
+ * If an email address was passed, first a new filter is offered for creation
+ * with the data prefilled.
+ *
+ * @param {?string} emailAddress - An email address to use as value in the first
+ * search term.
+ * @param {?nsIMsgFolder} folder - The filter will be created in this folder's
+ * filter list.
+ * @param {?string} fieldName - Search field string, from
+ * nsMsgSearchTerm.cpp::SearchAttribEntryTable.
+ */
+function MsgFilters(emailAddress, folder, fieldName) {
+ // Don't trigger anything if there are no accounts configured. This is to
+ // disable potential triggers via shortcuts.
+ if (MailServices.accounts.accounts.length == 0) {
+ return;
+ }
+
+ if (!folder) {
+ let chromeBrowser =
+ document.getElementById("tabmail")?.currentTabInfo.chromeBrowser ||
+ document.getElementById("messageBrowser");
+ let dbView = chromeBrowser?.contentWindow?.gDBView;
+ // Try to determine the folder from the selected message.
+ if (dbView?.numSelected) {
+ // Here we face a decision. If the message has been moved to a different
+ // account, then a single filter cannot work for both manual and incoming
+ // scope. So we will create the filter based on its existing location,
+ // which will make it work properly in manual scope. This is the best
+ // solution for POP3 with global inbox (as then both manual and incoming
+ // filters work correctly), but may not be what IMAP users who filter to a
+ // local folder really want.
+ folder = dbView.hdrForFirstSelectedMessage.folder;
+ }
+ if (!folder) {
+ folder = GetFirstSelectedMsgFolder();
+ }
+ }
+ let args;
+ if (emailAddress) {
+ // We have to do prefill filter so we are going to launch the filterEditor
+ // dialog and prefill that with the emailAddress.
+ args = {
+ filterList: folder.getEditableFilterList(msgWindow),
+ filterName: emailAddress,
+ };
+ // Set the field name to prefill in the filter, if one was specified.
+ if (fieldName) {
+ args.fieldName = fieldName;
+ }
+
+ window.openDialog(
+ "chrome://messenger/content/FilterEditor.xhtml",
+ "",
+ "chrome, modal, resizable,centerscreen,dialog=yes",
+ args
+ );
+
+ // If the user hits OK in the filterEditor dialog we set args.refresh=true
+ // there and we check this here in args to show filterList dialog.
+ // We also received the filter created via args.newFilter.
+ if ("refresh" in args && args.refresh) {
+ args = { refresh: true, folder, filter: args.newFilter };
+ MsgFilterList(args);
+ }
+ } else {
+ // Just launch filterList dialog.
+ args = { refresh: false, folder };
+ MsgFilterList(args);
+ }
+}
+
+function MsgViewAllHeaders() {
+ Services.prefs.setIntPref(
+ "mail.show_headers",
+ Ci.nsMimeHeaderDisplayTypes.AllHeaders
+ );
+}
+
+function MsgViewNormalHeaders() {
+ Services.prefs.setIntPref(
+ "mail.show_headers",
+ Ci.nsMimeHeaderDisplayTypes.NormalHeaders
+ );
+}
+
+function MsgBodyAllowHTML() {
+ Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", false);
+ Services.prefs.setIntPref("mailnews.display.html_as", 0);
+ Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", 0);
+}
+
+function MsgBodySanitized() {
+ Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", false);
+ Services.prefs.setIntPref("mailnews.display.html_as", 3);
+ Services.prefs.setIntPref(
+ "mailnews.display.disallow_mime_handlers",
+ gDisallow_classes_no_html
+ );
+}
+
+function MsgBodyAsPlaintext() {
+ Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", true);
+ Services.prefs.setIntPref("mailnews.display.html_as", 1);
+ Services.prefs.setIntPref(
+ "mailnews.display.disallow_mime_handlers",
+ gDisallow_classes_no_html
+ );
+}
+
+function MsgBodyAllParts() {
+ Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", false);
+ Services.prefs.setIntPref("mailnews.display.html_as", 4);
+ Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", 0);
+}
+
+function MsgFeedBodyRenderPrefs(plaintext, html, mime) {
+ // Separate render prefs not implemented for feeds, bug 458606.
+ // Services.prefs.setBoolPref("rss.display.prefer_plaintext", plaintext);
+ // Services.prefs.setIntPref("rss.display.html_as", html);
+ // Services.prefs.setIntPref("rss.display.disallow_mime_handlers", mime);
+
+ Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", plaintext);
+ Services.prefs.setIntPref("mailnews.display.html_as", html);
+ Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", mime);
+ // Reload only if showing rss summary; menuitem hidden if web page..
+}
+
+function ToggleInlineAttachment(target) {
+ var viewAttachmentInline = !Services.prefs.getBoolPref(
+ "mail.inline_attachments"
+ );
+ Services.prefs.setBoolPref("mail.inline_attachments", viewAttachmentInline);
+ target.setAttribute("checked", viewAttachmentInline ? "true" : "false");
+}
+
+function IsGetNewMessagesEnabled() {
+ for (let server of MailServices.accounts.allServers) {
+ if (server.type == "none") {
+ continue;
+ }
+ return true;
+ }
+ return false;
+}
+
+function IsGetNextNMessagesEnabled() {
+ let selectedFolders = GetSelectedMsgFolders();
+ let folder = selectedFolders.length ? selectedFolders[0] : null;
+
+ let menuItem = document.getElementById("menu_getnextnmsg");
+ if (
+ folder &&
+ !folder.isServer &&
+ folder.server instanceof Ci.nsINntpIncomingServer
+ ) {
+ menuItem.label = PluralForm.get(
+ folder.server.maxArticles,
+ document
+ .getElementById("bundle_messenger")
+ .getString("getNextNewsMessages")
+ ).replace("#1", folder.server.maxArticles);
+ menuItem.removeAttribute("hidden");
+ return true;
+ }
+
+ menuItem.setAttribute("hidden", "true");
+ return false;
+}
+
+function MsgSynchronizeOffline() {
+ window.openDialog(
+ "chrome://messenger/content/msgSynchronize.xhtml",
+ "",
+ "centerscreen,chrome,modal,titlebar,resizable=yes",
+ { msgWindow }
+ );
+}
+
+function IsAccountOfflineEnabled() {
+ var selectedFolders = GetSelectedMsgFolders();
+
+ if (selectedFolders && selectedFolders.length == 1) {
+ return selectedFolders[0].supportsOffline;
+ }
+ return false;
+}
+
+function GetDefaultAccountRootFolder() {
+ var account = MailServices.accounts.defaultAccount;
+ if (account) {
+ return account.incomingServer.rootMsgFolder;
+ }
+
+ return null;
+}
+
+/**
+ * Check for new messages for all selected folders, or for the default account
+ * in case no folders are selected.
+ */
+function GetFolderMessages(selectedFolders = GetSelectedMsgFolders()) {
+ var defaultAccountRootFolder = GetDefaultAccountRootFolder();
+
+ // if nothing selected, use the default
+ var folders = selectedFolders.length
+ ? selectedFolders
+ : [defaultAccountRootFolder];
+
+ if (!folders[0]) {
+ return;
+ }
+
+ for (var i = 0; i < folders.length; i++) {
+ var serverType = folders[i].server.type;
+ if (folders[i].isServer && serverType == "nntp") {
+ // If we're doing "get msgs" on a news server.
+ // Update unread counts on this server.
+ folders[i].server.performExpand(msgWindow);
+ } else if (folders[i].isServer && serverType == "imap") {
+ GetMessagesForInboxOnServer(folders[i].server);
+ } else if (serverType == "none") {
+ // If "Local Folders" is selected and the user does "Get Msgs" and
+ // LocalFolders is not deferred to, get new mail for the default account
+ //
+ // XXX TODO
+ // Should shift click get mail for all (authenticated) accounts?
+ // see bug #125885.
+ if (!folders[i].server.isDeferredTo) {
+ if (!defaultAccountRootFolder) {
+ continue;
+ }
+ GetNewMsgs(defaultAccountRootFolder.server, defaultAccountRootFolder);
+ } else {
+ GetNewMsgs(folders[i].server, folders[i]);
+ }
+ } else {
+ GetNewMsgs(folders[i].server, folders[i]);
+ }
+ }
+}
+
+/**
+ * Gets new messages for the given server, for the given folder.
+ *
+ * @param server which nsIMsgIncomingServer to check for new messages
+ * @param folder which nsIMsgFolder folder to check for new messages
+ */
+function GetNewMsgs(server, folder) {
+ // Note that for Global Inbox folder.server != server when we want to get
+ // messages for a specific account.
+
+ // Whenever we do get new messages, clear the old new messages.
+ folder.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NoMail;
+ folder.clearNewMessages();
+ server.getNewMessages(folder, msgWindow, new TransportErrorUrlListener());
+}
+
+function InformUserOfCertError(secInfo, targetSite) {
+ let params = {
+ exceptionAdded: false,
+ securityInfo: secInfo,
+ prefetchCert: true,
+ location: targetSite,
+ };
+ window.openDialog(
+ "chrome://pippki/content/exceptionDialog.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ params
+ );
+}
+
+/**
+ * A listener to be passed to the url object of the server request being issued
+ * to detect the bad server certificates.
+ *
+ * @implements {nsIUrlListener}
+ */
+function TransportErrorUrlListener() {}
+
+TransportErrorUrlListener.prototype = {
+ OnStartRunningUrl(url) {},
+
+ OnStopRunningUrl(url, exitCode) {
+ if (Components.isSuccessCode(exitCode)) {
+ return;
+ }
+ let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService(
+ Ci.nsINSSErrorsService
+ );
+ try {
+ let errorClass = nssErrorsService.getErrorClass(exitCode);
+ if (errorClass == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
+ let mailNewsUrl = url.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ let secInfo = mailNewsUrl.failedSecInfo;
+ InformUserOfCertError(secInfo, url.asciiHostPort);
+ }
+ } catch (e) {
+ // It's not an NSS error.
+ }
+ },
+
+ // nsISupports
+ QueryInterface: ChromeUtils.generateQI(["nsIUrlListener"]),
+};
+
+function SendUnsentMessages() {
+ let msgSendlater = Cc["@mozilla.org/messengercompose/sendlater;1"].getService(
+ Ci.nsIMsgSendLater
+ );
+
+ for (let identity of MailServices.accounts.allIdentities) {
+ let msgFolder = msgSendlater.getUnsentMessagesFolder(identity);
+ if (msgFolder) {
+ let numMessages = msgFolder.getTotalMessages(
+ false /* include subfolders */
+ );
+ if (numMessages > 0) {
+ msgSendlater.sendUnsentMessages(identity);
+ // Right now, all identities point to the same unsent messages
+ // folder, so to avoid sending multiple copies of the
+ // unsent messages, we only call messenger.SendUnsentMessages() once.
+ // See bug #89150 for details.
+ break;
+ }
+ }
+ }
+}
+
+function CoalesceGetMsgsForPop3ServersByDestFolder(
+ currentServer,
+ pop3DownloadServersArray,
+ localFoldersToDownloadTo
+) {
+ var inboxFolder = currentServer.rootMsgFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+ // coalesce the servers that download into the same folder...
+ var index = localFoldersToDownloadTo.indexOf(inboxFolder);
+ if (index == -1) {
+ if (inboxFolder) {
+ inboxFolder.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NoMail;
+ inboxFolder.clearNewMessages();
+ }
+ localFoldersToDownloadTo.push(inboxFolder);
+ index = pop3DownloadServersArray.length;
+ pop3DownloadServersArray.push([]);
+ }
+ pop3DownloadServersArray[index].push(currentServer);
+}
+
+function GetMessagesForAllAuthenticatedAccounts() {
+ // now log into any server
+ try {
+ // Array of arrays of servers for a particular folder.
+ var pop3DownloadServersArray = [];
+ // parallel array of folders to download to...
+ var localFoldersToDownloadTo = [];
+ var pop3Server;
+
+ for (let server of MailServices.accounts.allServers) {
+ if (
+ server.protocolInfo.canGetMessages &&
+ !server.passwordPromptRequired
+ ) {
+ if (server.type == "pop3") {
+ CoalesceGetMsgsForPop3ServersByDestFolder(
+ server,
+ pop3DownloadServersArray,
+ localFoldersToDownloadTo
+ );
+ pop3Server = server.QueryInterface(Ci.nsIPop3IncomingServer);
+ } else {
+ // get new messages on the server for imap or rss
+ GetMessagesForInboxOnServer(server);
+ }
+ }
+ }
+ for (let i = 0; i < pop3DownloadServersArray.length; ++i) {
+ // any ol' pop3Server will do - the serversArray specifies which servers to download from
+ pop3Server.downloadMailFromServers(
+ pop3DownloadServersArray[i],
+ msgWindow,
+ localFoldersToDownloadTo[i],
+ null
+ );
+ }
+ } catch (ex) {
+ dump(ex + "\n");
+ }
+}
+
+function CommandUpdate_UndoRedo() {
+ EnableMenuItem("menu_undo", SetupUndoRedoCommand("cmd_undo"));
+ EnableMenuItem("menu_redo", SetupUndoRedoCommand("cmd_redo"));
+}
+
+function SetupUndoRedoCommand(command) {
+ let folder = document.getElementById("tabmail")?.currentTabInfo.folder;
+ if (!folder?.server.canUndoDeleteOnServer) {
+ return false;
+ }
+
+ let canUndoOrRedo = false;
+ let txnType;
+ try {
+ if (command == "cmd_undo") {
+ canUndoOrRedo = messenger.canUndo();
+ txnType = messenger.getUndoTransactionType();
+ } else {
+ canUndoOrRedo = messenger.canRedo();
+ txnType = messenger.getRedoTransactionType();
+ }
+ } catch (ex) {
+ // If this fails, assume we can't undo or redo.
+ console.error(ex);
+ }
+
+ if (canUndoOrRedo) {
+ let commands = {
+ [Ci.nsIMessenger.eUnknown]: "valueDefault",
+ [Ci.nsIMessenger.eDeleteMsg]: "valueDeleteMsg",
+ [Ci.nsIMessenger.eMoveMsg]: "valueMoveMsg",
+ [Ci.nsIMessenger.eCopyMsg]: "valueCopyMsg",
+ [Ci.nsIMessenger.eMarkAllMsg]: "valueUnmarkAllMsgs",
+ };
+ goSetMenuValue(command, commands[txnType]);
+ } else {
+ goSetMenuValue(command, "valueDefault");
+ }
+
+ return canUndoOrRedo;
+}
+
+/**
+ * Focus the gloda global search input box on current tab, or,
+ * if the search box is not available, open a new gloda search tab
+ * (with its search box focused).
+ */
+function QuickSearchFocus() {
+ // Default to focusing the search box on the current tab
+ let newTab = false;
+ let searchInput;
+ let tabmail = document.getElementById("tabmail");
+ // Tabmail should never be undefined.
+ if (!tabmail || tabmail.globalOverlay) {
+ return;
+ }
+
+ switch (tabmail.currentTabInfo.mode.name) {
+ case "glodaFacet":
+ // If we're currently viewing a Gloda tab, drill down to find the
+ // built-in search input, and select that.
+ searchInput = tabmail.currentTabInfo.panel.querySelector(
+ ".remote-gloda-search"
+ );
+ break;
+ default:
+ searchInput = document.querySelector(
+ "#unifiedToolbarContent .search-bar global-search-bar"
+ );
+ break;
+ }
+
+ if (!searchInput) {
+ // If searchInput is not found on current tab (e.g. removed by user),
+ // use a new tab.
+ newTab = true;
+ } else {
+ // The searchInput element exists on current tab.
+ // However, via toolbar customization, it can be in different places:
+ // Toolbars, tab bar, menu bar, etc. If the containing elements are hidden,
+ // searchInput will also be hidden, so clientHeight and clientWidth of the
+ // searchbox or one of its parents will typically be zero and we can test
+ // for that. If searchInput is hidden, use a new tab.
+ let element = searchInput;
+ while (element) {
+ if (element.clientHeight == 0 || element.clientWidth == 0) {
+ newTab = true;
+ }
+ element = element.parentElement;
+ }
+ }
+
+ if (!newTab) {
+ // Focus and select global search box on current tab.
+ if (searchInput.select) {
+ searchInput.select();
+ } else {
+ searchInput.focus();
+ }
+ } else {
+ // Open a new global search tab (with focus on its global search box)
+ tabmail.openTab("glodaFacet");
+ }
+}
+
+/**
+ * Open a new gloda search tab, with its search box focused.
+ */
+function openGlodaSearchTab() {
+ document.getElementById("tabmail").openTab("glodaFacet");
+}
+
+function MsgSearchAddresses() {
+ var args = { directory: null };
+ OpenOrFocusWindow(
+ args,
+ "mailnews:absearch",
+ "chrome://messenger/content/addressbook/abSearchDialog.xhtml"
+ );
+}
+
+function MsgFilterList(args) {
+ OpenOrFocusWindow(
+ args,
+ "mailnews:filterlist",
+ "chrome://messenger/content/FilterListDialog.xhtml"
+ );
+}
+
+function OpenOrFocusWindow(args, windowType, chromeURL) {
+ var desiredWindow = Services.wm.getMostRecentWindow(windowType);
+
+ if (desiredWindow) {
+ desiredWindow.focus();
+ if ("refresh" in args && args.refresh) {
+ desiredWindow.refresh(args);
+ }
+ } else {
+ window.openDialog(
+ chromeURL,
+ "",
+ "chrome,resizable,status,centerscreen,dialog=no",
+ args
+ );
+ }
+}
+
+function initAppMenuPopup() {
+ file_init();
+ view_init();
+ InitGoMessagesMenu();
+ menu_new_init();
+ CommandUpdate_UndoRedo();
+ document.commandDispatcher.updateCommands("create-menu-tasks");
+ UIFontSize.updateAppMenuButton(window);
+ initUiDensityAppMenu();
+
+ document.getElementById("appmenu_FolderViews").disabled =
+ document.getElementById("tabmail").currentTabInfo.mode.name !=
+ "mail3PaneTab";
+}
+
+/**
+ * Generate menu items that open a preferences dialog/tab for an installed addon,
+ * and add them to a menu popup. E.g. in the appmenu or Tools menu > addon prefs.
+ *
+ * @param {Element} parent - The element (e.g. menupopup) to populate.
+ * @param {string} [elementName] - The kind of menu item elements to create (e.g. "toolbarbutton").
+ * @param {string} [classes] - Classes for menu item elements with no icon.
+ * @param {string} [iconClasses] - Classes for menu item elements with an icon.
+ */
+async function initAddonPrefsMenu(
+ parent,
+ elementName = "menuitem",
+ classes,
+ iconClasses = "menuitem-iconic"
+) {
+ // Starting at the bottom, clear all menu items until we hit
+ // "no add-on prefs", which is the only disabled element. Above this element
+ // there may be further items that we want to preserve.
+ let noPrefsElem = parent.querySelector('[disabled="true"]');
+ while (parent.lastChild != noPrefsElem) {
+ parent.lastChild.remove();
+ }
+
+ // Enumerate all enabled addons with URL to XUL document with prefs.
+ let addonsFound = [];
+ for (let addon of await AddonManager.getAddonsByTypes(["extension"])) {
+ if (addon.userDisabled || addon.appDisabled || addon.softDisabled) {
+ continue;
+ }
+ if (addon.optionsURL) {
+ if (addon.optionsType == 5) {
+ addonsFound.push({
+ addon,
+ optionsURL: `addons://detail/${encodeURIComponent(
+ addon.id
+ )}/preferences`,
+ optionsOpenInAddons: true,
+ });
+ } else if (addon.optionsType === null || addon.optionsType == 3) {
+ addonsFound.push({
+ addon,
+ optionsURL: addon.optionsURL,
+ optionsOpenInTab: addon.optionsType == 3,
+ });
+ }
+ }
+ }
+
+ // Populate the menu with addon names and icons.
+ // Note: Having the following code in the getAddonsByTypes() async callback
+ // above works on Windows and Linux but doesn't work on Mac, see bug 1419145.
+ if (addonsFound.length > 0) {
+ addonsFound.sort((a, b) => a.addon.name.localeCompare(b.addon.name));
+ for (let {
+ addon,
+ optionsURL,
+ optionsOpenInTab,
+ optionsOpenInAddons,
+ } of addonsFound) {
+ let newItem = document.createXULElement(elementName);
+ newItem.setAttribute("label", addon.name);
+ newItem.setAttribute("value", optionsURL);
+ if (optionsOpenInTab) {
+ newItem.setAttribute("optionsType", "tab");
+ } else if (optionsOpenInAddons) {
+ newItem.setAttribute("optionsType", "addons");
+ }
+ let iconURL = addon.iconURL || addon.icon64URL;
+ if (iconURL) {
+ newItem.setAttribute("class", iconClasses);
+ newItem.setAttribute("image", iconURL);
+ } else if (classes) {
+ newItem.setAttribute("class", classes);
+ }
+ parent.appendChild(newItem);
+ }
+ noPrefsElem.setAttribute("collapsed", "true");
+ } else {
+ // Only show message that there are no addons with prefs.
+ noPrefsElem.setAttribute("collapsed", "false");
+ }
+}
+
+function openNewCardDialog() {
+ toAddressBook({ action: "create" });
+}
+
+/**
+ * Opens Address Book tab and triggers address book creation dialog defined
+ * type.
+ *
+ * @param {?string}[type = "JS"] type - The address book type needing creation.
+ */
+function openNewABDialog(type = "JS") {
+ toAddressBook({ action: `create_ab_${type}` });
+}
+
+/**
+ * Verifies we have the attachments in order to populate the menupopup.
+ * Resets the popup to be populated.
+ *
+ * @param {DOMEvent} event - The popupshowing event.
+ */
+function fillAttachmentListPopup(event) {
+ if (event.target.id != "attachmentMenuList") {
+ return;
+ }
+
+ const popup = event.target;
+
+ // Clear out the old menupopup.
+ while (popup.firstElementChild?.localName == "menu") {
+ popup.firstElementChild?.remove();
+ }
+
+ let aboutMessage =
+ document.getElementById("tabmail")?.currentAboutMessage ||
+ document.getElementById("messageBrowser")?.contentWindow;
+ if (!aboutMessage) {
+ return;
+ }
+
+ let attachments = aboutMessage.currentAttachments;
+ for (let [index, attachment] of attachments.entries()) {
+ addAttachmentToPopup(aboutMessage, popup, attachment, index);
+ }
+ aboutMessage.goUpdateAttachmentCommands();
+}
+
+/**
+ * Add each attachment to the menupop up before the menuseparator and create
+ * a submenu with the attachments' options (open, save, detach and delete).
+ *
+ * @param {?Window} aboutMessage - The current message on the message pane.
+ * @param {XULPopupElement} popup - #attachmentMenuList menupopup.
+ * @param {AttachmentInfo} attachment - The file attached to the email.
+ * @param {integer} attachmentIndex - The attachment's index.
+ */
+function addAttachmentToPopup(
+ aboutMessage,
+ popup,
+ attachment,
+ attachmentIndex
+) {
+ let item = document.createXULElement("menu");
+
+ function getString(aName) {
+ return document.getElementById("bundle_messenger").getString(aName);
+ }
+
+ // Insert the item just before the separator. The separator is the 2nd to
+ // last element in the popup.
+ item.classList.add("menu-iconic");
+ item.setAttribute("image", getIconForAttachment(attachment));
+
+ const separator = popup.querySelector("menuseparator");
+
+ // We increment the attachmentIndex here since we only use it for the
+ // label and accesskey attributes, and we want the accesskeys for the
+ // attachments list in the menu to be 1-indexed.
+ attachmentIndex++;
+
+ let displayName = SanitizeAttachmentDisplayName(attachment);
+ let label = document
+ .getElementById("bundle_messenger")
+ .getFormattedString("attachmentDisplayNameFormat", [
+ attachmentIndex,
+ displayName,
+ ]);
+ item.setAttribute("crop", "center");
+ item.setAttribute("label", label);
+ item.setAttribute("accesskey", attachmentIndex % 10);
+
+ // Each attachment in the list gets its own menupopup with options for
+ // saving, deleting, detaching, etc.
+ let menupopup = document.createXULElement("menupopup");
+ menupopup = item.appendChild(menupopup);
+
+ item = popup.insertBefore(item, separator);
+
+ if (attachment.isExternalAttachment) {
+ if (!attachment.hasFile) {
+ item.classList.add("notfound");
+ } else {
+ // The text-link class must be added to the <label> and have a <menu>
+ // hover rule. Adding to <menu> makes hover overflow the underline to
+ // the popup items.
+ let label = item.children[1];
+ label.classList.add("text-link");
+ }
+ }
+
+ if (attachment.isDeleted) {
+ item.classList.add("notfound");
+ }
+
+ let detached = attachment.isExternalAttachment;
+ let deleted = !attachment.hasFile;
+ let canDetach = aboutMessage?.CanDetachAttachments() && !deleted && !detached;
+
+ if (deleted) {
+ // We can't do anything with a deleted attachment, so just return.
+ item.disabled = true;
+ return;
+ }
+
+ // Create the "open" menu item
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.attachment = attachment;
+ menuitem.addEventListener("command", () =>
+ attachment.open(aboutMessage.browsingContext)
+ );
+ menuitem.setAttribute("label", getString("openLabel"));
+ menuitem.setAttribute("accesskey", getString("openLabelAccesskey"));
+ menuitem.setAttribute("disabled", deleted);
+ menuitem = menupopup.appendChild(menuitem);
+
+ // Create the "save" menu item
+ menuitem = document.createXULElement("menuitem");
+ menuitem.attachment = attachment;
+ menuitem.addEventListener("command", () => attachment.save(messenger));
+ menuitem.setAttribute("label", getString("saveLabel"));
+ menuitem.setAttribute("accesskey", getString("saveLabelAccesskey"));
+ menuitem.setAttribute("disabled", deleted);
+ menuitem = menupopup.appendChild(menuitem);
+
+ // Create the "detach" menu item
+ menuitem = document.createXULElement("menuitem");
+ menuitem.attachment = attachment;
+ menuitem.addEventListener("command", () =>
+ attachment.detach(messenger, true)
+ );
+ menuitem.setAttribute("label", getString("detachLabel"));
+ menuitem.setAttribute("accesskey", getString("detachLabelAccesskey"));
+ menuitem.setAttribute("disabled", !canDetach);
+ menuitem = menupopup.appendChild(menuitem);
+
+ // Create the "delete" menu item
+ menuitem = document.createXULElement("menuitem");
+ menuitem.attachment = attachment;
+ menuitem.addEventListener("command", () =>
+ attachment.detach(messenger, false)
+ );
+ menuitem.setAttribute("label", getString("deleteLabel"));
+ menuitem.setAttribute("accesskey", getString("deleteLabelAccesskey"));
+ menuitem.setAttribute("disabled", !canDetach);
+ menuitem = menupopup.appendChild(menuitem);
+
+ // Create the "open containing folder" menu item, for existing detached only.
+ if (attachment.isFileAttachment) {
+ let menuseparator = document.createXULElement("menuseparator");
+ menupopup.appendChild(menuseparator);
+ menuitem = document.createXULElement("menuitem");
+ menuitem.attachment = attachment;
+ menuitem.setAttribute("oncommand", "this.attachment.openFolder();");
+ menuitem.setAttribute("label", getString("openFolderLabel"));
+ menuitem.setAttribute("accesskey", getString("openFolderLabelAccesskey"));
+ menuitem.setAttribute("disabled", !attachment.hasFile);
+ menuitem = menupopup.appendChild(menuitem);
+ }
+}
+
+/**
+ * Return the string of the corresponding type of attachment's icon.
+ *
+ * @param {AttachmentInfo} attachment - The file attached to the email.
+ * @returns {string}
+ */
+function getIconForAttachment(attachment) {
+ return attachment.isDeleted
+ ? "chrome://messenger/skin/icons/attachment-deleted.svg"
+ : `moz-icon://${attachment.name}?size=16&amp;contentType=${attachment.contentType}`;
+}
+
+/**
+ * Opens the Address Book to add the email address from the given mailto: URL.
+ *
+ * @param {string} url
+ */
+function addEmail(url) {
+ let addresses = getEmail(url);
+ toAddressBook({
+ action: "create",
+ address: addresses,
+ });
+}
+
+/**
+ * Extracts email address(es) from the given mailto: URL.
+ *
+ * @param {string} url
+ * @returns {string}
+ */
+function getEmail(url) {
+ let mailtolength = 7;
+ let qmark = url.indexOf("?");
+ let addresses;
+
+ if (qmark > mailtolength) {
+ addresses = url.substring(mailtolength, qmark);
+ } else {
+ addresses = url.substr(mailtolength);
+ }
+ // Let's try to unescape it using a character set
+ try {
+ addresses = Services.textToSubURI.unEscapeURIForUI(addresses);
+ } catch (ex) {
+ // Do nothing.
+ }
+ return addresses;
+}
+
+/**
+ * Begins composing an email to the address from the given mailto: URL.
+ *
+ * @param {string} linkURL
+ * @param {nsIMsgIdentity} [identity] - The identity to use, otherwise the
+ * default identity is used.
+ */
+function composeEmailTo(linkURL, identity) {
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ fields.to = getEmail(linkURL);
+ params.type = Ci.nsIMsgCompType.New;
+ params.format = Ci.nsIMsgCompFormat.Default;
+ if (identity) {
+ params.identity = identity;
+ }
+ params.composeFields = fields;
+ MailServices.compose.OpenComposeWindowWithParams(null, params);
+}