diff options
Diffstat (limited to '')
-rw-r--r-- | comm/suite/mailnews/content/mailWindowOverlay.js | 2695 |
1 files changed, 2695 insertions, 0 deletions
diff --git a/comm/suite/mailnews/content/mailWindowOverlay.js b/comm/suite/mailnews/content/mailWindowOverlay.js new file mode 100644 index 0000000000..223587e914 --- /dev/null +++ b/comm/suite/mailnews/content/mailWindowOverlay.js @@ -0,0 +1,2695 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm"); +var {FeedUtils} = ChromeUtils.import("resource:///modules/FeedUtils.jsm"); +var { FolderUtils } = ChromeUtils.import("resource:///modules/FolderUtils.jsm"); +var {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm"); +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js"); + +var kClassicMailLayout = 0; +var kWideMailLayout = 1; +var kVerticalMailLayout = 2; + +var kMouseButtonLeft = 0; +var kMouseButtonMiddle = 1; +var kMouseButtonRight = 2; + +// Per message header flags to keep track of whether the user is allowing remote +// content for a particular message. +// if you change or add more values to these constants, be sure to modify +// the corresponding definitions in nsMsgContentPolicy.cpp +var kNoRemoteContentPolicy = 0; +var kBlockRemoteContent = 1; +var kAllowRemoteContent = 2; + +var kIsAPhishMessage = 0; +var kNotAPhishMessage = 1; + +var kMsgForwardAsAttachment = 0; + +var gMessengerBundle; +var gOfflineManager; +// Timer to mark read, if the user has configured the app to mark a message as +// read if it is viewed for more than n seconds. +var gMarkViewedMessageAsReadTimer = null; + +// The user preference, if HTML is not allowed. Assume, that the user could have +// set this to a value > 1 in their 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 File | New | Account... menu item if the account preference is +// locked. Two other affected areas are the account central and the account +// manager dialogs. +function menu_new_init() { + let folders = GetSelectedMsgFolders(); + if (folders.length != 1) + return; + + let folder = folders[0]; + + if (!gMessengerBundle) + gMessengerBundle = document.getElementById("bundle_messenger"); + + if (Services.prefs.prefIsLocked("mail.disable_new_account_addition")) + document.getElementById("newAccountMenuItem") + .setAttribute("disabled", "true"); + + let isInbox = folder.isSpecialFolder(Ci.nsMsgFolderFlags.Inbox, false); + let showNew = folder.canCreateSubfolders || + (isInbox && !(folder.flags & Ci.nsMsgFolderFlags.Virtual)); + ShowMenuItem("menu_newFolder", showNew); + ShowMenuItem("menu_newVirtualFolder", showNew); + EnableMenuItem("menu_newFolder", folder.server.type != "imap" || + !Services.io.offline); + if (showNew) { + // Change "New Folder..." menu according to the context. + let label = (folder.isServer || isInbox) ? "newFolderMenuItem" : + "newSubfolderMenuItem"; + SetMenuItemLabel("menu_newFolder", gMessengerBundle.getString(label)); + } +} + +function goUpdateMailMenuItems(commandset) { + for (var i = 0; i < commandset.childNodes.length; i++) { + var commandID = commandset.childNodes[i].getAttribute("id"); + if (commandID) + goUpdateCommand(commandID); + } +} + +function file_init() { + document.commandDispatcher.updateCommands("create-menu-file"); +} + +function InitEditMessagesMenu() { + goSetMenuValue("cmd_delete", "valueDefault"); + goSetAccessKey("cmd_delete", "valueDefaultAccessKey"); + document.commandDispatcher.updateCommands("create-menu-edit"); + + // initialize the favorite Folder checkbox in the edit menu + let favoriteFolderMenu = document.getElementById("menu_favoriteFolder"); + if (!favoriteFolderMenu.hasAttribute("disabled")) { + let folders = GetSelectedMsgFolders(); + if (folders.length == 1 && !folders[0].isServer) { + let checked = folders[0].getFlag(Ci.nsMsgFolderFlags.Favorite); + // Adjust the checked state on the menu item. + favoriteFolderMenu.setAttribute("checked", checked); + favoriteFolderMenu.hidden = false; + } else { + favoriteFolderMenu.hidden = true; + } + } +} + +function InitGoMessagesMenu() { + // deactivate the folders in the go menu if we don't have a folderpane + document.getElementById("goFolderMenu") + .setAttribute("disabled", IsFolderPaneCollapsed()); + document.commandDispatcher.updateCommands("create-menu-go"); +} + +function view_init() { + if (!gMessengerBundle) + gMessengerBundle = document.getElementById("bundle_messenger"); + + var message_menuitem = document.getElementById("menu_showMessagePane"); + if (message_menuitem && !message_menuitem.hidden) { + message_menuitem.setAttribute("checked", !IsMessagePaneCollapsed()); + message_menuitem.setAttribute("disabled", gAccountCentralLoaded); + } + + var threadpane_menuitem = document.getElementById("menu_showThreadPane"); + if (threadpane_menuitem && !threadpane_menuitem.hidden) { + threadpane_menuitem.setAttribute("checked", !IsDisplayDeckCollapsed()); + threadpane_menuitem.setAttribute("disabled", gAccountCentralLoaded); + } + + var folderPane_menuitem = document.getElementById("menu_showFolderPane"); + if (folderPane_menuitem && !folderPane_menuitem.hidden) + folderPane_menuitem.setAttribute("checked", !IsFolderPaneCollapsed()); + + document.getElementById("viewSortMenu").disabled = gAccountCentralLoaded; + document.getElementById("viewMessageViewMenu").disabled = gAccountCentralLoaded; + document.getElementById("viewMessagesMenu").disabled = gAccountCentralLoaded; + document.getElementById("charsetMenu").disabled = !gMessageDisplay.displayedMessage; + + // Initialize the Message Body menuitem + let isFeed = gFolderDisplay && + ((gFolderDisplay.displayedFolder && + gFolderDisplay.displayedFolder.server.type == "rss") || + gFolderDisplay.selectedMessageIsFeed); + document.getElementById("viewBodyMenu").hidden = isFeed; + + // Initialize the Show Feed Summary menu + let viewFeedSummary = document.getElementById("viewFeedSummary"); + viewFeedSummary.hidden = !isFeed || + document.documentElement.getAttribute("windowtype") != "mail:3pane"; + + 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 Display Attachments Inline menu. + var viewAttachmentInline = Services.prefs.getBoolPref("mail.inline_attachments"); + document.getElementById("viewAttachmentsInlineMenuitem") + .setAttribute("checked", viewAttachmentInline); + + document.commandDispatcher.updateCommands("create-menu-view"); +} + +function InitViewLayoutStyleMenu(event) { + var paneConfig = Services.prefs.getIntPref("mail.pane_config.dynamic"); + var layoutStyleMenuitem = event.target.childNodes[paneConfig]; + if (layoutStyleMenuitem) + layoutStyleMenuitem.setAttribute("checked", "true"); +} + +function setSortByMenuItemCheckState(id, value) { + var menuitem = document.getElementById(id); + if (menuitem) { + menuitem.setAttribute("checked", value); + } +} + +function InitViewSortByMenu() { + var sortType = gDBView.sortType; + + setSortByMenuItemCheckState("sortByDateMenuitem", + sortType == Ci.nsMsgViewSortType.byDate); + setSortByMenuItemCheckState("sortByReceivedMenuitem", + sortType == Ci.nsMsgViewSortType.byReceived); + setSortByMenuItemCheckState("sortByFlagMenuitem", + sortType == Ci.nsMsgViewSortType.byFlagged); + setSortByMenuItemCheckState("sortByOrderReceivedMenuitem", + sortType == Ci.nsMsgViewSortType.byId); + setSortByMenuItemCheckState("sortByPriorityMenuitem", + sortType == Ci.nsMsgViewSortType.byPriority); + setSortByMenuItemCheckState("sortBySizeMenuitem", + sortType == Ci.nsMsgViewSortType.bySize); + setSortByMenuItemCheckState("sortByStatusMenuitem", + sortType == Ci.nsMsgViewSortType.byStatus); + setSortByMenuItemCheckState("sortBySubjectMenuitem", + sortType == Ci.nsMsgViewSortType.bySubject); + setSortByMenuItemCheckState("sortByUnreadMenuitem", + sortType == Ci.nsMsgViewSortType.byUnread); + setSortByMenuItemCheckState("sortByTagsMenuitem", + sortType == Ci.nsMsgViewSortType.byTags); + setSortByMenuItemCheckState("sortByJunkStatusMenuitem", + sortType == Ci.nsMsgViewSortType.byJunkStatus); + setSortByMenuItemCheckState("sortByFromMenuitem", + sortType == Ci.nsMsgViewSortType.byAuthor); + setSortByMenuItemCheckState("sortByRecipientMenuitem", + sortType == Ci.nsMsgViewSortType.byRecipient); + setSortByMenuItemCheckState("sortByAttachmentsMenuitem", + sortType == Ci.nsMsgViewSortType.byAttachments); + + var sortOrder = gDBView.sortOrder; + var sortTypeSupportsGrouping = (sortType == Ci.nsMsgViewSortType.byAuthor || + sortType == Ci.nsMsgViewSortType.byDate || + sortType == Ci.nsMsgViewSortType.byReceived || + sortType == Ci.nsMsgViewSortType.byPriority || + sortType == Ci.nsMsgViewSortType.bySubject || + sortType == Ci.nsMsgViewSortType.byTags || + sortType == Ci.nsMsgViewSortType.byRecipient || + sortType == Ci.nsMsgViewSortType.byFlagged || + sortType == Ci.nsMsgViewSortType.byAttachments); + + setSortByMenuItemCheckState("sortAscending", + sortOrder == Ci.nsMsgViewSortOrder.ascending); + setSortByMenuItemCheckState("sortDescending", + sortOrder == Ci.nsMsgViewSortOrder.descending); + + var grouped = ((gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort) != 0); + var threaded = ((gDBView.viewFlags & Ci.nsMsgViewFlagsType.kThreadedDisplay) != 0 && !grouped); + var sortThreadedMenuItem = document.getElementById("sortThreaded"); + var sortUnthreadedMenuItem = document.getElementById("sortUnthreaded"); + + sortThreadedMenuItem.setAttribute("checked", threaded); + sortUnthreadedMenuItem.setAttribute("checked", !threaded && !grouped); + + var groupBySortOrderMenuItem = document.getElementById("groupBySort"); + + groupBySortOrderMenuItem.setAttribute("disabled", !sortTypeSupportsGrouping); + groupBySortOrderMenuItem.setAttribute("checked", grouped); +} + +function InitViewMessagesMenu() { + var viewFlags = gDBView ? gDBView.viewFlags : 0; + var viewType = gDBView ? gDBView.viewType : 0; + + document.getElementById("viewAllMessagesMenuItem").setAttribute("checked", + (viewFlags & Ci.nsMsgViewFlagsType.kUnreadOnly) == 0 && + (viewType == Ci.nsMsgViewType.eShowAllThreads)); + + document.getElementById("viewUnreadMessagesMenuItem").setAttribute("checked", + (viewFlags & Ci.nsMsgViewFlagsType.kUnreadOnly) != 0); + + document.getElementById("viewThreadsWithUnreadMenuItem").setAttribute("checked", + viewType == Ci.nsMsgViewType.eShowThreadsWithUnread); + + document.getElementById("viewWatchedThreadsWithUnreadMenuItem").setAttribute("checked", + viewType == Ci.nsMsgViewType.eShowWatchedThreadsWithUnread); + + document.getElementById("viewIgnoredThreadsMenuItem").setAttribute("checked", + (viewFlags & Ci.nsMsgViewFlagsType.kShowIgnored) != 0); +} + +function InitMessageMenu() { + var selectedMsg = gFolderDisplay.selectedMessage; + var isNews = gFolderDisplay.selectedMessageIsNews; + var isFeed = gFolderDisplay.selectedMessageIsFeed; + + // We show Reply to Newsgroups only for news messages. + document.getElementById("replyNewsgroupMainMenu").hidden = !isNews; + + // We show Reply to List only for list posts. + document.getElementById("replyListMainMenu").hidden = isNews || !IsListPost(); + + // For mail messages we say reply. For news we say ReplyToSender. + document.getElementById("replyMainMenu").hidden = isNews; + document.getElementById("replySenderMainMenu").hidden = !isNews; + + // We show Reply to Sender and Newsgroup only for news messages. + document.getElementById("replySenderAndNewsgroupMainMenu").hidden = !isNews; + + // For mail messages we say reply all. For news we say ReplyToAllRecipients. + document.getElementById("replyallMainMenu").hidden = isNews; + document.getElementById("replyAllRecipientsMainMenu").hidden = !isNews; + + // We only show Ignore Thread and Watch Thread menu items for news. + document.getElementById("threadItemsSeparator").hidden = !isNews; + document.getElementById("killThread").hidden = !isNews; + document.getElementById("killSubthread").hidden = !isNews; + document.getElementById("watchThread").hidden = !isNews; + document.getElementById("menu_cancel").hidden = !isNews; + + // Disable the Move and Copy menus if there are no messages selected. + // Disable the Move menu if we can't delete messages from the folder. + var msgFolder = GetLoadedMsgFolder(); + var enableMenuItem = !isNews && selectedMsg && + msgFolder && msgFolder.canDeleteMessages; + document.getElementById("moveMenu").disabled = !enableMenuItem; + + // Also disable copy when no folder is loaded (like for .eml files). + var canCopy = selectedMsg && (!gMessageDisplay.isDummy || + window.arguments[0].scheme == "file"); + document.getElementById("copyMenu").disabled = !canCopy; + + // Disable the Forward as/Tag menu items if no message is selected. + document.getElementById("forwardAsMenu").disabled = !selectedMsg; + document.getElementById("tagMenu").disabled = !selectedMsg; + + // 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", + Ci.nsMsgFolderFlags.Templates); + showCommandInSpecialFolder("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") + .childNodes[index].setAttribute("checked", true); + + let openRssMenu = document.getElementById("openFeedMessage"); + openRssMenu.hidden = !isFeed; + if (winType != "mail:3pane") + openRssMenu.hidden = true; + + // Disable the Mark menu when we're not in a folder. + document.getElementById("markMenu").disabled = !msgFolder; + + document.commandDispatcher.updateCommands("create-menu-message"); +} + +/** + * Show folder-specific menu items only for messages in special folders, e.g. + * show 'cmd_editDraftMsg' in Drafts folder. + * show 'cmd_newMsgFromTemplate' in Templates folder. + * + * aCommandId the ID of a command to be shown in folders having aFolderFlag + * aFolderFlag the nsMsgFolderFlag that the folder must have to show the + * command + */ +function showCommandInSpecialFolder(aCommandId, aFolderFlag) { + let msg = gFolderDisplay.selectedMessage; + let folder = gFolderDisplay.displayedFolder; + // Check msg.folder exists as messages opened from a file have none. + let inSpecialFolder = (msg && + msg.folder && + msg.folder.isSpecialFolder(aFolderFlag, true)) || + (folder && folder.getFlag(aFolderFlag)); + document.getElementById(aCommandId).setAttribute("hidden", !inSpecialFolder); + return inSpecialFolder; +} + +function InitViewHeadersMenu() { + var headerchoice = + Services.prefs.getIntPref("mail.show_headers", + Ci.nsMimeHeaderDisplayTypes.NormalHeaders); + document + .getElementById("cmd_viewAllHeader") + .setAttribute("checked", + headerchoice == Ci.nsMimeHeaderDisplayTypes.AllHeaders); + document + .getElementById("cmd_viewNormalHeader") + .setAttribute("checked", + headerchoice == Ci.nsMimeHeaderDisplayTypes.NormalHeaders); + document.commandDispatcher.updateCommands("create-menu-mark"); +} + +function InitViewBodyMenu() { + // 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 = gFolderDisplay.selectedMessageIsFeed; + 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 inital 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; + if (!isFeed) { + AllBodyParts_menuitem = document.getElementById(menuIDs[3]); + AllBodyParts_menuitem.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 = !FeedMessageHandler.gShowSummary; + } +} + +function SetMenuItemLabel(menuItemId, customLabel) { + var menuItem = document.getElementById(menuItemId); + if (menuItem) + menuItem.setAttribute("label", customLabel); +} + +function RemoveAllMessageTags() { + var selectedMessages = gFolderDisplay.selectedMessages; + if (!selectedMessages.length) + return; + + var messages = []; + var tagArray = MailServices.tags.getAllTags(); + + var allKeys = ""; + for (let j = 0; j < tagArray.length; ++j) { + if (j) + allKeys += " "; + allKeys += tagArray[j].key; + } + + var prevHdrFolder = null; + // this crudely handles cross-folder virtual folders with selected messages + // that spans folders, by coalescing consecutive messages in the selection + // that happen to be in the same folder. nsMsgSearchDBView does this better, + // but nsIMsgDBView doesn't handle commands with arguments, and untag takes a + // key argument. Furthermore, we only delete legacy labels and known tags, + // keeping other keywords like (non)junk intact. + + for (let i = 0; i < selectedMessages.length; ++i) { + var msgHdr = selectedMessages[i]; + msgHdr.label = 0; // remove legacy label + if (prevHdrFolder != msgHdr.folder) { + if (prevHdrFolder) + prevHdrFolder.removeKeywordsFromMessages(messages, allKeys); + messages = []; + prevHdrFolder = msgHdr.folder; + } + messages.push(msgHdr); + } + if (prevHdrFolder) + prevHdrFolder.removeKeywordsFromMessages(messages, allKeys); + OnTagsChange(); +} + +function InitNewMsgMenu(aPopup) { + var identity = null; + var folder = GetFirstSelectedMsgFolder(); + if (folder) + identity = getIdentityForServer(folder.server); + if (!identity) { + let defaultAccount = MailServices.accounts.defaultAccount; + if (defaultAccount) + identity = defaultAccount.defaultIdentity; + } + + // If the identity is not found, use the mail.html_compose pref to + // determine the message compose type (HTML or PlainText). + var composeHTML = identity ? identity.composeHtml + : Services.prefs.getBoolPref("mail.html_compose"); + const kIDs = {true: "button-newMsgHTML", false: "button-newMsgPlain"}; + document.getElementById(kIDs[composeHTML]).setAttribute("default", "true"); + document.getElementById(kIDs[!composeHTML]).removeAttribute("default"); +} + +function InitMessageReply(aPopup) { + var isNews = gFolderDisplay.selectedMessageIsNews; + // For mail messages we say reply. For news we say ReplyToSender. + // We show Reply to Newsgroups only for news messages. + aPopup.childNodes[0].hidden = isNews; // Reply + aPopup.childNodes[1].hidden = isNews || !IsListPost(); // Reply to List + aPopup.childNodes[2].hidden = !isNews; // Reply to Newsgroup + aPopup.childNodes[3].hidden = !isNews; // Reply to Sender Only +} + +function InitMessageForward(aPopup) { + var forwardType = Services.prefs.getIntPref("mail.forward_message_mode"); + + if (forwardType != kMsgForwardAsAttachment) { + // forward inline is the first menuitem + aPopup.firstChild.setAttribute("default", "true"); + aPopup.lastChild.removeAttribute("default"); + } else { + // attachment is the last menuitem + aPopup.lastChild.setAttribute("default", "true"); + aPopup.firstChild.removeAttribute("default"); + } +} + +function ToggleMessageTagKey(index) { + // toggle the tag state based upon that of the first selected message + var msgHdr = gFolderDisplay.selectedMessage; + if (!msgHdr) + return; + + var tagArray = MailServices.tags.getAllTags(); + for (var i = 0; i < tagArray.length; ++i) { + var key = tagArray[i].key; + if (!--index) { + // found the key, now toggle its state + var curKeys = msgHdr.getStringProperty("keywords"); + if (msgHdr.label) + curKeys += " $label" + msgHdr.label; + var addKey = !(" " + curKeys + " ").includes(" " + key + " "); + ToggleMessageTag(key, addKey); + return; + } + } +} + +function ToggleMessageTagMenu(target) { + var key = target.getAttribute("value"); + var addKey = target.getAttribute("checked") == "true"; + ToggleMessageTag(key, addKey); +} + +function ToggleMessageTag(key, addKey) { + var messages = []; + var selectedMessages = gFolderDisplay.selectedMessages; + var toggler = addKey ? "addKeywordsToMessages" : "removeKeywordsFromMessages"; + var prevHdrFolder = null; + // this crudely handles cross-folder virtual folders with selected messages + // that spans folders, by coalescing consecutive msgs in the selection + // that happen to be in the same folder. nsMsgSearchDBView does this + // better, but nsIMsgDBView doesn't handle commands with arguments, + // and (un)tag takes a key argument. + for (let i = 0; i < selectedMessages.length; ++i) { + var msgHdr = selectedMessages[i]; + if (msgHdr.label) { + // Since we touch all these messages anyway, migrate the label now. + // If we don't, the thread tree won't always show the correct tag state, + // because resetting a label doesn't update the tree anymore... + msgHdr.folder.addKeywordsToMessages([msgHdr], "$label" + msgHdr.label); + msgHdr.label = 0; // remove legacy label + } + if (prevHdrFolder != msgHdr.folder) { + if (prevHdrFolder) + prevHdrFolder[toggler](messages, key); + messages = []; + prevHdrFolder = msgHdr.folder; + } + messages.push(msgHdr); + } + if (prevHdrFolder) + prevHdrFolder[toggler](messages, key); + OnTagsChange(); +} + +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>) + var shortcutkey = document.getElementById("key_tag" + index); + var accesskey = shortcutkey ? shortcutkey.getAttribute("key") : ""; + if (accesskey) + menuitem.setAttribute("accesskey", accesskey); + var label = gMessengerBundle.getFormattedString("mailnews.tags.format", + [accesskey, name]); + menuitem.setAttribute("label", label); +} + +function InitMessageTags(menuPopup) { + var tagArray = MailServices.tags.getAllTags(); + var tagCount = tagArray.length; + + // remove any existing non-static entries... + var menuseparator = menuPopup.lastChild.previousSibling; + for (var i = menuPopup.childNodes.length; i > 4; --i) + menuseparator.previousSibling.remove(); + + // hide double menuseparator + menuseparator.previousSibling.hidden = !tagCount; + + // create label and accesskey for the static remove item + var tagRemoveLabel = gMessengerBundle.getString("mailnews.tags.remove"); + SetMessageTagLabel(menuPopup.firstChild, 0, tagRemoveLabel); + + // now rebuild the list + var msgHdr = gFolderDisplay.selectedMessage; + var curKeys = msgHdr.getStringProperty("keywords"); + if (msgHdr.label) + curKeys += " $label" + msgHdr.label; + for (var i = 0; i < tagCount; ++i) { + var taginfo = tagArray[i]; + var removeKey = (" " + curKeys + " ").includes(" " + taginfo.key + " "); + if (taginfo.ordinal.includes("~AUTOTAG") && !removeKey) + continue; + + // TODO we want to either remove or "check" the tags that already exist + var newMenuItem = document.createElement("menuitem"); + SetMessageTagLabel(newMenuItem, i + 1, taginfo.tag); + newMenuItem.setAttribute("value", taginfo.key); + newMenuItem.setAttribute("type", "checkbox"); + newMenuItem.setAttribute("checked", removeKey); + newMenuItem.setAttribute("oncommand", "ToggleMessageTagMenu(event.target);"); + var color = taginfo.color; + if (color) + newMenuItem.setAttribute("class", "lc-" + color.substr(1)); + menuPopup.insertBefore(newMenuItem, menuseparator); + } +} + +function InitBackToolbarMenu(menuPopup) { + PopulateHistoryMenu(menuPopup, -1); +} + +function InitForwardToolbarMenu(menuPopup) { + PopulateHistoryMenu(menuPopup, 1); +} + +function PopulateHistoryMenu(menuPopup, navOffset) { + // remove existing entries + while (menuPopup.hasChildNodes()) + menuPopup.lastChild.remove(); + + let startPos = messenger.navigatePos; + let historyArray = messenger.getNavigateHistory(); + let maxPos = historyArray.length / 2; // Array consists of pairs. + if (GetLoadedMessage()) + startPos += navOffset; + + // starting from the current entry, march through history until we reach + // the array border or our menuitem limit + for (var i = startPos, itemCount = 0; + (i >= 0) && (i < maxPos) && (itemCount < 25); + i += navOffset, ++itemCount) { + var menuText = ""; + let folder = MailUtils.getFolderForURI(historyArray[i * 2 + 1]); + if (!IsCurrentLoadedFolder(folder)) + menuText += folder.prettyName + ": "; + + var msgHdr = messenger.msgHdrFromURI(historyArray[i * 2]); + var subject = ""; + if (msgHdr.flags & Ci.nsMsgMessageFlags.HasRe) + subject = "Re: "; + if (msgHdr.mime2DecodedSubject) + subject += msgHdr.mime2DecodedSubject; + if (subject) + menuText += subject + " - "; + menuText += msgHdr.mime2DecodedAuthor; + + var newMenuItem = document.createElement("menuitem"); + newMenuItem.setAttribute("label", menuText); + newMenuItem.setAttribute("value", i - startPos); + newMenuItem.folder = folder; + menuPopup.appendChild(newMenuItem); + } +} + +function NavigateToUri(target) { + var historyIndex = target.getAttribute("value"); + var msgUri = messenger.getMsgUriAtNavigatePos(historyIndex); + let msgHdrKey = messenger.msgHdrFromURI(msgUri).messageKey; + messenger.navigatePos += Number(historyIndex); + if (target.folder.URI == GetThreadPaneFolder().URI) { + gDBView.selectMsgByKey(msgHdrKey); + } else { + gStartMsgKey = msgHdrKey; + SelectMsgFolder(target.folder); + } +} + +function InitMessageMark() { + document.getElementById("cmd_markAsFlagged") + .setAttribute("checked", SelectedMessagesAreFlagged()); + + document.commandDispatcher.updateCommands("create-menu-mark"); +} + +function UpdateJunkToolbarButton() { + var junkButtonDeck = document.getElementById("junk-deck"); + // Wallpaper over Bug 491676 by using the attribute instead of the property. + junkButtonDeck.setAttribute("selectedIndex", SelectedMessagesAreJunk() ? 1 : 0); +} + +function UpdateDeleteToolbarButton(aFolderPaneHasFocus) { + var deleteButtonDeck = document.getElementById("delete-deck"); + var selectedIndex = 0; + + // Never show "Undelete" in the 3-pane for folders, when delete would + // apply to the selected folder. + if (!aFolderPaneHasFocus && SelectedMessagesAreDeleted()) + selectedIndex = 1; + + // Wallpaper over Bug 491676 by using the attribute instead of the property. + deleteButtonDeck.setAttribute("selectedIndex", selectedIndex); +} + +function UpdateDeleteCommand() { + var value = "value"; + if (SelectedMessagesAreDeleted()) + value += "IMAPDeleted"; + if (GetNumSelectedMessages() < 2) + value += "Message"; + else + value += "Messages"; + goSetMenuValue("cmd_delete", value); + goSetAccessKey("cmd_delete", value + "AccessKey"); +} + +function SelectedMessagesAreDeleted() { + var firstSelectedMessage = gFolderDisplay.selectedMessage; + return firstSelectedMessage && + (firstSelectedMessage.flags & + Ci.nsMsgMessageFlags.IMAPDeleted); +} + +function SelectedMessagesAreJunk() { + var firstSelectedMessage = gFolderDisplay.selectedMessage; + if (!firstSelectedMessage) + return false; + + var junkScore = firstSelectedMessage.getStringProperty("junkscore"); + return (junkScore != "") && (junkScore != "0"); +} + +function SelectedMessagesAreRead() { + let messages = gFolderDisplay.selectedMessages; + if (messages.length == 0) + return undefined; + if (messages.every(function(msg) { return msg.isRead; })) + return true; + if (messages.every(function(msg) { return !msg.isRead; })) + return false; + return undefined; +} + +function SelectedMessagesAreFlagged() { + var firstSelectedMessage = gFolderDisplay.selectedMessage; + return firstSelectedMessage && firstSelectedMessage.isFlagged; +} + +function getMsgToolbarMenu_init() { + document.commandDispatcher.updateCommands("create-menu-getMsgToolbar"); +} + +function GetFirstSelectedMsgFolder() { + var selectedFolders = GetSelectedMsgFolders(); + return (selectedFolders.length > 0) ? selectedFolders[0] : null; +} + +function GetInboxFolder(server) { + try { + var rootMsgFolder = server.rootMsgFolder; + + // Now find Inbox. + return rootMsgFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox); + } catch (ex) { + dump(ex + "\n"); + } + return null; +} + +function GetMessagesForInboxOnServer(server) { + var inboxFolder = 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() { + // if offline, prompt for getting messages + if (DoGetNewMailWhenOffline()) + GetFolderMessages(); +} + +function MsgGetMessagesForAllServers(defaultServer) { + MailTasksGetMessagesForAllServers(true, msgWindow, defaultServer); +} + +/** + * Get messages for all those accounts which have the capability + * of getting messages and have session password available i.e., + * curretnly logged in accounts. + * if offline, prompt for getting messages. + */ +function MsgGetMessagesForAllAuthenticatedAccounts() { + if (DoGetNewMailWhenOffline()) + MailTasksGetMessagesForAllServers(false, msgWindow, null); +} + +/** + * 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 (DoGetNewMailWhenOffline()) + GetMessagesForInboxOnServer(aFolder.server); +} + +// if offline, prompt for getNextNMessages +function MsgGetNextNMessages() { + if (DoGetNewMailWhenOffline()) { + var folder = GetFirstSelectedMsgFolder(); + if (folder) + GetNextNMessages(folder); + } +} + +function MsgDeleteMessage(aReallyDelete) { + // If the user deletes a message before its mark as read timer goes off, + // we should mark it as read (unless the user changed the pref). This + // ensures that we clear the biff indicator from the system tray when + // the user deletes the new message. + if (Services.prefs.getBoolPref("mailnews.ui.deleteMarksRead")) + MarkSelectedMessagesRead(true); + SetNextMessageAfterDelete(); + + // determine if we're using the IMAP delete model + var server = GetFirstSelectedMsgFolder().server; + const kIMAPDelete = Ci.nsMsgImapDeleteModels.IMAPDelete; + var imapDeleteModelUsed = server instanceof Ci.nsIImapIncomingServer && + server.deleteModel == kIMAPDelete; + + // execute deleteNoTrash only if IMAP delete model is not used + if (aReallyDelete && !imapDeleteModelUsed) + gDBView.doCommand(nsMsgViewCommandType.deleteNoTrash); + else + gDBView.doCommand(nsMsgViewCommandType.deleteMsg); +} + +/** + * Copies the selected messages to the destination folder + * @param aDestFolder the destination folder + */ +function MsgCopyMessage(aDestFolder) { + if (gMessageDisplay.isDummy) { + let file = window.arguments[0].QueryInterface(Ci.nsIFileURL).file; + MailServices.copy.copyFileMessage(file, aDestFolder, null, false, + Ci.nsMsgMessageFlags.Read, + "", null, msgWindow); + } else { + gDBView.doCommandWithFolder(nsMsgViewCommandType.copyMessages, aDestFolder); + } +} + +/** + * Moves the selected messages to the destination folder + * @param aDestFolder the destination folder + */ +function MsgMoveMessage(aDestFolder) { + SetNextMessageAfterDelete(); + gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages, aDestFolder); +} + +/** + * Calls the ComposeMessage function with the desired type and proper default + * based on the event that fired it. + * + * @param aCompType The nsIMsgCompType to pass to the function. + * @param aEvent (optional) The event that triggered the call. + * @param aFormat (optional) Override the message format. + */ +function ComposeMsgByType(aCompType, aEvent, aFormat) { + var format = aFormat || ((aEvent && aEvent.shiftKey) ? msgComposeFormat.OppositeOfDefault : msgComposeFormat.Default); + + ComposeMessage(aCompType, + format, + GetFirstSelectedMsgFolder(), + gFolderDisplay ? gFolderDisplay.selectedMessageUris : null); +} + +function MsgNewMessage(aEvent) { + var mode = aEvent && aEvent.target.getAttribute("mode"); + ComposeMsgByType(msgComposeType.New, aEvent, mode && msgComposeFormat[mode]); +} + +function MsgReplyMessage(aEvent) { + if (gFolderDisplay.selectedMessageIsNews) + MsgReplyGroup(aEvent); + else if (!gFolderDisplay.selectedMessageIsFeed) + MsgReplySender(aEvent); +} + +function MsgReplyList(aEvent) { + ComposeMsgByType(msgComposeType.ReplyToList, aEvent); +} + +function MsgReplyGroup(aEvent) { + ComposeMsgByType(msgComposeType.ReplyToGroup, aEvent); +} + +function MsgReplySender(aEvent) { + ComposeMsgByType(msgComposeType.ReplyToSender, aEvent); +} + +function MsgReplyToAllMessage(aEvent) { + var loadedFolder = GetLoadedMsgFolder(); + var server = loadedFolder.server; + + if (server && server.type == "nntp") + MsgReplyToSenderAndGroup(aEvent); + else + MsgReplyToAllRecipients(aEvent); +} + +function MsgReplyToAllRecipients(aEvent) { + ComposeMsgByType(msgComposeType.ReplyAll, aEvent); +} + +function MsgReplyToSenderAndGroup(aEvent) { + ComposeMsgByType(msgComposeType.ReplyToSenderAndGroup, aEvent); +} + + +// Message Archive function + +function BatchMessageMover() { + this._batches = {}; + this._currentKey = null; + this._dstFolderParent = null; + this._dstFolderName = null; +} + +BatchMessageMover.prototype = +{ + archiveMessages(aMsgHdrs) { + if (!aMsgHdrs.length) + return; + + // We need to get the index of the message to select after archiving + // completes but reset the global variable to prevent the DBview from + // updating the selection; we'll do it manually at the end of + // processNextBatch. + SetNextMessageAfterDelete(); + this.messageToSelectAfterWereDone = gNextMessageViewIndexAfterDelete; + gNextMessageViewIndexAfterDelete = -2; + + for (let i = 0; i < aMsgHdrs.length; ++i) { + let msgHdr = aMsgHdrs[i]; + let server = msgHdr.folder.server; + let msgDate = new Date(msgHdr.date / 1000); // convert date to JS date object + let msgYear = msgDate.getFullYear().toString(); + let monthFolderName = msgYear + "-" + (msgDate.getMonth() + 1).toString().padStart(2, "0"); + + let archiveFolderUri; + let archiveGranularity; + let archiveKeepFolderStructure; + if (server.type == "rss") { + // RSS servers don't have an identity so we special case the archives URI. + archiveFolderUri = server.serverURI + "/Archives"; + archiveGranularity = + Services.prefs.getIntPref("mail.identity.default.archive_granularity"); + archiveKeepFolderStructure = + Services.prefs.getBoolPref("mail.identity.default.archive_keep_folder_structure"); + } else { + let identity = GetIdentityForHeader(msgHdr, + Ci.nsIMsgCompType.ReplyAll); + archiveFolderUri = identity.archiveFolder; + archiveGranularity = identity.archiveGranularity; + archiveKeepFolderStructure = identity.archiveKeepFolderStructure; + } + let archiveFolder = MailUtils.getFolderForURI(archiveFolderUri, false); + + let copyBatchKey = msgHdr.folder.URI + "\0" + monthFolderName; + if (!(copyBatchKey in this._batches)) + this._batches[copyBatchKey] = [msgHdr.folder, + archiveFolderUri, + archiveGranularity, + archiveKeepFolderStructure, + msgYear, + monthFolderName]; + this._batches[copyBatchKey].push(msgHdr); + } + + MailServices.mfn.addListener(this, MailServices.mfn.folderAdded); + + // Now we launch the code iterating over all message copies, one in turn. + this.processNextBatch(); + }, + + processNextBatch() { + for (let key in this._batches) { + this._currentBatch = this._batches[key]; + delete this._batches[key]; + return this.filterBatch(); + } + + // all done + MailServices.mfn.removeListener(this); + + // We're just going to select the message now. + let treeView = gDBView.QueryInterface(Ci.nsITreeView); + treeView.selection.select(this.messageToSelectAfterWereDone); + treeView.selectionChanged(); + + }, + + filterBatch() { + let batch = this._currentBatch; + // Apply filters to this batch. + let msgs = batch.slice(6); + let srcFolder = batch[0]; + MailServices.filters.applyFilters( + Ci.nsMsgFilterType.Archive, + msgs, srcFolder, msgWindow, this); + // continues with onStopOperation + }, + + onStopOperation(aResult) { + if (!Components.isSuccessCode(aResult)) { + Cu.reportError("Archive filter failed: " + aResult); + // We don't want to effectively disable archiving because a filter + // failed, so we'll continue after reporting the error. + } + // Now do the default archive processing + this.continueBatch(); + }, + + // continue processing of default archive operations + continueBatch() { + let batch = this._currentBatch; + let [srcFolder, archiveFolderUri, granularity, keepFolderStructure, msgYear, msgMonth] = batch; + let msgs = batch.slice(6); + + let moveArray = []; + // Don't move any items that the filter moves or deleted + for (let item of msgs) { + if (srcFolder.msgDatabase.ContainsKey(item.messageKey) && + !(srcFolder.getProcessingFlags(item.messageKey) & + Ci.nsMsgProcessingFlags.FilterToMove)) { + moveArray.push(item); + } + } + + if (moveArray.length == 0) + return this.processNextBatch(); // continue processing + + let archiveFolder = MailUtils.getFolderForURI(archiveFolderUri, false); + let dstFolder = archiveFolder; + // For folders on some servers (e.g. IMAP), we need to create the + // sub-folders asynchronously, so we chain the urls using the listener + // called back from createStorageIfMissing. For local, + // createStorageIfMissing is synchronous. + let isAsync = archiveFolder.server.protocolInfo.foldersCreatedAsync; + if (!archiveFolder.parent) { + archiveFolder.setFlag(Ci.nsMsgFolderFlags.Archive); + archiveFolder.createStorageIfMissing(this); + if (isAsync) + return; // continues with OnStopRunningUrl + } + if (!archiveFolder.canCreateSubfolders) + granularity = Ci.nsIMsgIdentity.singleArchiveFolder; + if (granularity >= Ci.nsIMsgIdentity.perYearArchiveFolders) { + archiveFolderUri += "/" + msgYear; + dstFolder = MailUtils.getFolderForURI(archiveFolderUri, false); + if (!dstFolder.parent) { + dstFolder.createStorageIfMissing(this); + if (isAsync) + return; // continues with OnStopRunningUrl + } + } + if (granularity >= Ci.nsIMsgIdentity.perMonthArchiveFolders) { + archiveFolderUri += "/" + msgMonth; + dstFolder = MailUtils.getFolderForURI(archiveFolderUri, false); + if (!dstFolder.parent) { + dstFolder.createStorageIfMissing(this); + if (isAsync) + return; // continues with OnStopRunningUrl + } + } + + // Create the folder structure in Archives. + // For imap folders, we need to create the sub-folders asynchronously, + // so we chain the actions using the listener called back from + // createSubfolder. For local, createSubfolder is synchronous. + if (archiveFolder.canCreateSubfolders && keepFolderStructure) { + // Collect in-order list of folders of source folder structure, + // excluding top-level INBOX folder + let folderNames = []; + let rootFolder = srcFolder.server.rootFolder; + let inboxFolder = GetInboxFolder(srcFolder.server); + let folder = srcFolder; + while (folder != rootFolder && folder != inboxFolder) { + folderNames.unshift(folder.name); + folder = folder.parent; + } + // Determine Archive folder structure. + for (let i = 0; i < folderNames.length; ++i) { + let folderName = folderNames[i]; + if (!dstFolder.containsChildNamed(folderName)) { + // Create Archive sub-folder (IMAP: async). + if (isAsync) { + this._dstFolderParent = dstFolder; + this._dstFolderName = folderName; + } + dstFolder.createSubfolder(folderName, msgWindow); + if (isAsync) + return; // continues with folderAdded + } + dstFolder = dstFolder.getChildNamed(folderName); + } + } + + if (dstFolder != srcFolder) { + // Make sure the target folder is visible in the folder tree. + EnsureFolderIndex(gFolderTreeView, dstFolder); + + let isNews = srcFolder.flags & Ci.nsMsgFolderFlags.Newsgroup; + + // If the source folder doesn't support deleting messages, we + // make archive a copy, not a move. + MailServices.copy.copyMessages(srcFolder, moveArray, dstFolder, + srcFolder.canDeleteMessages && !isNews, + this, msgWindow, true); + return; // continues with OnStopCopy + } + return this.processNextBatch(); + }, + + + // This also implements nsIUrlListener, but we only care about the + // OnStopRunningUrl (createStorageIfMissing callback). + OnStartRunningUrl(aUrl) { + }, + OnStopRunningUrl(aUrl, aExitCode) { + // This will always be a create folder url, afaik. + if (Components.isSuccessCode(aExitCode)) + this.continueBatch(); + else { + Cu.reportError("Archive failed to create folder: " + aExitCode); + this._batches = null; + this.processNextBatch(); // for cleanup and exit + } + }, + + // This also implements nsIMsgCopyServiceListener, but we only care + // about the OnStopCopy (copyMessages callback). + OnStartCopy() { + }, + OnProgress(aProgress, aProgressMax) { + }, + SetMessageKey(aKey) { + }, + GetMessageId() { + }, + OnStopCopy(aStatus) { + if (Components.isSuccessCode(aStatus)) { + return this.processNextBatch(); + } + + Cu.reportError("Archive failed to copy: " + aStatus); + this._batches = null; + this.processNextBatch(); // for cleanup and exit + + }, + + // This also implements nsIMsgFolderListener, but we only care about the + // folderAdded (createSubfolder callback). + folderAdded(aFolder) { + // Check that this is the folder we're interested in. + if (aFolder.parent == this._dstFolderParent && + aFolder.name == this._dstFolderName) { + this._dstFolderParent = null; + this._dstFolderName = null; + this.continueBatch(); + } + }, + + QueryInterface(aIID) { + if (aIID.equals(Ci.nsIUrlListener) || + aIID.equals(Ci.nsIMsgCopyServiceListener) || + aIID.equals(Ci.nsIMsgFolderListener) || + aIID.equals(Ci.nsIMsgOperationListener) || + aIID.equals(Ci.nsISupports)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + } +} + +function MsgArchiveSelectedMessages(aEvent) { + let batchMover = new BatchMessageMover(); + batchMover.archiveMessages(gFolderDisplay.selectedMessages); +} + + +function MsgForwardMessage(event) { + var forwardType = Services.prefs.getIntPref("mail.forward_message_mode"); + + // mail.forward_message_mode could be 1, if the user migrated from 4.x + // 1 (forward as quoted) is obsolete, so we treat is as forward inline + // since that is more like forward as quoted then forward as attachment + if (forwardType == kMsgForwardAsAttachment) + MsgForwardAsAttachment(event); + else + MsgForwardAsInline(event); +} + +function MsgForwardAsAttachment(event) { + ComposeMsgByType(msgComposeType.ForwardAsAttachment, event); +} + +function MsgForwardAsInline(event) { + ComposeMsgByType(msgComposeType.ForwardInline, event); +} + +function MsgEditMessageAsNew(aEvent) { + ComposeMsgByType(msgComposeType.EditAsNew, aEvent); +} + +function MsgEditDraftMessage(aEvent) { + ComposeMsgByType(msgComposeType.Draft, aEvent); +} + +function MsgNewMessageFromTemplate(aEvent) { + ComposeMsgByType(msgComposeType.Template, aEvent); +} + +function MsgEditTemplateMessage(aEvent) { + ComposeMsgByType(msgComposeType.EditTemplate, aEvent); +} + +function MsgComposeDraftMessage() { + ComposeMsgByType(msgComposeType.Draft, null, msgComposeFormat.Default); +} + +function MsgCreateFilter() { + // retrieve Sender direct from selected message's headers + var msgHdr = gFolderDisplay.selectedMessage; + var emailAddress = + MailServices.headerParser.extractHeaderAddressMailboxes(msgHdr.author); + var accountKey = msgHdr.accountKey; + var folder; + if (accountKey.length > 0) { + var account = accountManager.getAccount(accountKey); + if (account) { + server = account.incomingServer; + if (server) + folder = server.rootFolder; + } + } + if (!folder) + folder = GetFirstSelectedMsgFolder(); + + if (emailAddress) + top.MsgFilters(emailAddress, folder); +} + +function MsgSubscribe(folder) { + var preselectedFolder = folder || GetFirstSelectedMsgFolder(); + + if (preselectedFolder && preselectedFolder.server.type == "rss") + openSubscriptionsDialog(preselectedFolder); // open feed subscription dialog + else + Subscribe(preselectedFolder); // open imap/nntp subscription dialog +} + +/** + * 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 + * @return true if the user said it's ok to unsubscribe + */ +function ConfirmUnsubscribe(folders) { + if (!gMessengerBundle) + gMessengerBundle = document.getElementById("bundle_messenger"); + + let titleMsg = gMessengerBundle.getString("confirmUnsubscribeTitle"); + let dialogMsg = (folders.length == 1) ? + gMessengerBundle.getFormattedString("confirmUnsubscribeText", + [folders[0].name], 1) : + gMessengerBundle.getString("confirmUnsubscribeManyText"); + + return Services.prompt.confirm(window, titleMsg, dialogMsg); +} + +/** + * Unsubscribe from selected or passed in newsgroup/s. + * @param newsgroups (optional param) the newsgroup folders to unsubscribe from + */ +function MsgUnsubscribe(newsgroups) { + let folders = newsgroups || GetSelectedMsgFolders(); + if (!ConfirmUnsubscribe(folders)) + return; + + for (let folder of folders) { + let subscribableServer = + folder.server.QueryInterface(Ci.nsISubscribableServer); + subscribableServer.unsubscribe(folder.name); + subscribableServer.commitSubscribeChanges(); + } +} + +function ToggleFavoriteFolderFlag() { + var folder = GetFirstSelectedMsgFolder(); + folder.toggleFlag(Ci.nsMsgFolderFlags.Favorite); +} + +function MsgSaveAsFile() { + SaveAsFile(gFolderDisplay.selectedMessageUris); +} + +function MsgSaveAsTemplate() { + SaveAsTemplate(gFolderDisplay.selectedMessageUris); +} + +function MsgOpenFromFile() { + var fp = Cc["@mozilla.org/filepicker;1"] + .createInstance(Ci.nsIFilePicker); + + var filterLabel = gMessengerBundle.getString("EMLFiles"); + var windowTitle = gMessengerBundle.getString("OpenEMLFiles"); + + fp.init(window, windowTitle, Ci.nsIFilePicker.modeOpen); + fp.appendFilter(filterLabel, "*.eml; *.msg"); + + // Default or last filter is "All Files". + fp.appendFilters(Ci.nsIFilePicker.filterAll); + + fp.open(rv => { + if (rv != Ci.nsIFilePicker.returnOK || !fp.file) { + return; + } + let uri = fp.fileURL.QueryInterface(Ci.nsIURL); + uri.query = "type=application/x-message-display"; + + window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank", + "all,chrome,dialog=no,status,toolbar", uri); + }); +} + +function MsgOpenNewWindowForFolder(folderURI, msgKeyToSelect) { + let mailWindowService = Cc["@mozilla.org/messenger/windowservice;1"] + .getService(Ci.nsIMessengerWindowService); + if (!mailWindowService) + return; + + if (folderURI) { + mailWindowService.openMessengerWindowWithUri("mail:3pane", folderURI, + msgKeyToSelect); + return; + } + + // If there is a right-click happening, GetSelectedMsgFolders() + // will tell us about it (while the selection's currentIndex would reflect + // the node that was selected/displayed before the right-click.) + for (let folder of GetSelectedMsgFolders()) { + mailWindowService.openMessengerWindowWithUri("mail:3pane", folder.URI, + msgKeyToSelect); + } +} + +function MsgOpenSelectedMessages() { + // Toggle message body (feed summary) and content-base url in message pane or + // load in browser, per pref, otherwise open summary or web page in new window + // or tab, per that pref. + if (gFolderDisplay.selectedMessageIsFeed) { + let msgHdr = gFolderDisplay.selectedMessage; + if (document.documentElement.getAttribute("windowtype") == "mail:3pane" && + FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenToggleInMessagePane) { + let showSummary = FeedMessageHandler.shouldShowSummary(msgHdr, true); + FeedMessageHandler.setContent(msgHdr, showSummary); + FeedMessageHandler.onSelectPref = + showSummary ? FeedMessageHandler.kSelectOverrideSummary : + FeedMessageHandler.kSelectOverrideWebPage; + return; + } + if (FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenLoadInBrowser) { + setTimeout(FeedMessageHandler.loadWebPage, 20, msgHdr, {browser: true}); + return; + } + } + + var dbView = GetDBView(); + var indices = GetSelectedIndices(dbView); + var numMessages = indices.length; + + // This is a radio type button pref, currently with only 2 buttons. + // We need to keep the pref type as 'bool' for backwards compatibility + // with 4.x migrated prefs. For future radio button(s), please use another + // pref (either 'bool' or 'int' type) to describe it. + // + // mailnews.reuse_message_window values: + // false: open new standalone message window for each message + // true : reuse existing standalone message window for each message + if (Services.prefs.getBoolPref("mailnews.reuse_message_window") && + numMessages == 1 && + MsgOpenSelectedMessageInExistingWindow()) + return; + + var openWindowWarning = Services.prefs.getIntPref("mailnews.open_window_warning"); + if ((openWindowWarning > 1) && (numMessages >= openWindowWarning)) { + InitPrompts(); + if (!gMessengerBundle) + gMessengerBundle = document.getElementById("bundle_messenger"); + var title = gMessengerBundle.getString("openWindowWarningTitle"); + var text = PluralForm.get(numMessages, + gMessengerBundle.getString("openWindowWarningConfirmation")) + .replace("#1", numMessages); + if (!Services.prompt.confirm(window, title, text)) + return; + } + + for (var i = 0; i < numMessages; i++) { + MsgOpenNewWindowForMessage(dbView.getURIForViewIndex(indices[i]), dbView.getFolderForViewIndex(indices[i]).URI); + } +} + +function MsgOpenSelectedMessageInExistingWindow() { + var windowID = Services.wm.getMostRecentWindow("mail:messageWindow"); + if (!windowID) + return false; + + try { + var messageURI = gDBView.URIForFirstSelectedMessage; + var msgHdr = gDBView.hdrForFirstSelectedMessage; + + // Reset the window's message uri and folder uri vars, and + // update the command handlers to what's going to be used. + // This has to be done before the call to CreateView(). + windowID.gCurrentMessageUri = messageURI; + windowID.gCurrentFolderUri = msgHdr.folder.URI; + windowID.UpdateMailToolbar("MsgOpenExistingWindowForMessage"); + + // even if the folder uri's match, we can't use the existing view + // (msgHdr.folder.URI == windowID.gCurrentFolderUri) + // the reason is quick search and mail views. + // see bug #187673 + // + // for the sake of simplicity, + // let's always call CreateView(gDBView) + // which will clone gDBView + windowID.CreateView(gDBView); + windowID.OnLoadMessageWindowDelayed(false); + + // bring existing window to front + windowID.focus(); + return true; + } catch (ex) { + dump("reusing existing standalone message window failed: " + ex + "\n"); + } + return false; +} + +function MsgOpenSearch(aSearchStr, aEvent) { + // If you change /suite/navigator/navigator.js->BrowserSearch::loadSearch() + // make sure you make corresponding changes here. + var submission = Services.search.defaultEngine.getSubmission(aSearchStr); + if (!submission) + return; + + var newTabPref = Services.prefs.getBoolPref("browser.search.opentabforcontextsearch"); + var where = newTabPref ? aEvent && aEvent.shiftKey ? "tabshifted" : "tab" : "window"; + openUILinkIn(submission.uri.spec, where, null, submission.postData); +} + +function MsgOpenNewWindowForMessage(messageUri, folderUri) { + if (!messageUri) + messageUri = gFolderDisplay.selectedMessageUri; + + if (!folderUri) + // Use GetSelectedMsgFolders() to find out which message to open + // instead of gDBView.getURIForViewIndex(currentIndex). This is + // required because on a right-click, the currentIndex value will be + // different from the actual row that is highlighted. + // GetSelectedMsgFolders() will return the message that is + // highlighted. + folderUri = GetSelectedMsgFolders()[0].URI; + + // be sure to pass in the current view.... + if (messageUri && folderUri) { + window.openDialog( "chrome://messenger/content/messageWindow.xul", "_blank", "all,chrome,dialog=no,status,toolbar", messageUri, folderUri, gDBView ); + } +} + +function CloseMailWindow() { + window.close(); +} + +function MsgJunk() { + MsgJunkMailInfo(true); + JunkSelectedMessages(!SelectedMessagesAreJunk()); +} + +/** + * Checks if the selected messages can be marked as read or unread + * + * @param read true if trying to mark messages as read, false otherwise + * @return true if the chosen operation can be performed + */ +function CanMarkMsgAsRead(read) { + return SelectedMessagesAreRead() != read; +} + +/** + * Marks the selected messages as read or unread + * + * @param read true if trying to mark messages as read, false if marking unread, + * undefined if toggling the read status + */ +function MsgMarkMsgAsRead(read) { + if (read == undefined) + read = !SelectedMessagesAreRead(); + MarkSelectedMessagesRead(read); +} + +function MsgMarkAsFlagged() { + MarkSelectedMessagesFlagged(!SelectedMessagesAreFlagged()); +} + +function MsgMarkReadByDate() { + window.openDialog("chrome://messenger/content/markByDate.xul", "", + "chrome,modal,titlebar,centerscreen", + GetLoadedMsgFolder()); +} + +function MsgMarkAllRead() { + let folders = GetSelectedMsgFolders(); + for (let folder of folders) + folder.markAllMessagesRead(msgWindow); +} + +function MsgDownloadFlagged() { + gDBView.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline); +} + +function MsgDownloadSelected() { + gDBView.doCommand(nsMsgViewCommandType.downloadSelectedForOffline); +} + +function MsgMarkThreadAsRead() { + ClearPendingReadTimer(); + gDBView.doCommand(nsMsgViewCommandType.markThreadRead); +} + +function MsgViewPageSource() { + ViewPageSource(gFolderDisplay.selectedMessageUris); +} + +var gFindInstData; +function getFindInstData() { + if (!gFindInstData) { + gFindInstData = new nsFindInstData(); + gFindInstData.browser = getMessageBrowser(); + gFindInstData.rootSearchWindow = window.top.content; + gFindInstData.currentSearchWindow = window.top.content; + } + return gFindInstData; +} + +function MsgFind() { + findInPage(getFindInstData()); +} + +function MsgFindAgain(reverse) { + findAgainInPage(getFindInstData(), reverse); +} + +function MsgCanFindAgain() { + return canFindAgainInPage(); +} + +/** + * Go through each selected server and mark all its folders read. + */ +function MsgMarkAllFoldersRead() { + if (!Services.prompt.confirm(window, + gMessengerBundle.getString("confirmMarkAllFoldersReadTitle"), + gMessengerBundle.getString("confirmMarkAllFoldersReadMessage"))) { + return; + } + + const selectedFolders = GetSelectedMsgFolders(); + if (selectedFolders) { + const selectedServers = selectedFolders.filter(folder => folder.isServer); + + selectedServers.forEach(function(server) { + for (let folder of server.rootFolder.descendants) { + folder.markAllMessagesRead(msgWindow); + } + }); + } +} + +function MsgFilters(emailAddress, folder) { + if (!folder) + folder = GetFirstSelectedMsgFolder(); + var args; + if (emailAddress) { + // Prefill the filterEditor with the emailAddress. + args = {filterList: folder.getEditableFilterList(msgWindow), filterName: emailAddress}; + window.openDialog("chrome://messenger/content/FilterEditor.xul", "", + "chrome, modal, resizable,centerscreen,dialog", 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 MsgApplyFilters() { + var preselectedFolder = GetFirstSelectedMsgFolder(); + + var curFilterList = preselectedFolder.getFilterList(msgWindow); + // create a new filter list and copy over the enabled filters to it. + // We do this instead of having the filter after the fact code ignore + // disabled filters because the Filter Dialog filter after the fact + // code would have to clone filters to allow disabled filters to run, + // and we don't support cloning filters currently. + var tempFilterList = + MailServices.filters.getTempFilterList(preselectedFolder); + var numFilters = curFilterList.filterCount; + // make sure the temp filter list uses the same log stream + tempFilterList.loggingEnabled = curFilterList.loggingEnabled; + tempFilterList.logStream = curFilterList.logStream; + var newFilterIndex = 0; + for (var i = 0; i < numFilters; i++) { + var curFilter = curFilterList.getFilterAt(i); + // only add enabled, UI visibile filters that are in the manual context + if (curFilter.enabled && !curFilter.temporary && + (curFilter.filterType & Ci.nsMsgFilterType.Manual)) { + tempFilterList.insertFilterAt(newFilterIndex, curFilter); + newFilterIndex++; + } + } + MailServices.filters.applyFiltersToFolders(tempFilterList, + [preselectedFolder], + msgWindow); +} + +function MsgApplyFiltersToSelection() { + var folder = gDBView.msgFolder; + var indices = GetSelectedIndices(gDBView); + if (indices && indices.length) { + var selectedMsgs = []; + for (var i = 0; i < indices.length; i++) { + try { + // Getting the URI will tell us if the item is real or a dummy header + var uri = gDBView.getURIForViewIndex(indices[i]); + if (uri) { + var msgHdr = folder.GetMessageHeader(gDBView.getKeyAt(indices[i])); + if (msgHdr) + selectedMsgs.push(msgHdr); + } + } catch (ex) {} + } + + MailServices.filters.applyFilters(Ci.nsMsgFilterType.Manual, selectedMsgs, + folder, msgWindow); + } +} + +function ChangeMailLayout(newLayout) { + Services.prefs.setIntPref("mail.pane_config.dynamic", newLayout); +} + +function MsgViewAllHeaders() { + Services.prefs.setIntPref("mail.show_headers", + Ci.nsMimeHeaderDisplayTypes.AllHeaders); +} + +function MsgViewNormalHeaders() { + Services.prefs.setIntPref("mail.show_headers", + Ci.nsMimeHeaderDisplayTypes.NormalHeaders); +} + +function MsgBodyAllowHTML() { + ChangeMsgBodyDisplay(false, 0, 0); +} + +function MsgBodySanitized() { + ChangeMsgBodyDisplay(false, 3, gDisallow_classes_no_html); +} + +function MsgBodyAsPlaintext() { + ChangeMsgBodyDisplay(true, 1, gDisallow_classes_no_html); +} + +function MsgBodyAllParts() { + ChangeMsgBodyDisplay(false, 4, 0); +} + +function ChangeMsgBodyDisplay(plaintext, html, mime) { + Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", plaintext); + Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", mime); + Services.prefs.setIntPref("mailnews.display.html_as", html); +} + +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.disallow_mime_handlers", mime); + // Services.prefs.setIntPref("rss.display.html_as", html) + + Services.prefs.setBoolPref("mailnews.display.prefer_plaintext", plaintext); + Services.prefs.setIntPref("mailnews.display.disallow_mime_handlers", mime); + Services.prefs.setIntPref("mailnews.display.html_as", html); +} + +function ToggleInlineAttachment(target) { + var viewInline = !Services.prefs.getBoolPref("mail.inline_attachments"); + Services.prefs.setBoolPref("mail.inline_attachments", viewInline); + target.setAttribute("checked", viewInline ? "true" : "false"); +} + +function MsgStop() { + StopUrls(); +} + +function MsgSendUnsentMsgs() { + // if offline, prompt for sendUnsentMessages + if (!Services.io.offline) { + SendUnsentMessages(); + } else { + var option = PromptMessagesOffline("send"); + if (option == 0) { + if (!gOfflineManager) + GetOfflineMgrService(); + gOfflineManager.goOnline(false /* sendUnsentMessages */, + false /* playbackOfflineImapOperations */, + msgWindow); + SendUnsentMessages(); + } + } +} + +function PrintEnginePrintInternal(aDoPrintPreview, aMsgType) { + var messageList = gFolderDisplay.selectedMessageUris; + if (!messageList) { + dump("PrintEnginePrint(): No messages selected.\n"); + return false; + } + + window.openDialog("chrome://messenger/content/msgPrintEngine.xul", "", + "chrome,dialog=no,all,centerscreen", + messageList.length, messageList, statusFeedback, + aDoPrintPreview, aMsgType); + return true; + +} + +function PrintEnginePrint() { + return PrintEnginePrintInternal(false, Ci.nsIMsgPrintEngine.MNAB_PRINT_MSG); +} + +function PrintEnginePrintPreview() { + return PrintEnginePrintInternal(true, Ci.nsIMsgPrintEngine.MNAB_PRINTPREVIEW_MSG); +} + +// Kept for add-on compatibility. +function SelectFolder(folderUri) { + SelectMsgFolder(MailUtils.getFolderForURI(folderUri)); +} + +function IsMailFolderSelected() { + var selectedFolders = GetSelectedMsgFolders(); + var folder = selectedFolders.length ? selectedFolders[0] : null; + return folder && folder.server.type != "nntp"; +} + +function IsGetNewMessagesEnabled() { + // users don't like it when the "Get Msgs" button is disabled + // so let's never do that. + // we'll just handle it as best we can in GetFolderMessages() + // when they click "Get Msgs" and + // Local Folders or a news server is selected + // see bugs #89404 and #111102 + return true; +} + +function IsGetNextNMessagesEnabled() { + var selectedFolders = GetSelectedMsgFolders(); + var folder = selectedFolders.length ? selectedFolders[0] : null; + + var menuItem = document.getElementById("menu_getnextnmsg"); + if (folder && !folder.isServer && + folder.server instanceof Ci.nsINntpIncomingServer) { + var menuLabel = PluralForm.get(folder.server.maxArticles, + gMessengerBundle.getString("getNextNewsMessages")) + .replace("#1", folder.server.maxArticles); + menuItem.setAttribute("label", menuLabel); + menuItem.removeAttribute("hidden"); + return true; + } + + menuItem.setAttribute("hidden", "true"); + return false; +} + +function SetUpToolbarButtons(uri) { + let deleteButton = document.getElementById("button-delete"); + let replyAllButton = document.getElementById("button-replyall"); + + // Eventually, we might want to set up the toolbar differently for imap, + // pop, and news. For now, just tweak it based on if it is news or not. + let forNews = isNewsURI(uri); + + deleteButton.hidden = forNews; + if (forNews) { + replyAllButton.setAttribute("type", "menu-button"); + replyAllButton.setAttribute("tooltiptext", + replyAllButton.getAttribute("tooltiptextnews")); + } else { + replyAllButton.removeAttribute("type"); + replyAllButton.setAttribute("tooltiptext", + replyAllButton.getAttribute("tooltiptextmail")); + } +} + +function getMessageBrowser() { + return document.getElementById("messagepane"); +} + +// The zoom manager, view source and possibly some other functions still rely +// on the getBrowser function. +function getBrowser() { + return GetTabMail() ? GetTabMail().getBrowserForSelectedTab() : + getMessageBrowser(); +} + +function MsgSynchronizeOffline() { + window.openDialog("chrome://messenger/content/msgSynchronize.xul", "", + "centerscreen,chrome,modal,titlebar,resizable", + {msgWindow}); +} + +function MsgOpenAttachment() {} +function MsgUpdateMsgCount() {} +function MsgImport() {} +function MsgSynchronize() {} +function MsgGetSelectedMsg() {} +function MsgGetFlaggedMsg() {} +function MsgSelectThread() {} +function MsgShowFolders() {} +function MsgShowLocationbar() {} +function MsgViewAttachInline() {} +function MsgWrapLongLines() {} +function MsgIncreaseFont() {} +function MsgDecreaseFont() {} +function MsgShowImages() {} +function MsgRefresh() {} +function MsgViewPageInfo() {} +function MsgFirstUnreadMessage() {} +function MsgFirstFlaggedMessage() {} +function MsgAddSenderToAddressBook() {} +function MsgAddAllToAddressBook() {} + +function SpaceHit(event) { + var contentWindow = document.commandDispatcher.focusedWindow; + if (contentWindow.top == window) + contentWindow = content; + else if (document.commandDispatcher.focusedElement && + !hrefAndLinkNodeForClickEvent(event)) + return; + var rssiframe = content.document.getElementById("_mailrssiframe"); + + // If we are displaying an RSS article, we really want to scroll + // the nested iframe. + if (contentWindow == content && rssiframe) + contentWindow = rssiframe.contentWindow; + + if (event && event.shiftKey) { + // if at the start of the message, go to the previous one + if (contentWindow.scrollY > 0) + contentWindow.scrollByPages(-1); + else if (Services.prefs.getBoolPref("mail.advance_on_spacebar")) + goDoCommand("cmd_previousUnreadMsg"); + } else { + // if at the end of the message, go to the next one + if (contentWindow.scrollY < contentWindow.scrollMaxY) + contentWindow.scrollByPages(1); + else if (Services.prefs.getBoolPref("mail.advance_on_spacebar")) + goDoCommand("cmd_nextUnreadMsg"); + } +} + +function IsAccountOfflineEnabled() { + var selectedFolders = GetSelectedMsgFolders(); + + if (selectedFolders && (selectedFolders.length == 1)) + return selectedFolders[0].supportsOffline; + + return false; +} + +function DoGetNewMailWhenOffline() { + if (!Services.io.offline) + return true; + + if (PromptMessagesOffline("get") == 0) { + var sendUnsent = false; + if (this.CheckForUnsentMessages != undefined && CheckForUnsentMessages()) { + sendUnsent = + Services.prefs.getIntPref("offline.send.unsent_messages") == 1 || + Services.prompt.confirmEx( + window, + gOfflinePromptsBundle.getString("sendMessagesOfflineWindowTitle"), + gOfflinePromptsBundle.getString("sendMessagesLabel2"), + Services.prompt.BUTTON_TITLE_IS_STRING * + (Services.prompt.BUTTON_POS_0 + Services.prompt.BUTTON_POS_1), + gOfflinePromptsBundle.getString("sendMessagesSendButtonLabel"), + gOfflinePromptsBundle.getString("sendMessagesNoSendButtonLabel"), + null, null, {value: false}) == 0; + } + if (!gOfflineManager) + GetOfflineMgrService(); + gOfflineManager.goOnline(sendUnsent /* sendUnsentMessages */, + false /* playbackOfflineImapOperations */, + msgWindow); + return true; + } + return false; +} + +// prompt for getting/sending messages when offline +function PromptMessagesOffline(aPrefix) { + InitPrompts(); + var checkValue = {value: false}; + return Services.prompt.confirmEx( + window, + gOfflinePromptsBundle.getString(aPrefix + "MessagesOfflineWindowTitle"), + gOfflinePromptsBundle.getString(aPrefix + "MessagesOfflineLabel"), + (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) + + (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1), + gOfflinePromptsBundle.getString(aPrefix + "MessagesOfflineGoButtonLabel"), + null, null, null, checkValue); +} + +function GetDefaultAccountRootFolder() { + var account = accountManager.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() { + var selectedFolders = GetSelectedMsgFolders(); + var defaultAccountRootFolder = GetDefaultAccountRootFolder(); + + var folders = (selectedFolders.length) ? selectedFolders + : [defaultAccountRootFolder]; + + if (!folders[0]) { + return; + } + + for (let folder of folders) { + var serverType = folder.server.type; + if (folder.isServer && (serverType == "nntp")) { + // If we're doing "get msgs" on a news server, + // update unread counts on this server. + folder.server.performExpand(msgWindow); + } 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 (!folder.server.isDeferredTo) { + if (!defaultAccountRootFolder) { + continue; + } + GetNewMsgs(defaultAccountRootFolder.server, defaultAccountRootFolder); + } else { + GetNewMsgs(folder.server, folder); + } + } else { + GetNewMsgs(folder.server, folder); + } + } +} + +/** + * 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, null); +} + +function SendUnsentMessages() { + let msgSendlater = Cc["@mozilla.org/messengercompose/sendlater;1"] + .getService(Ci.nsIMsgSendLater); + + let allIdentities = MailServices.accounts.allIdentities; + for (let currentIdentity of allIdentities) { + let msgFolder = msgSendlater.getUnsentMessagesFolder(currentIdentity); + if (msgFolder) { + let numMessages = msgFolder.getTotalMessages(false /* include subfolders */); + if (numMessages > 0) { + msgSendlater.statusFeedback = statusFeedback; + msgSendlater.sendUnsentMessages(currentIdentity); + // 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 CommandUpdate_UndoRedo() { + EnableMenuItem("menu_undo", SetupUndoRedoCommand("cmd_undo")); + EnableMenuItem("menu_redo", SetupUndoRedoCommand("cmd_redo")); +} + +function SetupUndoRedoCommand(command) { + // If we have selected a server, and are viewing account central + // there is no loaded folder. + var loadedFolder = GetLoadedMsgFolder(); + if (!loadedFolder || !loadedFolder.server.canUndoDeleteOnServer) + return false; + + var canUndoOrRedo = false; + var txnType = 0; + + if (command == "cmd_undo") { + canUndoOrRedo = messenger.canUndo(); + txnType = messenger.getUndoTransactionType(); + } else { + canUndoOrRedo = messenger.canRedo(); + txnType = messenger.getRedoTransactionType(); + } + + if (canUndoOrRedo) { + switch (txnType) { + default: + case Ci.nsIMessenger.eUnknown: + goSetMenuValue(command, "valueDefault"); + break; + case Ci.nsIMessenger.eDeleteMsg: + goSetMenuValue(command, "valueDeleteMsg"); + break; + case Ci.nsIMessenger.eMoveMsg: + goSetMenuValue(command, "valueMoveMsg"); + break; + case Ci.nsIMessenger.eCopyMsg: + goSetMenuValue(command, "valueCopyMsg"); + break; + case Ci.nsIMessenger.eMarkAllMsg: + goSetMenuValue(command, "valueUnmarkAllMsgs"); + break; + } + } else { + goSetMenuValue(command, "valueDefault"); + } + return canUndoOrRedo; +} + +function HandleJunkStatusChanged(folder) { + // This might be the stand alone window, open to a message that was + // and attachment (or on disk), in which case, we want to ignore it. + var loadedMessage = GetLoadedMessage(); + if (!loadedMessage || + /type=application\/x-message-display/.test(loadedMessage) || + !IsCurrentLoadedFolder(folder)) + return; + + // If multiple message are selected and we change the junk status + // we don't want to show the junk bar (since the message pane is blank). + var msgHdr = null; + if (GetNumSelectedMessages() == 1) + msgHdr = messenger.msgHdrFromURI(loadedMessage); + + var junkBarWasDisplayed = gMessageNotificationBar.isShowingJunkNotification(); + gMessageNotificationBar.setJunkMsg(msgHdr); + + // Only reload message if junk bar display state has changed. + if (msgHdr && junkBarWasDisplayed != gMessageNotificationBar.isShowingJunkNotification()) { + // We may be forcing junk mail to be rendered with sanitized html. + // In that scenario, we want to reload the message if the status has just + // changed to not junk. + var sanitizeJunkMail = Services.prefs.getBoolPref("mail.spam.display.sanitize"); + + // Only bother doing this if we are modifying the html for junk mail... + if (sanitizeJunkMail) { + let junkScore = msgHdr.getStringProperty("junkscore"); + let isJunk = (junkScore == Ci.nsIJunkMailPlugin.IS_SPAM_SCORE); + + // If the current row isn't going to change, reload to show sanitized or + // unsanitized. Otherwise we wouldn't see the reloaded version anyway. + + // XXX: need to special handle last message in view, for imap mark as deleted + + // 1) When marking as non-junk, the msg would move back to the inbox. + // 2) When marking as junk, the msg will move or delete, if manualMark is set. + // 3) Marking as junk in the junk folder just changes the junk status. + if ((!isJunk && folder.isSpecialFolder(Ci.nsMsgFolderFlags.Inbox)) || + (isJunk && !folder.server.spamSettings.manualMark) || + (isJunk && folder.isSpecialFolder(Ci.nsMsgFolderFlags.Junk))) + ReloadMessage(); + } + } +} + +var gMessageNotificationBar = +{ + get mStringBundle() { + delete this.mStringBundle; + + return this.mStringBundle = document.getElementById("bundle_messenger"); + }, + + get mBrandBundle() { + delete this.mBrandBundle; + + return this.mBrandBundle = document.getElementById("bundle_brand"); + }, + + get mMsgNotificationBar() { + delete this.mMsgNotificationBar; + + return this.mMsgNotificationBar = document.getElementById("messagepanebox"); + }, + + setJunkMsg(aMsgHdr) { + let isJunk = false; + if (aMsgHdr) { + let junkScore = aMsgHdr.getStringProperty("junkscore"); + isJunk = ((junkScore != "") && (junkScore != "0")); + } + + goUpdateCommand("button_junk"); + + if (isJunk) { + if (!this.isShowingJunkNotification()) { + let brandName = this.mBrandBundle.getString("brandShortName"); + let junkBarMsg = this.mStringBundle.getFormattedString("junkBarMessage", + [brandName]); + + let buttons = [{ + label: this.mStringBundle.getString("junkBarInfoButton"), + accessKey: this.mStringBundle.getString("junkBarInfoButtonKey"), + popup: null, + callback() { + MsgJunkMailInfo(false); + return true; + } + }, + { + label: this.mStringBundle.getString("junkBarButton"), + accessKey: this.mStringBundle.getString("junkBarButtonKey"), + popup: null, + callback() { + JunkSelectedMessages(false); + return true; + } + }]; + this.mMsgNotificationBar.appendNotification(junkBarMsg, "junkContent", + null, this.mMsgNotificationBar.PRIORITY_WARNING_HIGH, buttons); + this.mMsgNotificationBar.collapsed = false; + } + } + }, + + remoteOrigins: null, + + isShowingJunkNotification() { + return !!this.mMsgNotificationBar.getNotificationWithValue("junkContent"); + }, + + setRemoteContentMsg(aMsgHdr, aContentURI, aCanOverride) { + // remoteOrigins is a Set of all blockable Origins. + if (!this.remoteOrigins) + this.remoteOrigins = new Set(); + + var origin = aContentURI.spec; + try { + origin = aContentURI.scheme + "://" + aContentURI.hostPort; + } + // No hostport so likely a special url. Try to use the whole url and see + // what comes of it. + catch (e) { } + + this.remoteOrigins.add(origin); + + if (this.mMsgNotificationBar.getNotificationWithValue("remoteContent")) + return; + + var headerParser = MailServices.headerParser; + // update the allow remote content for sender string + var mailbox = headerParser.extractHeaderAddressMailboxes(aMsgHdr.author); + var emailAddress = mailbox || aMsgHdr.author; + var displayName = headerParser.extractFirstName(aMsgHdr.mime2DecodedAuthor); + var brandName = this.mBrandBundle.getString("brandShortName"); + var remoteContentMsg = this.mStringBundle + .getFormattedString("remoteContentBarMessage", + [brandName]); + var buttons = [{ + label: this.mStringBundle.getString("remoteContentPrefLabel"), + accessKey: this.mStringBundle.getString("remoteContentPrefAccesskey"), + popup: "remoteContentOptions" + }]; + + this.mMsgNotificationBar + .appendNotification(remoteContentMsg, + "remoteContent", + null, + this.mMsgNotificationBar.PRIORITY_WARNING_MEDIUM, + (aCanOverride ? buttons : [])); + }, + + // aUrl is the nsIURI for the message currently loaded in the message pane + setPhishingMsg(aUrl) { + // if we've explicitly marked this message as not being an email scam, then don't + // bother checking it with the phishing detector. + var phishingMsg = false; + + if (!checkMsgHdrPropertyIsNot("notAPhishMessage", kIsAPhishMessage)) + phishingMsg = isMsgEmailScam(aUrl); + + var oldNotif = this.mMsgNotificationBar.getNotificationWithValue("phishingContent"); + if (phishingMsg) { + if (!oldNotif) { + let brandName = this.mBrandBundle.getString("brandShortName"); + let phishingMsgNote = this.mStringBundle.getFormattedString("phishingBarMessage", + [brandName]); + + let buttons = [{ + label: this.mStringBundle.getString("phishingBarIgnoreButton"), + accessKey: this.mStringBundle.getString("phishingBarIgnoreButtonKey"), + popup: null, + callback() { + MsgIsNotAScam(); + } + }]; + + this.mMsgNotificationBar.appendNotification(phishingMsgNote, "phishingContent", + null, this.mMsgNotificationBar.PRIORITY_CRITICAL_MEDIUM, buttons); + } + } + }, + + setMDNMsg(aMdnGenerator, aMsgHeader, aMimeHdr) { + this.mdnGenerator = aMdnGenerator; + // Return receipts can be RFC 3798 "Disposition-Notification-To", + // or non-standard "Return-Receipt-To". + var mdnHdr = aMimeHdr.extractHeader("Disposition-Notification-To", false) || + aMimeHdr.extractHeader("Return-Receipt-To", false); // not + var fromHdr = aMimeHdr.extractHeader("From", false); + + var mdnAddr = MailServices.headerParser + .extractHeaderAddressMailboxes(mdnHdr); + var fromAddr = MailServices.headerParser + .extractHeaderAddressMailboxes(fromHdr); + + var authorName = MailServices.headerParser + .extractFirstName(aMsgHeader.mime2DecodedAuthor) + || aMsgHeader.author; + + var barMsg; + // If the return receipt doesn't go to the sender address, note that in the + // notification. + if (mdnAddr != fromAddr) + barMsg = this.mStringBundle.getFormattedString("mdnBarMessageAddressDiffers", + [authorName, mdnAddr]); + else + barMsg = this.mStringBundle.getFormattedString("mdnBarMessageNormal", [authorName]); + + var oldNotif = this.mMsgNotificationBar.getNotificationWithValue("mdnContent"); + if (!oldNotif) { + let buttons = [{ + label: this.mStringBundle.getString("mdnBarSendReqButton"), + accessKey: this.mStringBundle.getString("mdnBarSendReqButtonKey"), + popup: null, + callback: SendMDNResponse + }, + { + label: this.mStringBundle.getString("mdnBarIgnoreButton"), + accessKey: this.mStringBundle.getString("mdnBarIgnoreButtonKey"), + popup: null, + callback: IgnoreMDNResponse + }]; + + this.mMsgNotificationBar.appendNotification(barMsg, "mdnContent", + null, this.mMsgNotificationBar.PRIORITY_INFO_MEDIUM, buttons); + } + }, + + clearMsgNotifications() { + } +}; + +/** + * LoadMsgWithRemoteContent + * Reload the current message, allowing remote content + */ +function LoadMsgWithRemoteContent() { + // we want to get the msg hdr for the currently selected message + // change the "remoteContentBar" property on it + // then reload the message + + setMsgHdrPropertyAndReload("remoteContentPolicy", kAllowRemoteContent); + window.content.focus(); +} + +/** + * Populate the remote content options for the current message. + */ +function onRemoteContentOptionsShowing(aEvent) { + var origins = [...gMessageNotificationBar.remoteOrigins]; + + var addresses = {}; + MailServices.headerParser.parseHeadersWithArray( + gMessageDisplay.displayedMessage.author, addresses, {}, {}); + var authorEmailAddress = addresses.value[0]; + + var emailURI = Services.io.newURI( + "chrome://messenger/content/email=" + authorEmailAddress); + var principal = Services.scriptSecurityManager + .createCodebasePrincipal(emailURI, {}); + // Put author email first in the menu. + origins.unshift(principal.origin); + + // Out with the old... + let childNodes = aEvent.target.querySelectorAll(".allow-remote-uri"); + for (let child of childNodes) + child.remove(); + + var messengerBundle = gMessageNotificationBar.mStringBundle; + var separator = document.getElementById("remoteContentSettingsMenuSeparator") + + // ... and in with the new. + for (let origin of origins) { + let menuitem = document.createElement("menuitem"); + let host = origin.replace("chrome://messenger/content/email=", ""); + let hostString = messengerBundle.getFormattedString("remoteContentAllow", [host]); + menuitem.setAttribute("label", hostString); + menuitem.setAttribute("value", origin); + menuitem.setAttribute("class", "allow-remote-uri"); + aEvent.target.insertBefore(menuitem, separator); + } +} + +/** + * Add privileges to display remote content for the given uri. + * @param aItem |Node| Item that was selected. The origin + * is extracted and converted to a uri and used to add + * permissions for the site. + */ +function allowRemoteContentForURI(aItem) { + + var origin = aItem.getAttribute("value"); + + if (!origin) + return; + + let uri = Services.io.newURI(origin); + Services.perms.add(uri, "image", Services.perms.ALLOW_ACTION); + + ReloadMessage(); +} + +/** + * Displays fine-grained, per-site permissions for remote content. + */ +function editRemoteContentSettings() { + toDataManager("|permissions"); + if (!Services.prefs.getBoolPref("browser.preferences.instantApply")) + ReloadMessage(); +} + +/** + * msgHdrForCurrentMessage + * Returns the msg hdr associated with the current loaded message. + */ +function msgHdrForCurrentMessage() { + var msgURI = GetLoadedMessage(); + return (msgURI && !(/type=application\/x-message-display/.test(msgURI))) ? messenger.msgHdrFromURI(msgURI) : null; +} + +function MsgIsNotAScam() { + // we want to get the msg hdr for the currently selected message + // change the "isPhishingMsg" property on it + // then reload the message + + setMsgHdrPropertyAndReload("notAPhishMessage", kNotAPhishMessage); +} + +function setMsgHdrPropertyAndReload(aProperty, aValue) { + // we want to get the msg hdr for the currently selected message + // change the appropiate property on it then reload the message + + var msgHdr = msgHdrForCurrentMessage(); + if (msgHdr) { + msgHdr.setUint32Property(aProperty, aValue); + ReloadMessage(); + } +} + +function checkMsgHdrPropertyIsNot(aProperty, aValue) { + // we want to get the msg hdr for the currently selected message, + // get the appropiate property on it and then test against value. + + var msgHdr = msgHdrForCurrentMessage(); + return (msgHdr && msgHdr.getUint32Property(aProperty) != aValue); +} + +/** + * Mark a specified message as read. + * @param msgHdr header (nsIMsgDBHdr) of the message to mark as read + */ +function MarkMessageAsRead(msgHdr) { + ClearPendingReadTimer(); + msgHdr.folder.markMessagesRead([msgHdr], true); +} + +function ClearPendingReadTimer() { + if (gMarkViewedMessageAsReadTimer) { + clearTimeout(gMarkViewedMessageAsReadTimer); + gMarkViewedMessageAsReadTimer = null; + } +} + +function OnMsgParsed(aUrl) { + gMessageNotificationBar.setPhishingMsg(aUrl); + + // notify anyone (e.g., extensions) who's interested in when a message is loaded. + var msgURI = GetLoadedMessage(); + Services.obs.notifyObservers(msgWindow.msgHeaderSink, + "MsgMsgDisplayed", msgURI); + + // scale any overflowing images + var doc = getMessageBrowser().contentDocument; + var imgs = doc.getElementsByTagName("img"); + for (var img of imgs) { + if (img.className == "moz-attached-image" && + img.naturalWidth > doc.body.clientWidth) { + if (img.hasAttribute("shrinktofit")) + img.setAttribute("isshrunk", "true"); + else + img.setAttribute("overflowing", "true"); + } + } +} + +function OnMsgLoaded(aUrl) { + if (!aUrl) + return; + + // nsIMsgMailNewsUrl.folder throws an error when opening .eml files. + var folder; + try { + folder = aUrl.folder; + } catch (ex) {} + + var msgURI = GetLoadedMessage(); + + if (!folder || !msgURI) + return; + + // If we are in the middle of a delete or move operation, make sure that + // if the user clicks on another message then that message stays selected + // and the selection does not "snap back" to the message chosen by + // SetNextMessageAfterDelete() when the operation completes (bug 243532). + var wintype = document.documentElement.getAttribute("windowtype"); + gNextMessageViewIndexAfterDelete = -2; + + var msgHdr = msgHdrForCurrentMessage(); + gMessageNotificationBar.setJunkMsg(msgHdr); + // Reset the blocked origins so we can populate it again for this message. + // Reset to null so it's only a Set if there's something in the Set. + gMessageNotificationBar.remoteOrigins = null; + + var markReadAutoMode = Services.prefs.getBoolPref("mailnews.mark_message_read.auto"); + + // We just finished loading a message. If messages are to be marked as read + // automatically, set a timer to mark the message is read after n seconds + // where n can be configured by the user. + if (msgHdr && !msgHdr.isRead && markReadAutoMode) { + let markReadOnADelay = Services.prefs.getBoolPref("mailnews.mark_message_read.delay"); + // Only use the timer if viewing using the 3-pane preview pane and the + // user has set the pref. + if (markReadOnADelay && wintype == "mail:3pane") // 3-pane window + { + ClearPendingReadTimer(); + let markReadDelayTime = Services.prefs.getIntPref("mailnews.mark_message_read.delay.interval"); + if (markReadDelayTime == 0) + MarkMessageAsRead(msgHdr); + else + gMarkViewedMessageAsReadTimer = setTimeout(MarkMessageAsRead, + markReadDelayTime * 1000, + msgHdr); + } else // standalone msg window + { + MarkMessageAsRead(msgHdr); + } + } + + // See if MDN was requested but has not been sent. + HandleMDNResponse(aUrl); +} + +/* + * This function handles all mdn response generation (ie, imap and pop). + * For pop the msg uid can be 0 (ie, 1st msg in a local folder) so no + * need to check uid here. No one seems to set mimeHeaders to null so + * no need to check it either. + */ +function HandleMDNResponse(aUrl) { + if (!aUrl) + return; + + var msgFolder = aUrl.folder; + var msgHdr = gFolderDisplay.selectedMessage; + if (!msgFolder || !msgHdr || gFolderDisplay.selectedMessageIsNews) + return; + + // if the message is marked as junk, do NOT attempt to process a return receipt + // in order to better protect the user + if (SelectedMessagesAreJunk()) + return; + + var mimeHdr; + + try { + mimeHdr = aUrl.mimeHeaders; + } catch (ex) { + return; + } + + // If we didn't get the message id when we downloaded the message header, + // we cons up an md5: message id. If we've done that, we'll try to extract + // the message id out of the mime headers for the whole message. + var msgId = msgHdr.messageId; + if (msgId.split(":")[0] == "md5") { + var mimeMsgId = mimeHdr.extractHeader("Message-Id", false); + if (mimeMsgId) + msgHdr.messageId = mimeMsgId; + } + + // After a msg is downloaded it's already marked READ at this point so we must check if + // the msg has a "Disposition-Notification-To" header and no MDN report has been sent yet. + if (msgHdr.flags & Ci.nsMsgMessageFlags.MDNReportSent) + return; + + var DNTHeader = mimeHdr.extractHeader("Disposition-Notification-To", false); + var oldDNTHeader = mimeHdr.extractHeader("Return-Receipt-To", false); + if (!DNTHeader && !oldDNTHeader) + return; + + // Everything looks good so far, let's generate the MDN response. + var mdnGenerator = Cc["@mozilla.org/messenger-mdn/generator;1"] + .createInstance(Ci.nsIMsgMdnGenerator); + var askUser = mdnGenerator.process(Ci.nsIMsgMdnGenerator.eDisplayed, + msgWindow, + msgFolder, + msgHdr.messageKey, + mimeHdr, + false); + if (askUser) + gMessageNotificationBar.setMDNMsg(mdnGenerator, msgHdr, mimeHdr); +} + +function SendMDNResponse() { + gMessageNotificationBar.mdnGenerator.userAgreed(); +} + +function IgnoreMDNResponse() { + gMessageNotificationBar.mdnGenerator.userDeclined(); +} + +/** + * Opens a search window with the given folder, or the displayed one if none is + * chosen. + * + * @param [aFolder] the folder to open the search window for, if different from + * the displayed one + */ +function MsgSearchMessages(aFolder) { + let folder = aFolder || gFolderDisplay.displayedFolder; + OpenOrFocusWindow({ folder }, "mailnews:search", + "chrome://messenger/content/SearchDialog.xul"); +} + +function MsgJunkMailInfo(aCheckFirstUse) { + if (aCheckFirstUse) { + if (!Services.prefs.getBoolPref("mailnews.ui.junk.firstuse")) + return; + Services.prefs.setBoolPref("mailnews.ui.junk.firstuse", false); + + // Check to see if this is an existing profile where the user has started + // using the junk mail feature already. + if (MailServices.junk.userHasClassified) + return; + } + + var desiredWindow = Services.wm.getMostRecentWindow("mailnews:junkmailinfo"); + + if (desiredWindow) + desiredWindow.focus(); + else + window.openDialog("chrome://messenger/content/junkMailInfo.xul", "mailnews:junkmailinfo", "centerscreen,resizeable=no,titlebar,chrome,modal", null); +} + +function MsgSearchAddresses() { + var args = { directory: null }; + OpenOrFocusWindow(args, "mailnews:absearch", "chrome://messenger/content/ABSearchDialog.xul"); +} + +function MsgFilterList(args) { + OpenOrFocusWindow(args, "mailnews:filterlist", "chrome://messenger/content/FilterListDialog.xul"); +} + +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 getMailToolbox() { + return document.getElementById("mail-toolbox"); +} + +function MailToolboxCustomizeInit() { + toolboxCustomizeInit("mail-menubar"); +} + +function MailToolboxCustomizeDone(aToolboxChanged) { + toolboxCustomizeDone("mail-menubar", getMailToolbox(), aToolboxChanged); + + // Make sure the folder location picker is initialized. + let folderContainer = document.getElementById("folder-location-container"); + if (folderContainer && + folderContainer.parentNode.localName != "toolbarpalette") { + FolderPaneSelectionChange(); + } +} + +function MailToolboxCustomizeChange(event) { + toolboxCustomizeChange(getMailToolbox(), event); +} |