summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/content
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/mailnews/content')
-rw-r--r--comm/suite/mailnews/content/ABSearchDialog.js327
-rw-r--r--comm/suite/mailnews/content/ABSearchDialog.xul99
-rw-r--r--comm/suite/mailnews/content/FilterListDialog.js1037
-rw-r--r--comm/suite/mailnews/content/FilterListDialog.xul197
-rw-r--r--comm/suite/mailnews/content/SearchDialog.js729
-rw-r--r--comm/suite/mailnews/content/SearchDialog.xul178
-rw-r--r--comm/suite/mailnews/content/browserRequest.js107
-rw-r--r--comm/suite/mailnews/content/browserRequest.xul34
-rw-r--r--comm/suite/mailnews/content/commandglue.js989
-rw-r--r--comm/suite/mailnews/content/folderDisplay.js142
-rw-r--r--comm/suite/mailnews/content/folderPane.js2221
-rw-r--r--comm/suite/mailnews/content/folderPane.xul169
-rw-r--r--comm/suite/mailnews/content/mail-offline.js164
-rw-r--r--comm/suite/mailnews/content/mail3PaneWindowCommands.js1057
-rw-r--r--comm/suite/mailnews/content/mailCommands.js415
-rw-r--r--comm/suite/mailnews/content/mailContextMenus.js828
-rw-r--r--comm/suite/mailnews/content/mailEditorOverlay.xul61
-rw-r--r--comm/suite/mailnews/content/mailKeysOverlay.xul64
-rw-r--r--comm/suite/mailnews/content/mailOverlay.js30
-rw-r--r--comm/suite/mailnews/content/mailOverlay.xul29
-rw-r--r--comm/suite/mailnews/content/mailTasksOverlay.js250
-rw-r--r--comm/suite/mailnews/content/mailTasksOverlay.xul64
-rw-r--r--comm/suite/mailnews/content/mailViewList.js161
-rw-r--r--comm/suite/mailnews/content/mailViewList.xul79
-rw-r--r--comm/suite/mailnews/content/mailViewSetup.js119
-rw-r--r--comm/suite/mailnews/content/mailViewSetup.xul51
-rw-r--r--comm/suite/mailnews/content/mailWidgets.xml1946
-rw-r--r--comm/suite/mailnews/content/mailWindow.js593
-rw-r--r--comm/suite/mailnews/content/mailWindowOverlay.js2695
-rw-r--r--comm/suite/mailnews/content/mailWindowOverlay.xul1929
-rw-r--r--comm/suite/mailnews/content/messageWindow.js1044
-rw-r--r--comm/suite/mailnews/content/messageWindow.xul141
-rw-r--r--comm/suite/mailnews/content/messenger.css236
-rw-r--r--comm/suite/mailnews/content/messenger.xul275
-rw-r--r--comm/suite/mailnews/content/msgFolderPickerOverlay.js100
-rw-r--r--comm/suite/mailnews/content/msgHdrViewOverlay.js1971
-rw-r--r--comm/suite/mailnews/content/msgHdrViewOverlay.xul273
-rw-r--r--comm/suite/mailnews/content/msgMail3PaneWindow.js1265
-rw-r--r--comm/suite/mailnews/content/msgViewNavigation.js243
-rw-r--r--comm/suite/mailnews/content/msgViewPickerOverlay.js413
-rw-r--r--comm/suite/mailnews/content/nsDragAndDrop.js595
-rw-r--r--comm/suite/mailnews/content/phishingDetector.js173
-rw-r--r--comm/suite/mailnews/content/searchBar.js432
-rw-r--r--comm/suite/mailnews/content/searchTermOverlay.xul64
-rw-r--r--comm/suite/mailnews/content/start.xhtml69
-rw-r--r--comm/suite/mailnews/content/tabmail.js969
-rw-r--r--comm/suite/mailnews/content/tabmail.xml1583
-rw-r--r--comm/suite/mailnews/content/threadPane.js598
-rw-r--r--comm/suite/mailnews/content/threadPane.xul91
49 files changed, 27299 insertions, 0 deletions
diff --git a/comm/suite/mailnews/content/ABSearchDialog.js b/comm/suite/mailnews/content/ABSearchDialog.js
new file mode 100644
index 0000000000..15d85b6234
--- /dev/null
+++ b/comm/suite/mailnews/content/ABSearchDialog.js
@@ -0,0 +1,327 @@
+/* -*- 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/. */
+
+const {encodeABTermValue} = ChromeUtils.import("resource:///modules/ABQueryUtils.jsm");
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+
+var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
+var gSearchSession;
+
+var nsMsgSearchScope = Ci.nsMsgSearchScope;
+var nsMsgSearchOp = Ci.nsMsgSearchOp;
+var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+var nsIAbDirectory = Ci.nsIAbDirectory;
+
+var gStatusText;
+var gSearchBundle;
+var gAddressBookBundle;
+
+var gSearchStopButton;
+var gPropertiesButton;
+var gComposeButton;
+var gSearchPhoneticName = "false";
+
+var gSearchAbViewListener = {
+ onSelectionChanged: function() {
+ UpdateCardView();
+ },
+ onCountChanged: function(aTotal) {
+ let statusText;
+ if (aTotal == 0) {
+ statusText = gAddressBookBundle.getString("noMatchFound");
+ } else {
+ statusText = PluralForm
+ .get(aTotal, gAddressBookBundle.getString("matchesFound1"))
+ .replace("#1", aTotal);
+ }
+
+ gStatusText.setAttribute("label", statusText);
+ }
+};
+
+function searchOnLoad()
+{
+ setHelpFileURI("chrome://communicator/locale/help/suitehelp.rdf");
+ UpgradeAddressBookResultsPaneUI("mailnews.ui.advanced_directory_search_results.version");
+
+ initializeSearchWidgets();
+ initializeSearchWindowWidgets();
+
+ gSearchBundle = document.getElementById("bundle_search");
+ gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
+ gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
+ gAddressBookBundle = document.getElementById("bundle_addressBook");
+ gSearchSession = Cc[searchSessionContractID].createInstance(Ci.nsIMsgSearchSession);
+
+ // initialize a flag for phonetic name search
+ gSearchPhoneticName =
+ GetLocalizedStringPref("mail.addr_book.show_phonetic_fields");
+
+ if (window.arguments && window.arguments[0])
+ SelectDirectory(window.arguments[0].directory);
+ else
+ SelectDirectory(document.getElementById("abPopup-menupopup")
+ .firstChild.value);
+
+ // initialize globals, see abCommon.js, InitCommonJS()
+ abList = document.getElementById("abPopup");
+
+ onMore(null);
+}
+
+function searchOnUnload()
+{
+ CloseAbView();
+}
+
+function initializeSearchWindowWidgets()
+{
+ gSearchStopButton = document.getElementById("search-button");
+ gPropertiesButton = document.getElementById("propertiesButton");
+ gComposeButton = document.getElementById("composeButton");
+ gStatusText = document.getElementById('statusText');
+ // matchAll doesn't make sense for address book search
+ hideMatchAllItem();
+}
+
+function onSearchStop()
+{
+}
+
+function onAbSearchReset(event)
+{
+ gPropertiesButton.setAttribute("disabled","true");
+ gComposeButton.setAttribute("disabled","true");
+
+ CloseAbView();
+
+ onReset(event);
+ gStatusText.setAttribute("label", "");
+}
+
+function SelectDirectory(aURI)
+{
+ var selectedAB = aURI;
+
+ if (!selectedAB)
+ selectedAB = kPersonalAddressbookURI;
+
+ // set popup with address book names
+ var abPopup = document.getElementById('abPopup');
+ if ( abPopup )
+ abPopup.value = selectedAB;
+
+ setSearchScope(GetScopeForDirectoryURI(selectedAB));
+}
+
+function GetScopeForDirectoryURI(aURI)
+{
+ var directory = MailServices.ab.getDirectory(aURI);
+ var booleanAnd = gSearchBooleanRadiogroup.selectedItem.value == "and";
+
+ if (directory.isRemote) {
+ if (booleanAnd)
+ return nsMsgSearchScope.LDAPAnd;
+ else
+ return nsMsgSearchScope.LDAP;
+ }
+ else {
+ if (booleanAnd)
+ return nsMsgSearchScope.LocalABAnd;
+ else
+ return nsMsgSearchScope.LocalAB;
+ }
+}
+
+function onEnterInSearchTerm()
+{
+ // on enter
+ // if not searching, start the search
+ // if searching, stop and then start again
+ if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) {
+ onSearch();
+ }
+ else {
+ onSearchStop();
+ onSearch();
+ }
+}
+
+function onSearch()
+{
+ gStatusText.setAttribute("label", "");
+ gPropertiesButton.setAttribute("disabled","true");
+ gComposeButton.setAttribute("disabled","true");
+
+ gSearchSession.clearScopes();
+
+ var currentAbURI = document.getElementById('abPopup').getAttribute('value');
+
+ gSearchSession.addDirectoryScopeTerm(GetScopeForDirectoryURI(currentAbURI));
+ gSearchSession.searchTerms = saveSearchTerms(gSearchSession.searchTerms, gSearchSession);
+
+ var searchUri = currentAbURI + "?(";
+
+ for (let i = 0; i < gSearchSession.searchTerms.length; i++) {
+ let searchTerm = gSearchSession.searchTerms[i];
+
+ // get the "and" / "or" value from the first term
+ if (i == 0) {
+ if (searchTerm.booleanAnd)
+ searchUri += "and";
+ else
+ searchUri += "or";
+ }
+
+ var attrs;
+
+ switch (searchTerm.attrib) {
+ case nsMsgSearchAttrib.Name:
+ if (gSearchPhoneticName != "true")
+ attrs = ["DisplayName","FirstName","LastName","NickName"];
+ else
+ attrs = ["DisplayName","FirstName","LastName","NickName","PhoneticFirstName","PhoneticLastName"];
+ break;
+ case nsMsgSearchAttrib.DisplayName:
+ attrs = ["DisplayName"];
+ break;
+ case nsMsgSearchAttrib.Email:
+ attrs = ["PrimaryEmail"];
+ break;
+ case nsMsgSearchAttrib.PhoneNumber:
+ attrs = ["HomePhone","WorkPhone","FaxNumber","PagerNumber","CellularNumber"];
+ break;
+ case nsMsgSearchAttrib.Organization:
+ attrs = ["Company"];
+ break;
+ case nsMsgSearchAttrib.Department:
+ attrs = ["Department"];
+ break;
+ case nsMsgSearchAttrib.City:
+ attrs = ["WorkCity"];
+ break;
+ case nsMsgSearchAttrib.Street:
+ attrs = ["WorkAddress"];
+ break;
+ case nsMsgSearchAttrib.Nickname:
+ attrs = ["NickName"];
+ break;
+ case nsMsgSearchAttrib.WorkPhone:
+ attrs = ["WorkPhone"];
+ break;
+ case nsMsgSearchAttrib.HomePhone:
+ attrs = ["HomePhone"];
+ break;
+ case nsMsgSearchAttrib.Fax:
+ attrs = ["FaxNumber"];
+ break;
+ case nsMsgSearchAttrib.Pager:
+ attrs = ["PagerNumber"];
+ break;
+ case nsMsgSearchAttrib.Mobile:
+ attrs = ["CellularNumber"];
+ break;
+ case nsMsgSearchAttrib.Title:
+ attrs = ["JobTitle"];
+ break;
+ case nsMsgSearchAttrib.AdditionalEmail:
+ attrs = ["SecondEmail"];
+ break;
+ default:
+ dump("XXX " + searchTerm.attrib + " not a supported search attr!\n");
+ attrs = ["DisplayName"];
+ break;
+ }
+
+ var opStr;
+
+ switch (searchTerm.op) {
+ case nsMsgSearchOp.Contains:
+ opStr = "c";
+ break;
+ case nsMsgSearchOp.DoesntContain:
+ opStr = "!c";
+ break;
+ case nsMsgSearchOp.Is:
+ opStr = "=";
+ break;
+ case nsMsgSearchOp.Isnt:
+ opStr = "!=";
+ break;
+ case nsMsgSearchOp.BeginsWith:
+ opStr = "bw";
+ break;
+ case nsMsgSearchOp.EndsWith:
+ opStr = "ew";
+ break;
+ case nsMsgSearchOp.SoundsLike:
+ opStr = "~=";
+ break;
+ default:
+ opStr = "c";
+ break;
+ }
+
+ // currently, we can't do "and" and "or" searches at the same time
+ // (it's either all "and"s or all "or"s)
+ var max_attrs = attrs.length;
+
+ for (var j=0;j<max_attrs;j++) {
+ // append the term(s) to the searchUri
+ searchUri += "(" + attrs[j] + "," + opStr + "," + encodeABTermValue(searchTerm.value.str) + ")";
+ }
+ }
+
+ searchUri += ")";
+ SetAbView(searchUri);
+}
+
+// used to toggle functionality for Search/Stop button.
+function onSearchButton(event)
+{
+ if (event.target.label == gSearchBundle.getString("labelForSearchButton"))
+ onSearch();
+ else
+ onSearchStop();
+}
+
+function GetAbViewListener()
+{
+ return gSearchAbViewListener;
+}
+
+function onProperties()
+{
+ AbEditSelectedCard();
+}
+
+function onCompose()
+{
+ AbNewMessage();
+}
+
+function AbResultsPaneDoubleClick(card)
+{
+ AbEditCard(card);
+}
+
+function UpdateCardView()
+{
+ var numSelected = GetNumSelectedCards();
+
+ if (!numSelected) {
+ gPropertiesButton.setAttribute("disabled","true");
+ gComposeButton.setAttribute("disabled","true");
+ return;
+ }
+
+ gComposeButton.removeAttribute("disabled");
+
+ if (numSelected == 1)
+ gPropertiesButton.removeAttribute("disabled");
+ else
+ gPropertiesButton.setAttribute("disabled","true");
+}
diff --git a/comm/suite/mailnews/content/ABSearchDialog.xul b/comm/suite/mailnews/content/ABSearchDialog.xul
new file mode 100644
index 0000000000..a32c7dbb8b
--- /dev/null
+++ b/comm/suite/mailnews/content/ABSearchDialog.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+<?xml-stylesheet href="chrome://messenger/skin/searchDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abResultsPaneOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % abResultsPaneOverlayDTD SYSTEM "chrome://messenger/locale/addressbook
+/abResultsPaneOverlay.dtd">
+%abResultsPaneOverlayDTD;
+<!ENTITY % SearchDialogDTD SYSTEM "chrome://messenger/locale/SearchDialog.dtd">
+%SearchDialogDTD;
+]>
+
+<dialog id="searchAddressBookWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ windowtype="mailnews:absearch"
+ title="&abSearchDialogTitle.label;"
+ style="width: 52em; height: 34em;"
+ persist="screenX screenY width height sizemode"
+ buttons="help"
+ ondialoghelp="return openHelp('mail_advanced_ab_search');"
+ onload="searchOnLoad();"
+ onunload="onSearchStop(); searchOnUnload();">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://messenger/content/mailWindow.js"/>
+ <script src="chrome://messenger/content/msgMail3PaneWindow.js"/>
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://messenger/content/commandglue.js"/>
+ <script src="chrome://messenger/content/ABSearchDialog.js"/>
+ <script src="chrome://messenger/content/addressbook/abCommon.js"/>
+
+ <broadcaster id="Communicator:WorkMode"/>
+
+ <dummy class="usesMailWidgets"/>
+
+ <vbox id="searchTerms" flex="3" persist="height">
+ <vbox>
+ <hbox align="center">
+ <label value="&abSearchHeading.label;" accesskey="&abSearchHeading.accesskey;" control="abPopup"/>
+ <menulist id="abPopup" oncommand="SelectDirectory(this.value);">
+ <menupopup id="abPopup-menupopup" class="addrbooksPopup"/>
+ </menulist>
+ <spacer flex="10"/>
+ <button id="search-button" oncommand="onSearchButton(event);" default="true"/>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <button label="&resetButton.label;" oncommand="onAbSearchReset(event);" accesskey="&resetButton.accesskey;"/>
+ </hbox>
+ </vbox>
+
+ <hbox flex="1">
+ <vbox id="searchTermListBox" flex="1"/>
+ </hbox>
+ </vbox>
+
+ <splitter id="gray_horizontal_splitter" collapse="after" persist="state">
+ <grippy/>
+ </splitter>
+
+ <vbox id="searchResults" flex="4" persist="height">
+ <vbox id="searchResultListBox" flex="1">
+ <tree id="abResultsTree" flex="1" context="threadPaneContext"/>
+ </vbox>
+ <hbox align="center">
+ <button id="propertiesButton"
+ label="&propertiesButton.label;"
+ accesskey="&propertiesButton.accesskey;"
+ disabled="true"
+ oncommand="onProperties();"/>
+ <button id="composeButton"
+ label="&composeButton.label;"
+ accesskey="&composeButton.accesskey;"
+ disabled="true"
+ oncommand="onCompose();"/>
+ <spacer flex="1"/>
+ <button dlgtype="help" class="dialog-button"/>
+ </hbox>
+ </vbox>
+
+ <statusbar class="chromeclass-status" id="status-bar">
+ <statusbarpanel id="statusText" crop="right" flex="1"/>
+ <statusbarpanel id="offline-status" class="statusbarpanel-iconic"/>
+ </statusbar>
+
+</dialog>
diff --git a/comm/suite/mailnews/content/FilterListDialog.js b/comm/suite/mailnews/content/FilterListDialog.js
new file mode 100644
index 0000000000..555796fc5e
--- /dev/null
+++ b/comm/suite/mailnews/content/FilterListDialog.js
@@ -0,0 +1,1037 @@
+/* -*- 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/. */
+
+const { PluralForm } = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+
+var gEditButton;
+var gDeleteButton;
+var gNewButton;
+var gCopyToNewButton;
+var gTopButton;
+var gUpButton;
+var gDownButton;
+var gBottomButton;
+var gSearchBox;
+var gRunFiltersFolderPrefix;
+var gRunFiltersFolder;
+var gRunFiltersButton;
+var gFilterBundle;
+var gFilterListMsgWindow = null;
+var gFilterListbox;
+var gCurrentFilterList;
+var gStatusBar;
+var gStatusText;
+var gServerMenu;
+
+var msgMoveMotion = {
+ Up : 0,
+ Down : 1,
+ Top : 2,
+ Bottom : 3,
+}
+
+var gStatusFeedback = {
+ showStatusString: function(status)
+ {
+ gStatusText.setAttribute("value", status);
+ },
+ startMeteors: function()
+ {
+ // change run button to be a stop button
+ gRunFiltersButton.setAttribute("label", gRunFiltersButton.getAttribute("stoplabel"));
+ gRunFiltersButton.setAttribute("accesskey", gRunFiltersButton.getAttribute("stopaccesskey"));
+ gStatusBar.setAttribute("mode", "undetermined");
+ },
+ stopMeteors: function()
+ {
+ try {
+ // change run button to be a stop button
+ gRunFiltersButton.setAttribute("label", gRunFiltersButton.getAttribute("runlabel"));
+ gRunFiltersButton.setAttribute("accesskey", gRunFiltersButton.getAttribute("runaccesskey"));
+ gStatusBar.setAttribute("mode", "normal");
+ }
+ catch (ex) {
+ // can get here if closing window when running filters
+ }
+ },
+ showProgress: function(percentage)
+ {
+ },
+ closeWindow: function()
+ {
+ }
+};
+
+function onLoad()
+{
+ setHelpFileURI("chrome://communicator/locale/help/suitehelp.rdf");
+ gFilterListMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(Ci.nsIMsgWindow);
+ gFilterListMsgWindow.domWindow = window;
+ gFilterListMsgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+ gFilterListMsgWindow.statusFeedback = gStatusFeedback;
+
+ gFilterBundle = document.getElementById("bundle_filter");
+
+ gServerMenu = document.getElementById("serverMenu");
+ gFilterListbox = document.getElementById("filterList");
+
+ gEditButton = document.getElementById("editButton");
+ gDeleteButton = document.getElementById("deleteButton");
+ gNewButton = document.getElementById("newButton");
+ gCopyToNewButton = document.getElementById("copyToNewButton");
+ gTopButton = document.getElementById("reorderTopButton");
+ gUpButton = document.getElementById("reorderUpButton");
+ gDownButton = document.getElementById("reorderDownButton");
+ gBottomButton = document.getElementById("reorderBottomButton");
+ gSearchBox = document.getElementById("searchBox");
+ gRunFiltersFolderPrefix = document.getElementById("folderPickerPrefix");
+ gRunFiltersFolder = document.getElementById("runFiltersFolder");
+ gRunFiltersButton = document.getElementById("runFiltersButton");
+ gStatusBar = document.getElementById("statusbar-icon");
+ gStatusText = document.getElementById("statusText");
+
+ updateButtons();
+
+ processWindowArguments(window.arguments[0]);
+
+ Services.obs.addObserver(onFilterClose,
+ "quit-application-requested");
+
+ top.controllers.insertControllerAt(0, gFilterController);
+}
+
+/**
+ * Processes arguments sent to this dialog when opened or refreshed.
+ *
+ * @param aArguments An object having members representing the arguments.
+ * { arg1: value1, arg2: value2, ... }
+ */
+function processWindowArguments(aArguments) {
+ let wantedFolder;
+ if ("folder" in aArguments)
+ wantedFolder = aArguments.folder;
+
+ // If a specific folder was requested, try to select it
+ // if we don't already show its server.
+ if (!gServerMenu._folder ||
+ (wantedFolder && (wantedFolder != gServerMenu._folder) &&
+ (wantedFolder.rootFolder != gServerMenu._folder))) {
+
+ // Get the folder where filters should be defined, if that server
+ // can accept filters.
+ let firstItem = getFilterFolderForSelection(wantedFolder);
+
+ // if the selected server cannot have filters, get the default server
+ // if the default server cannot have filters, check all accounts
+ // and get a server that can have filters.
+ if (!firstItem) {
+ var server = getServerThatCanHaveFilters();
+ if (server)
+ firstItem = server.rootFolder;
+ }
+
+ if (firstItem)
+ setFilterFolder(firstItem);
+
+ if (wantedFolder)
+ setRunFolder(wantedFolder);
+ } else {
+ // If we didn't change folder still redraw the list
+ // to show potential new filters if we were called for refresh.
+ rebuildFilterList();
+ }
+
+ // If a specific filter was requested, try to select it.
+ if ("filter" in aArguments)
+ selectFilter(aArguments.filter);
+}
+
+/**
+ * This is called from OpenOrFocusWindow() if the dialog is already open.
+ * New filters could have been created by operations outside the dialog.
+ *
+ * @param aArguments An object of arguments having the same format
+ * as window.arguments[0].
+ */
+function refresh(aArguments) {
+ // As we really don't know what has changed, clear the search box
+ // unconditionally so that the changed/added filters are surely visible.
+ resetSearchBox();
+
+ processWindowArguments(aArguments);
+}
+
+function CanRunFiltersAfterTheFact(aServer)
+{
+ // filter after the fact is implement using search
+ // so if you can't search, you can't filter after the fact
+ return aServer.canSearchMessages;
+}
+
+/**
+ * Change the root server for which we are managing filters.
+ *
+ * @param msgFolder The nsIMsgFolder server containing filters
+ * (or a folder for NNTP server).
+ */
+function setFilterFolder(msgFolder) {
+ if (!msgFolder || msgFolder == gServerMenu._folder)
+ return;
+
+ // Save the current filters to disk before switching because
+ // the dialog may be closed and we'll lose current filters.
+ if (gCurrentFilterList)
+ gCurrentFilterList.saveToDefaultFile();
+
+ // Setting this attribute should go away in bug 473009.
+ gServerMenu._folder = msgFolder;
+ // Calling this should go away in bug 802609.
+ gServerMenu.menupopup.selectFolder(msgFolder);
+
+ // Calling getEditableFilterList will detect any errors in
+ // msgFilterRules.dat, backup the file, and alert the user.
+ gCurrentFilterList = msgFolder.getEditableFilterList(gFilterListMsgWindow);
+ rebuildFilterList();
+
+ // Select the first item in the list, if there is one.
+ if (gFilterListbox.itemCount > 0)
+ gFilterListbox.selectItem(gFilterListbox.getItemAtIndex(0));
+
+ // This will get the deferred to account root folder, if server is deferred.
+ // We intentionally do this after setting the current server, as we want
+ // that to refer to the rootFolder for the actual server, not the
+ // deferred-to server, as current server is really a proxy for the
+ // server whose filters we are editing. But below here we are managing
+ // where the filters will get applied, which is on the deferred-to server.
+ msgFolder = msgFolder.server.rootMsgFolder;
+
+ // root the folder picker to this server
+ let runMenu = gRunFiltersFolder.menupopup;
+ runMenu._teardown();
+ runMenu._parentFolder = msgFolder;
+ runMenu._ensureInitialized();
+
+ var canFilterAfterTheFact = CanRunFiltersAfterTheFact(msgFolder.server);
+ gRunFiltersButton.hidden = !canFilterAfterTheFact;
+ gRunFiltersFolder.hidden = !canFilterAfterTheFact;
+ gRunFiltersFolderPrefix.hidden = !canFilterAfterTheFact;
+
+ if (canFilterAfterTheFact) {
+ let wantedFolder = null;
+ // For a given server folder, get the default run target folder or show
+ // "Choose Folder".
+ if (!msgFolder.isServer) {
+ wantedFolder = msgFolder;
+ } else {
+ try {
+ switch (msgFolder.server.type) {
+ case "nntp":
+ // For NNTP select the subscribed newsgroup.
+ wantedFolder = gServerMenu._folder;
+ break;
+ case "rss":
+ // Show "Choose Folder" for feeds.
+ wantedFolder = null;
+ break;
+ case "imap":
+ case "pop3":
+ case "none":
+ // Find Inbox for IMAP and POP or Local Folders,
+ // show "Choose Folder" if not found.
+ wantedFolder = msgFolder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
+ break;
+ default:
+ // For other account types we don't know what's good to select,
+ // so show "Choose Folder".
+ wantedFolder = null;
+ }
+ } catch (e) {
+ Cu.reportError("Failed to select a suitable folder to run filters on: " + e);
+ wantedFolder = null;
+ }
+ }
+ // Select a useful first folder for the server.
+ setRunFolder(wantedFolder);
+ }
+}
+
+/**
+ * Select a folder on which filters are to be run.
+ *
+ * @param aFolder nsIMsgFolder folder to select.
+ */
+function setRunFolder(aFolder) {
+ // Setting this attribute should go away in bug 473009.
+ gRunFiltersFolder._folder = aFolder;
+ // Calling this should go away in bug 802609.
+ gRunFiltersFolder.menupopup.selectFolder(gRunFiltersFolder._folder);
+ updateButtons();
+}
+
+/**
+ * Toggle enabled state of a filter, in both the filter properties and the UI.
+ *
+ * @param aFilterItem an item (row) of the filter list to be toggled
+ */
+function toggleFilter(aFilterItem)
+{
+ let filter = aFilterItem._filter;
+ if (filter.unparseable && !filter.enabled)
+ {
+ Services.prompt.alert(window, null,
+ gFilterBundle.getFormattedString("cannotEnableIncompatFilter",
+ [document.getElementById("bundle_brand").getString("brandShortName")]));
+ return;
+ }
+ filter.enabled = !filter.enabled;
+
+ // Now update the checkbox
+ aFilterItem.childNodes[1].setAttribute("enabled", filter.enabled);
+ // For accessibility set the checked state on listitem
+ aFilterItem.setAttribute("aria-checked", filter.enabled);
+}
+
+/**
+ * Selects a specific filter in the filter list.
+ * The listbox view is scrolled to the corresponding item.
+ *
+ * @param aFilter The nsIMsgFilter to select.
+ *
+ * @return true/false indicating whether the filter was found and selected.
+ */
+function selectFilter(aFilter) {
+ if (currentFilter() == aFilter)
+ return true;
+
+ resetSearchBox(aFilter);
+
+ let filterCount = gCurrentFilterList.filterCount;
+ for (let i = 0; i < filterCount; i++) {
+ if (gCurrentFilterList.getFilterAt(i) == aFilter) {
+ gFilterListbox.ensureIndexIsVisible(i);
+ gFilterListbox.selectedIndex = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Returns the currently selected filter. If multiple filters are selected,
+ * returns the first one. If none are selected, returns null.
+ */
+function currentFilter()
+{
+ let currentItem = gFilterListbox.selectedItem;
+ return currentItem ? currentItem._filter : null;
+}
+
+function onEditFilter()
+{
+ if (gEditButton.disabled)
+ return;
+
+ var selectedFilter = currentFilter();
+ if (!selectedFilter)
+ return;
+
+ let args = {filter: selectedFilter, filterList: gCurrentFilterList};
+
+ window.openDialog("chrome://messenger/content/FilterEditor.xul", "FilterEditor", "chrome,modal,titlebar,resizable,centerscreen", args);
+
+ if ("refresh" in args && args.refresh) {
+ // Reset search if edit was okay (name change might lead to hidden entry).
+ resetSearchBox(selectedFilter);
+ rebuildFilterList();
+ }
+}
+
+/**
+ * Handler function for the 'New...' buttons.
+ * Opens the filter dialog for creating a new filter.
+ */
+function onNewFilter() {
+ calculatePositionAndShowCreateFilterDialog({});
+}
+
+/**
+ * Handler function for the 'Copy...' button.
+ * Opens the filter dialog for copying the selected filter.
+ */
+function onCopyToNewFilter() {
+ if (gCopyToNewButton.disabled)
+ return;
+
+ let selectedFilter = currentFilter();
+ if (!selectedFilter)
+ return;
+
+ calculatePositionAndShowCreateFilterDialog({copiedFilter: selectedFilter});
+}
+
+/**
+ * Calculates the position for inserting the new filter,
+ * and then displays the create dialog.
+ *
+ * @param args The object containing the arguments for the dialog,
+ * passed to the filterEditorOnLoad() function.
+ * It will be augmented with the insertion position
+ * and global filters list properties by this function.
+ */
+function calculatePositionAndShowCreateFilterDialog(args) {
+ let selectedFilter = currentFilter();
+ // If no filter is selected use the first position.
+ let position = 0;
+ if (selectedFilter) {
+ // Get the position in the unfiltered list.
+ // - this is where the new filter should be inserted!
+ let filterCount = gCurrentFilterList.filterCount;
+ for (let i = 0; i < filterCount; i++) {
+ if (gCurrentFilterList.getFilterAt(i) == selectedFilter) {
+ position = i;
+ break;
+ }
+ }
+ }
+ args.filterPosition = position;
+ args.filterList = gCurrentFilterList;
+ args.refresh = false;
+
+ window.openDialog("chrome://messenger/content/FilterEditor.xul",
+ "FilterEditor",
+ "chrome,modal,titlebar,resizable,centerscreen", args);
+
+ if (args.refresh)
+ {
+ // On success: reset the search box if necessary!
+ resetSearchBox(args.newFilter);
+ rebuildFilterList();
+
+ // Select the new filter, it is at the position of previous selection.
+ gFilterListbox.selectItem(gFilterListbox.getItemAtIndex(position));
+ if (currentFilter() != args.newFilter)
+ Cu.reportError("Filter created at an unexpected position!");
+ }
+}
+
+function onDeleteFilter()
+{
+ if (gDeleteButton.disabled)
+ return;
+
+ let items = gFilterListbox.selectedItems;
+ if (!items.length)
+ return;
+
+ let checkValue = {value: false};
+ if (Services.prefs.getBoolPref("mailnews.filters.confirm_delete") &&
+ Services.prompt.confirmEx(window, null,
+ gFilterBundle.getString("deleteFilterConfirmation"),
+ Services.prompt.STD_YES_NO_BUTTONS,
+ '', '', '',
+ gFilterBundle.getString('dontWarnAboutDeleteCheckbox'),
+ checkValue))
+ return;
+
+ if (checkValue.value)
+ Services.prefs.setBoolPref("mailnews.filters.confirm_delete", false);
+
+ // Save filter position before the first selected one.
+ let newSelectionIndex = gFilterListbox.selectedIndex - 1;
+
+ // Must reverse the loop, as the items list shrinks when we delete.
+ for (let index = items.length - 1; index >= 0; --index) {
+ let item = items[index];
+ gCurrentFilterList.removeFilter(item._filter);
+ item.remove();
+ }
+ updateCountBox();
+
+ // Select filter above previously selected if one existed,
+ // otherwise the first one.
+ if (newSelectionIndex == -1 && gFilterListbox.itemCount > 0)
+ newSelectionIndex = 0;
+ if (newSelectionIndex > -1) {
+ gFilterListbox.selectedIndex = newSelectionIndex;
+ updateViewPosition(-1);
+ }
+}
+
+/**
+ * Move filter one step up in visible list.
+ */
+function onUp(event) {
+ moveFilter(msgMoveMotion.Up);
+}
+
+/**
+ * Move filter one step down in visible list.
+ */
+function onDown(event) {
+ moveFilter(msgMoveMotion.Down);
+}
+
+/**
+ * Move filter to bottom for long filter lists.
+ */
+function onTop(event) {
+ moveFilter(msgMoveMotion.Top);
+}
+
+/**
+ * Move filter to top for long filter lists.
+ */
+function onBottom(event) {
+ moveFilter(msgMoveMotion.Bottom);
+}
+
+/**
+ * Moves a singular selected filter up or down either 1 increment or to the
+ * top/bottom.
+ *
+ * @param motion
+ * msgMoveMotion.Up, msgMoveMotion.Down, msgMoveMotion.Top, msgMoveMotion.Bottom
+ */
+function moveFilter(motion) {
+ // At the moment, do not allow moving groups of filters.
+ let selectedFilter = currentFilter();
+ if (!selectedFilter)
+ return;
+
+ let relativeStep = 0;
+ let moveFilterNative;
+
+ switch (motion) {
+ case msgMoveMotion.Top:
+ if (selectedFilter) {
+ gCurrentFilterList.removeFilter(selectedFilter);
+ gCurrentFilterList.insertFilterAt(0, selectedFilter);
+ rebuildFilterList();
+ }
+ return;
+ case msgMoveMotion.Bottom:
+ if (selectedFilter) {
+ gCurrentFilterList.removeFilter(selectedFilter);
+ gCurrentFilterList.insertFilterAt(gCurrentFilterList.filterCount,
+ selectedFilter);
+ rebuildFilterList();
+ }
+ return;
+ case msgMoveMotion.Up:
+ relativeStep = -1;
+ moveFilterNative = Ci.nsMsgFilterMotion.up;
+ break;
+ case msgMoveMotion.Down:
+ relativeStep = +1;
+ moveFilterNative = Ci.nsMsgFilterMotion.down;
+ break;
+ }
+
+ if (!gSearchBox.value) {
+ // Use legacy move filter code: up, down; only if searchBox is empty.
+ moveCurrentFilter(moveFilterNative);
+ return;
+ }
+
+ let nextIndex = gFilterListbox.selectedIndex + relativeStep;
+ let nextFilter = gFilterListbox.getItemAtIndex(nextIndex)._filter;
+
+ gCurrentFilterList.removeFilter(selectedFilter);
+
+ // Find the index of the filter we want to insert at.
+ let newIndex = -1;
+ let filterCount = gCurrentFilterList.filterCount;
+ for (let i = 0; i < filterCount; i++) {
+ if (gCurrentFilterList.getFilterAt(i) == nextFilter) {
+ newIndex = i;
+ break;
+ }
+ }
+
+ if (motion == msgMoveMotion.Down)
+ newIndex += relativeStep;
+
+ gCurrentFilterList.insertFilterAt(newIndex, selectedFilter);
+
+ rebuildFilterList();
+}
+
+function viewLog()
+{
+ let args = {filterList: gCurrentFilterList};
+
+ window.openDialog("chrome://messenger/content/viewLog.xul", "FilterLog", "chrome,modal,titlebar,resizable,centerscreen", args);
+}
+
+function onFilterUnload()
+{
+ // make sure to save the filter to disk
+ if (gCurrentFilterList)
+ gCurrentFilterList.saveToDefaultFile();
+
+ Services.obs.removeObserver(onFilterClose, "quit-application-requested");
+ top.controllers.removeController(gFilterController);
+}
+
+function onFilterClose(aCancelQuit, aTopic, aData)
+{
+ if (aTopic == "quit-application-requested" &&
+ aCancelQuit instanceof Ci.nsISupportsPRBool &&
+ aCancelQuit.data)
+ return false;
+
+ if (gRunFiltersButton.getAttribute("label") == gRunFiltersButton.getAttribute("stoplabel")) {
+ var promptTitle = gFilterBundle.getString("promptTitle");
+ var promptMsg = gFilterBundle.getString("promptMsg");;
+ var stopButtonLabel = gFilterBundle.getString("stopButtonLabel");
+ var continueButtonLabel = gFilterBundle.getString("continueButtonLabel");
+
+ if (Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1),
+ continueButtonLabel, stopButtonLabel, null, null, {value:0}) == 0) {
+ if (aTopic == "quit-application-requested")
+ aCancelQuit.data = true;
+ return false;
+ }
+ gFilterListMsgWindow.StopUrls();
+ }
+
+ return true;
+}
+
+function runSelectedFilters()
+{
+ // if run button has "stop" label, do stop.
+ if (gRunFiltersButton.getAttribute("label") == gRunFiltersButton.getAttribute("stoplabel")) {
+ gFilterListMsgWindow.StopUrls();
+ return;
+ }
+
+ let folder = gRunFiltersFolder._folder ||
+ gRunFiltersFolder.selectedItem._folder;
+ if (!folder)
+ return;
+
+ let filterList = MailServices.filters.getTempFilterList(folder);
+
+ // make sure the tmp filter list uses the real filter list log stream
+ filterList.logStream = gCurrentFilterList.logStream;
+ filterList.loggingEnabled = gCurrentFilterList.loggingEnabled;
+
+ let index = 0;
+ for (let item of gFilterListbox.selectedItems) {
+ filterList.insertFilterAt(index++, item._filter);
+ }
+
+ MailServices.filters.applyFiltersToFolders(filterList, [folder], gFilterListMsgWindow);
+}
+
+function moveCurrentFilter(motion)
+{
+ let filter = currentFilter();
+ if (!filter)
+ return;
+
+ gCurrentFilterList.moveFilter(filter, motion);
+ rebuildFilterList();
+}
+
+/**
+ * Redraws the list of filters. Takes the search box value into account.
+ *
+ * This function should perform very fast even in case of high number of filters.
+ * Therefore there are some optimizations (e.g. listelement.children[] instead of
+ * list.getItemAtIndex()), that favour speed vs. semantical perfection.
+ */
+function rebuildFilterList()
+{
+ // Get filters that match the search box.
+ let aTempFilterList = onFindFilter();
+
+ let searchBoxFocus = false;
+ let activeElement = document.activeElement;
+
+ // Find if the currently focused element is a child inside the search box
+ // (probably html:input). Traverse up the parents until the first element
+ // with an ID is found. If it is not searchBox, return false.
+ while (activeElement != null) {
+ if (activeElement == gSearchBox) {
+ searchBoxFocus = true;
+ break;
+ }
+ else if (activeElement.id) {
+ searchBoxFocus = false;
+ break;
+ }
+ activeElement = activeElement.parentNode;
+ }
+
+ // Make a note of which filters were previously selected
+ let selectedNames = [];
+ for (let i = 0; i < gFilterListbox.selectedItems.length; i++)
+ selectedNames.push(gFilterListbox.selectedItems[i]._filter.filterName);
+
+ // Save scroll position so we can try to restore it later.
+ // Doesn't work when the list is rebuilt after search box condition changed.
+ let firstVisibleRowIndex = gFilterListbox.getIndexOfFirstVisibleRow();
+
+ // listbox.xml seems to cache the value of the first selected item in a
+ // range at _selectionStart. The old value though is now obsolete,
+ // since we will recreate all of the elements. We need to clear this,
+ // and one way to do this is with a call to clearSelection. This might be
+ // ugly from an accessibility perspective, since it fires an onSelect event.
+ gFilterListbox.clearSelection();
+
+ let listitem, nameCell, enabledCell, filter;
+ let filterCount = gCurrentFilterList.filterCount;
+ let listitemCount = gFilterListbox.itemCount;
+ let listitemIndex = 0;
+ let tempFilterListLength = aTempFilterList ? aTempFilterList.length - 1 : 0;
+ for (let i = 0; i < filterCount; i++) {
+ if (aTempFilterList && listitemIndex > tempFilterListLength)
+ break;
+
+ filter = gCurrentFilterList.getFilterAt(i);
+ if (aTempFilterList && aTempFilterList[listitemIndex] != i)
+ continue;
+
+ if (listitemCount > listitemIndex) {
+ // If there is a free existing listitem, reuse it.
+ // Use .children[] instead of .getItemAtIndex() as it is much faster.
+ listitem = gFilterListbox.children[listitemIndex + 1];
+ nameCell = listitem.childNodes[0];
+ enabledCell = listitem.childNodes[1];
+ }
+ else
+ {
+ // If there are not enough listitems in the list, create a new one.
+ listitem = document.createElement("listitem");
+ listitem.setAttribute("role", "checkbox");
+ nameCell = document.createElement("listcell");
+ enabledCell = document.createElement("listcell");
+ enabledCell.setAttribute("class", "listcell-iconic");
+ listitem.appendChild(nameCell);
+ listitem.appendChild(enabledCell);
+ gFilterListbox.appendChild(listitem);
+ let size = (enabledCell.clientWidth - 28) / 2;
+ enabledCell.style.paddingLeft = size.toString() + "px";
+ // We have to attach this listener to the listitem, even though we only
+ // care about clicks on the enabledCell. However, attaching to that item
+ // doesn't result in any events actually getting received.
+ listitem.addEventListener("click", onFilterClick, true);
+ listitem.addEventListener("dblclick", onFilterDoubleClick, true);
+ }
+ // For accessibility set the label on listitem.
+ listitem.setAttribute("label", filter.filterName);
+ // Set the listitem values to represent the current filter.
+ nameCell.setAttribute("label", filter.filterName);
+ enabledCell.setAttribute("enabled", filter.enabled);
+ listitem.setAttribute("aria-checked", filter.enabled);
+ listitem._filter = filter;
+
+ if (selectedNames.includes(filter.filterName))
+ gFilterListbox.addItemToSelection(listitem);
+
+ listitemIndex++;
+ }
+ // Remove any superfluous listitems, if the number of filters shrunk.
+ for (let i = listitemCount - 1; i >= listitemIndex; i--) {
+ gFilterListbox.lastChild.remove();
+ }
+
+ updateViewPosition(firstVisibleRowIndex);
+ updateCountBox();
+
+ // If before rebuilding the list the searchbox was focused, focus it again.
+ // In any other case, focus the list.
+ if (searchBoxFocus)
+ gSearchBox.focus();
+ else
+ gFilterListbox.focus();
+}
+
+function updateViewPosition(firstVisibleRowIndex)
+{
+ if (firstVisibleRowIndex == -1)
+ firstVisibleRowIndex = gFilterListbox.getIndexOfFirstVisibleRow();
+
+ // Restore to the extent possible the scroll position.
+ if (firstVisibleRowIndex && gFilterListbox.itemCount)
+ gFilterListbox.scrollToIndex(Math.min(firstVisibleRowIndex,
+ gFilterListbox.itemCount - 1));
+
+ if (gFilterListbox.selectedCount) {
+ // Make sure that at least the first selected item is visible.
+ gFilterListbox.ensureElementIsVisible(gFilterListbox.selectedItems[0]);
+
+ // The current item should be the first selected item, so that keyboard
+ // selection extension can work.
+ gFilterListbox.currentItem = gFilterListbox.selectedItems[0];
+ }
+
+ updateButtons();
+}
+
+/**
+ * Try to only enable buttons that make sense
+ * - moving filters is currently only enabled for single selection
+ * also movement is restricted by searchBox and current selection position
+ * - edit only for single filters
+ * - delete / run only for one or more selected filters
+ */
+function updateButtons()
+{
+ var numFiltersSelected = gFilterListbox.selectedItems.length;
+ var oneFilterSelected = (numFiltersSelected == 1);
+
+ // "edit" only enabled when one filter selected
+ // or if we couldn't parse the filter.
+ let disabled = !oneFilterSelected || currentFilter().unparseable;
+ gEditButton.disabled = disabled;
+
+ // "copy" is the same as "edit".
+ gCopyToNewButton.disabled = disabled;
+
+ // "delete" only disabled when no filters are selected
+ gDeleteButton.disabled = !numFiltersSelected;
+
+ // we can run multiple filters on a folder
+ // so only disable this UI if no filters are selected
+ gRunFiltersFolderPrefix.disabled = !numFiltersSelected;
+ gRunFiltersFolder.disabled = !numFiltersSelected;
+ gRunFiltersButton.disabled = !numFiltersSelected ||
+ !gRunFiltersFolder._folder;
+
+ // "up" and "top" enabled only if one filter is selected,
+ // and it's not the first.
+ // Don't use gFilterListbox.currentIndex here, it's buggy when we've just
+ // changed the children in the list (via rebuildFilterList)
+ disabled = !(oneFilterSelected &&
+ gFilterListbox.getSelectedItem(0) != gFilterListbox.getItemAtIndex(0));
+ gUpButton.disabled = disabled;
+ gTopButton.disabled = disabled;
+
+ // "down" and "bottom" enabled only if one filter selected,
+ // and it's not the last.
+ disabled = !(oneFilterSelected &&
+ gFilterListbox.selectedIndex < gFilterListbox.itemCount - 1);
+ gDownButton.disabled = disabled;
+ gBottomButton.disabled = disabled;
+}
+
+/**
+ * Given a selected folder, returns the folder where filters should
+ * be defined (the root folder except for news) if the server can
+ * accept filters.
+ *
+ * @param nsIMsgFolder aFolder - selected folder, from window args
+ * @returns an nsIMsgFolder where the filter is defined
+ */
+function getFilterFolderForSelection(aFolder) {
+ if (!aFolder || !aFolder.server)
+ return null;
+
+ let rootFolder = aFolder.server.rootFolder;
+ if (rootFolder && rootFolder.isServer && rootFolder.server.canHaveFilters)
+ return (aFolder.server.type == "nntp") ? aFolder : rootFolder;
+
+ return null;
+}
+
+/**
+ * If the selected server cannot have filters, get the default server.
+ * If the default server cannot have filters, check all accounts
+ * and get a server that can have filters.
+ *
+ * @returns an nsIMsgIncomingServer
+ */
+function getServerThatCanHaveFilters()
+{
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ if (defaultAccount) {
+ let defaultIncomingServer = defaultAccount.incomingServer;
+ // Check to see if default server can have filters.
+ if (defaultIncomingServer.canHaveFilters)
+ return defaultIncomingServer;
+ }
+
+ // if it cannot, check all accounts to find a server
+ // that can have filters
+ for (let currentServer of MailServices.accounts.allServers)
+ {
+ if (currentServer.canHaveFilters)
+ return currentServer;
+ }
+
+ return null;
+}
+
+function onFilterClick(event)
+{
+ // We only care about button 0 (left click) events.
+ if (event.button != 0)
+ return;
+
+ // Remember, we had to attach the click-listener to the whole listitem, so
+ // now we need to see if the clicked the enable-column
+ let toggle = event.target.childNodes[1];
+ if ((event.clientX < toggle.boxObject.x + toggle.boxObject.width) &&
+ (event.clientX > toggle.boxObject.x)) {
+ toggleFilter(event.target);
+ event.stopPropagation();
+ }
+}
+
+function onFilterDoubleClick(event)
+{
+ // We only care about button 0 (left click) events.
+ if (event.button != 0)
+ return;
+
+ onEditFilter();
+}
+
+function onFilterListKeyPress(aEvent) {
+ if (aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey || aEvent.shiftKey)
+ return;
+
+ if (aEvent.keyCode) {
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_INSERT:
+ if (!gNewButton.disabled)
+ onNewFilter();
+ break;
+ case KeyEvent.DOM_VK_DELETE:
+ if (!gDeleteButton.disabled)
+ onDeleteFilter();
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ if (!gEditButton.disabled)
+ onEditFilter();
+ break;
+ }
+ return;
+ }
+
+ switch (aEvent.charCode) {
+ case KeyEvent.DOM_VK_SPACE:
+ for (let item of gFilterListbox.selectedItems) {
+ toggleFilter(item);
+ }
+ break;
+ default:
+ gSearchBox.focus();
+ gSearchBox.value = String.fromCharCode(aEvent.charCode);
+ }
+}
+
+/**
+ * Decides if the given filter matches the given keyword.
+ *
+ * @param aFilter nsIMsgFilter to check
+ * @param aKeyword the string to find in the filter name
+ *
+ * @return True if the filter name contains the searched keyword.
+ Otherwise false. In the future this may be extended to match
+ other filter attributes.
+ */
+function filterSearchMatch(aFilter, aKeyword) {
+ return (aFilter.filterName.toLocaleLowerCase().includes(aKeyword))
+}
+
+/**
+ * Called from rebuildFilterList when the list needs to be redrawn.
+ * @return Uses the search term in search box, to produce an array of
+ * row (filter) numbers (indexes) that match the search term.
+ */
+function onFindFilter() {
+ let keyWord = gSearchBox.value.toLocaleLowerCase();
+
+ // If searchbox is empty, just return and let rebuildFilterList
+ // create an unfiltered list.
+ if (!keyWord)
+ return null;
+
+ // Rematch everything in the list, remove what doesn't match the search box.
+ let rows = gCurrentFilterList.filterCount;
+ let matchingFilterList = [];
+ // Use the full gCurrentFilterList, not the filterList listbox,
+ // which may already be filtered.
+ for (let i = 0; i < rows; i++) {
+ if (filterSearchMatch(gCurrentFilterList.getFilterAt(i), keyWord))
+ matchingFilterList.push(i);
+ }
+
+ return matchingFilterList;
+}
+
+/**
+ * Clear the search term in the search box if needed.
+ *
+ * @param aFilter If this nsIMsgFilter matches the search term,
+ * do not reset the box. If this is null,
+ * reset unconditionally.
+ */
+function resetSearchBox(aFilter) {
+ let keyword = gSearchBox.value.toLocaleLowerCase();
+ if (keyword && (!aFilter || !filterSearchMatch(aFilter, keyword)))
+ gSearchBox.reset();
+}
+
+/**
+ * Display "1 item", "11 items" or "4 of 10" if list is filtered via search box.
+ */
+function updateCountBox() {
+ let countBox = document.getElementById("countBox");
+ let sum = gCurrentFilterList.filterCount;
+ let len = gFilterListbox.itemCount;
+
+ if (len == sum) {
+ // "N items"
+ countBox.value = PluralForm.get(len, gFilterBundle.getString("filterCountItems"))
+ .replace("#1", len);
+ countBox.removeAttribute("filterActive");
+ } else {
+ // "N of M"
+ countBox.value = gFilterBundle.getFormattedString("filterCountVisibleOfTotal",
+ [len, sum]);
+ if (len == 0 && sum > 0)
+ countBox.setAttribute("filterActive", "nomatches");
+ else
+ countBox.setAttribute("filterActive", "matches");
+ }
+}
+
+function doHelpButton()
+{
+ openHelp("mail-filters");
+}
+
+var gFilterController =
+{
+ supportsCommand: function(aCommand)
+ {
+ return aCommand == "cmd_selectAll";
+ },
+
+ isCommandEnabled: function(aCommand)
+ {
+ return aCommand == "cmd_selectAll";
+ },
+
+ doCommand: function(aCommand)
+ {
+ if (aCommand == "cmd_selectAll")
+ gFilterListbox.selectAll();
+ },
+
+ onEvent: function(aEvent)
+ {
+ }
+};
diff --git a/comm/suite/mailnews/content/FilterListDialog.xul b/comm/suite/mailnews/content/FilterListDialog.xul
new file mode 100644
index 0000000000..95f6d473ae
--- /dev/null
+++ b/comm/suite/mailnews/content/FilterListDialog.xul
@@ -0,0 +1,197 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/filterDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/FilterListDialog.dtd">
+
+<dialog id="filterListDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="mailnews:filterlist"
+ title="&filterListDialog.title;"
+ style="width: 45em; height: 31em;"
+ persist="width height screenX screenY"
+ buttons="help"
+ ondialoghelp="return openHelp('mail-filters');"
+ onload="onLoad();"
+ onunload="onFilterUnload();"
+ onclose="return onFilterClose();">
+
+ <script src="chrome://messenger/content/FilterListDialog.js"/>
+
+ <stringbundle id="bundle_filter"
+ src="chrome://messenger/locale/filter.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <keyset id="filterKeys">
+ <key id="key_selectAll"/>
+ </keyset>
+
+ <hbox align="center">
+ <label value="&filtersForPrefix.label;"
+ accesskey="&filtersForPrefix.accesskey;"
+ control="serverMenu"/>
+
+ <menulist id="serverMenu"
+ class="folderMenuItem"
+ IsServer="true"
+ IsSecure="false"
+ ServerType="none">
+ <menupopup id="serverMenuPopup"
+ class="menulist-menupopup"
+ type="folder"
+ mode="filters"
+ expandFolders="nntp"
+ showFileHereLabel="true"
+ showAccountsFileHere="true"
+ oncommand="setFilterFolder(event.target._folder)"/>
+ </menulist>
+ <textbox id="searchBox"
+ class="searchBox"
+ flex="1"
+ type="search"
+ oncommand="rebuildFilterList();"
+ emptytext="&searchBox.emptyText;"
+ isempty="true"/>
+ </hbox>
+
+ <grid flex="1">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <separator class="thin"/>
+ </row>
+
+ <row>
+ <hbox>
+ <label id="filterHeader"
+ flex="1"
+ control="filterTree">&filterHeader.label;</label>
+ <label id="countBox"/>
+ </hbox>
+ </row>
+
+ <row flex="1">
+ <vbox>
+ <listbox id="filterList"
+ flex="1"
+ seltype="multiple"
+ onselect="updateButtons();"
+ onkeypress="onFilterListKeyPress(event);">
+ <listhead>
+ <listheader id="nameColumn"
+ label="&nameColumn.label;"
+ flex="1"/>
+ <listheader id="activeColumn"
+ label="&activeColumn.label;"
+ minwidth="40px"/>
+ </listhead>
+ </listbox>
+ </vbox>
+
+ <vbox>
+ <button id="newButton"
+ label="&newButton.label;"
+ accesskey="&newButton.accesskey;"
+ oncommand="onNewFilter();"/>
+ <button id="copyToNewButton"
+ label="&copyButton.label;"
+ accesskey="&copyButton.accesskey;"
+ oncommand="onCopyToNewFilter();"/>
+ <button id="editButton"
+ label="&editButton.label;"
+ accesskey="&editButton.accesskey;"
+ oncommand="onEditFilter();"/>
+ <button id="deleteButton"
+ label="&deleteButton.label;"
+ accesskey="&deleteButton.accesskey;"
+ oncommand="onDeleteFilter();"/>
+ <spacer flex="1"/>
+ <button id="reorderTopButton"
+ label="&reorderTopButton;"
+ accesskey="&reorderTopButton.accessKey;"
+ tooltiptext="&reorderTopButton.toolTip;"
+ oncommand="onTop(event);"/>
+ <button id="reorderUpButton"
+ label="&reorderUpButton.label;"
+ accesskey="&reorderUpButton.accesskey;"
+ class="up"
+ oncommand="onUp(event);"/>
+ <button id="reorderDownButton"
+ label="&reorderDownButton.label;"
+ accesskey="&reorderDownButton.accesskey;"
+ class="down"
+ oncommand="onDown(event);"/>
+ <button id="reorderBottomButton"
+ label="&reorderBottomButton;"
+ accesskey="&reorderBottomButton.accessKey;"
+ tooltiptext="&reorderBottomButton.toolTip;"
+ oncommand="onBottom(event);"/>
+ <spacer flex="1"/>
+ <button dlgtype="help" class="dialog-button"/>
+ </vbox>
+ </row>
+
+ <row>
+ <separator class="thin"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center">
+ <label id="folderPickerPrefix"
+ value="&folderPickerPrefix.label;"
+ accesskey="&folderPickerPrefix.accesskey;"
+ disabled="true"
+ control="runFiltersFolder"/>
+
+ <menulist id="runFiltersFolder"
+ flex="1"
+ disabled="true"
+ class="folderMenuItem"
+ displayformat="verbose">
+ <menupopup id="runFiltersPopup"
+ class="menulist-menupopup"
+ type="folder"
+ showFileHereLabel="true"
+ showAccountsFileHere="false"
+ oncommand="setRunFolder(event.target._folder);"/>
+ </menulist>
+ <spacer flex="1"/>
+ <button id="runFiltersButton"
+ label="&runFilters.label;"
+ accesskey="&runFilters.accesskey;"
+ runlabel="&runFilters.label;"
+ runaccesskey="&runFilters.accesskey;"
+ stoplabel="&stopFilters.label;"
+ stopaccesskey="&stopFilters.accesskey;"
+ disabled="true"
+ oncommand="runSelectedFilters();"/>
+ </hbox>
+ <vbox>
+ <button label="&viewLogButton.label;"
+ accesskey="&viewLogButton.accesskey;"
+ oncommand="viewLog();"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+
+ <statusbar class="chromeclass-status" id="status-bar">
+ <statusbarpanel class="statusbarpanel-progress">
+ <progressmeter id="statusbar-icon"
+ class="progressmeter-statusbar"
+ mode="normal"
+ value="0"/>
+ </statusbarpanel>
+ <statusbarpanel id="statusText" crop="right" flex="1"/>
+ </statusbar>
+</dialog>
diff --git a/comm/suite/mailnews/content/SearchDialog.js b/comm/suite/mailnews/content/SearchDialog.js
new file mode 100644
index 0000000000..665b12e16b
--- /dev/null
+++ b/comm/suite/mailnews/content/SearchDialog.js
@@ -0,0 +1,729 @@
+/* -*- 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/. */
+
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+
+var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
+var gDBView;
+var gSearchSession;
+var gMsgFolderSelected;
+
+var nsIMsgFolder = Ci.nsIMsgFolder;
+var nsIMsgWindow = Ci.nsIMsgWindow;
+var nsMsgSearchScope = Ci.nsMsgSearchScope;
+
+var gFolderPicker;
+var gStatusBar = null;
+var gStatusFeedback = new nsMsgStatusFeedback();
+var gMessengerBundle = null;
+var RDF;
+var gSearchBundle;
+var gNextMessageViewIndexAfterDelete = -2;
+
+// Datasource search listener -- made global as it has to be registered
+// and unregistered in different functions.
+var gDataSourceSearchListener;
+var gViewSearchListener;
+
+var gSearchStopButton;
+
+// Controller object for search results thread pane
+var nsSearchResultsController =
+{
+ supportsCommand: function(command)
+ {
+ switch(command) {
+ case "cmd_openMessage":
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ case "file_message_button":
+ case "goto_folder_button":
+ case "saveas_vf_button":
+ case "cmd_selectAll":
+ case "cmd_markAsRead":
+ case "cmd_markAsUnread":
+ case "cmd_markAsFlagged":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ // this controller only handles commands
+ // that rely on items being selected in
+ // the search results pane.
+ isCommandEnabled: function(command)
+ {
+ var enabled = true;
+
+ switch (command) {
+ case "goto_folder_button":
+ if (GetNumSelectedMessages() != 1)
+ enabled = false;
+ break;
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ // this assumes that advanced searches don't cross accounts
+ if (GetNumSelectedMessages() <= 0)
+ enabled = false;
+ break;
+ case "saveas_vf_button":
+ // need someway to see if there are any search criteria...
+ return true;
+ case "cmd_selectAll":
+ return GetDBView() != null;
+ default:
+ if (GetNumSelectedMessages() <= 0)
+ enabled = false;
+ break;
+ }
+
+ return enabled;
+ },
+
+ doCommand: function(command)
+ {
+ switch(command) {
+ case "cmd_openMessage":
+ MsgOpenSelectedMessages();
+ return true;
+
+ case "cmd_delete":
+ case "button_delete":
+ MsgDeleteSelectedMessages(nsMsgViewCommandType.deleteMsg);
+ return true;
+ case "cmd_shiftDelete":
+ MsgDeleteSelectedMessages(nsMsgViewCommandType.deleteNoTrash);
+ return true;
+
+ case "goto_folder_button":
+ GoToFolder();
+ return true;
+
+ case "saveas_vf_button":
+ saveAsVirtualFolder();
+ return true;
+
+ case "cmd_selectAll":
+ // move the focus to the search results pane
+ GetThreadTree().focus();
+ GetDBView().doCommand(nsMsgViewCommandType.selectAll)
+ return true;
+
+ case "cmd_markAsRead":
+ MsgMarkMsgAsRead(true);
+ return true;
+
+ case "cmd_markAsUnread":
+ MsgMarkMsgAsRead(false);
+ return true;
+
+ case "cmd_markAsFlagged":
+ MsgMarkAsFlagged();
+ return true;
+
+ default:
+ return false;
+ }
+
+ },
+
+ onEvent: function(event)
+ {
+ }
+}
+
+function UpdateMailSearch(caller)
+{
+ //dump("XXX update mail-search " + caller + "\n");
+ document.commandDispatcher.updateCommands('mail-search');
+}
+
+function SetAdvancedSearchStatusText(aNumHits)
+{
+ var statusMsg;
+ // if there are no hits, it means no matches were found in the search.
+ if (aNumHits == 0)
+ {
+ statusMsg = gSearchBundle.getString("noMatchesFound");
+ }
+ else
+ {
+ statusMsg = PluralForm.get(aNumHits,
+ gSearchBundle.getString("matchesFound"));
+ statusMsg = statusMsg.replace("#1", aNumHits);
+ }
+ gStatusFeedback.showStatusString(statusMsg);
+}
+
+// nsIMsgSearchNotify object
+var gSearchNotificationListener =
+{
+ onSearchHit: function(header, folder)
+ {
+ // XXX TODO
+ // update status text?
+ },
+
+ onSearchDone: function(status)
+ {
+ gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
+ gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
+ gStatusFeedback._stopMeteors();
+ SetAdvancedSearchStatusText(gDBView.QueryInterface(Ci.nsITreeView).rowCount);
+ },
+
+ onNewSearch: function()
+ {
+ gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForStopButton"));
+ gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForStopButton.accesskey"));
+ UpdateMailSearch("new-search");
+ gStatusFeedback._startMeteors();
+ gStatusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
+ }
+}
+
+// the folderListener object
+var gFolderListener = {
+ onFolderAdded: function(parentFolder, child) {},
+ onMessageAdded: function(parentFolder, msg) {},
+ onFolderRemoved: function(parentFolder, child) {},
+ onMessageRemoved: function(parentFolder, msg) {},
+
+ onFolderPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ onFolderIntPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ onFolderBoolPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ onFolderUnicharPropertyChanged: function(item, property, oldValue, newValue){},
+ onFolderPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+
+ onFolderEvent: function(folder, event) {
+ if (event == "DeleteOrMoveMsgCompleted") {
+ HandleDeleteOrMoveMessageCompleted(folder);
+ }
+ else if (event == "DeleteOrMoveMsgFailed") {
+ HandleDeleteOrMoveMessageFailed(folder);
+ }
+ }
+}
+
+function HideSearchColumn(id)
+{
+ var col = document.getElementById(id);
+ if (col) {
+ col.setAttribute("hidden","true");
+ col.setAttribute("ignoreincolumnpicker","true");
+ }
+}
+
+function ShowSearchColumn(id)
+{
+ var col = document.getElementById(id);
+ if (col) {
+ col.removeAttribute("hidden");
+ col.removeAttribute("ignoreincolumnpicker");
+ }
+}
+
+function searchOnLoad()
+{
+ setHelpFileURI("chrome://communicator/locale/help/suitehelp.rdf");
+ initializeSearchWidgets();
+ initializeSearchWindowWidgets();
+ messenger = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger);
+
+ gSearchBundle = document.getElementById("bundle_search");
+ gSearchStopButton.setAttribute("label", gSearchBundle.getString("labelForSearchButton"));
+ gSearchStopButton.setAttribute("accesskey", gSearchBundle.getString("labelForSearchButton.accesskey"));
+ gMessengerBundle = document.getElementById("bundle_messenger");
+ setupDatasource();
+ setupSearchListener();
+
+ if (window.arguments && window.arguments[0])
+ selectFolder(window.arguments[0].folder);
+
+ onMore(null);
+ UpdateMailSearch("onload");
+
+ // hide and remove these columns from the column picker. you can't thread search results
+ HideSearchColumn("threadCol"); // since you can't thread search results
+ HideSearchColumn("totalCol"); // since you can't thread search results
+ HideSearchColumn("unreadCol"); // since you can't thread search results
+ HideSearchColumn("unreadButtonColHeader");
+ HideSearchColumn("idCol");
+ HideSearchColumn("junkStatusCol");
+ HideSearchColumn("accountCol");
+
+ // we want to show the location column for search
+ ShowSearchColumn("locationCol");
+}
+
+function searchOnUnload()
+{
+ // unregister listeners
+ gSearchSession.unregisterListener(gViewSearchListener);
+ gSearchSession.unregisterListener(gSearchNotificationListener);
+
+ MailServices.mailSession.RemoveFolderListener(gFolderListener);
+
+ if (gDBView)
+ {
+ gDBView.close();
+ gDBView = null;
+ }
+
+ top.controllers.removeController(nsSearchResultsController);
+
+ // release this early because msgWindow holds a weak reference
+ msgWindow.rootDocShell = null;
+}
+
+function initializeSearchWindowWidgets()
+{
+ gFolderPicker = document.getElementById("searchableFolders");
+ gSearchStopButton = document.getElementById("search-button");
+ gStatusBar = document.getElementById('statusbar-icon');
+ hideMatchAllItem();
+
+ msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(nsIMsgWindow);
+ msgWindow.domWindow = window;
+ msgWindow.rootDocShell.allowAuth = true;
+ msgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+ msgWindow.statusFeedback = gStatusFeedback;
+
+ // functionality to enable/disable buttons using nsSearchResultsController
+ // depending of whether items are selected in the search results thread pane.
+ top.controllers.insertControllerAt(0, nsSearchResultsController);
+}
+
+
+function onSearchStop() {
+ gSearchSession.interruptSearch();
+}
+
+function onResetSearch(event) {
+ onReset(event);
+
+ var tree = GetThreadTree();
+ tree.treeBoxObject.view = null;
+ gStatusFeedback.showStatusString("");
+}
+
+function selectFolder(folder)
+{
+ var folderURI;
+
+ // if we can't search messages on this folder, just select the first one
+ if (!folder || !folder.server.canSearchMessages ||
+ (folder.flags & Ci.nsMsgFolderFlags.Virtual)) {
+ // find first item in our folder picker menu list
+ folderURI = gFolderPicker.firstChild.tree.builderView.getResourceAtIndex(0).Value;
+ } else {
+ folderURI = folder.URI;
+ }
+ updateSearchFolderPicker(folderURI);
+}
+
+function updateSearchFolderPicker(folderURI)
+{
+ SetFolderPicker(folderURI, gFolderPicker.id);
+
+ // use the URI to get the real folder
+ gMsgFolderSelected = MailUtils.getFolderForURI(folderURI);
+
+ var searchSubFolders = document.getElementById("checkSearchSubFolders");
+ if (searchSubFolders)
+ searchSubFolders.disabled = !gMsgFolderSelected.hasSubFolders;
+ var searchLocalSystem = document.getElementById("menuSearchLocalSystem");
+ if (searchLocalSystem)
+ searchLocalSystem.disabled = gMsgFolderSelected.server.searchScope == nsMsgSearchScope.offlineMail;
+ setSearchScope(GetScopeForFolder(gMsgFolderSelected));
+}
+
+function updateSearchLocalSystem()
+{
+ setSearchScope(GetScopeForFolder(gMsgFolderSelected));
+}
+
+function UpdateAfterCustomHeaderChange()
+{
+ updateSearchAttributes();
+}
+
+function onChooseFolder(event) {
+ var folderURI = event.id;
+ if (folderURI) {
+ updateSearchFolderPicker(folderURI);
+ }
+}
+
+function onEnterInSearchTerm()
+{
+ // on enter
+ // if not searching, start the search
+ // if searching, stop and then start again
+ if (gSearchStopButton.getAttribute("label") == gSearchBundle.getString("labelForSearchButton")) {
+ onSearch();
+ }
+ else {
+ onSearchStop();
+ onSearch();
+ }
+}
+
+function onSearch()
+{
+ // set the view. do this on every search, to
+ // allow the tree to reset itself
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ if (treeView)
+ {
+ var tree = GetThreadTree();
+ tree.treeBoxObject.view = treeView;
+ }
+
+ gSearchSession.clearScopes();
+ // tell the search session what the new scope is
+ if (!gMsgFolderSelected.isServer && !gMsgFolderSelected.noSelect)
+ gSearchSession.addScopeTerm(GetScopeForFolder(gMsgFolderSelected),
+ gMsgFolderSelected);
+
+ var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
+ if (gMsgFolderSelected && (searchSubfolders || gMsgFolderSelected.isServer || gMsgFolderSelected.noSelect))
+ {
+ AddSubFolders(gMsgFolderSelected);
+ }
+ // reflect the search widgets back into the search session
+ gSearchSession.searchTerms = saveSearchTerms(gSearchSession.searchTerms, gSearchSession);
+
+ try
+ {
+ gSearchSession.search(msgWindow);
+ }
+ catch(ex)
+ {
+ dump("Search Exception\n");
+ }
+ // refresh the tree after the search starts, because initiating the
+ // search will cause the datasource to clear itself
+}
+
+function AddSubFolders(folder) {
+ for (let nextFolder of folder.subFolders) {
+ if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual))
+ {
+ if (!nextFolder.noSelect)
+ gSearchSession.addScopeTerm(GetScopeForFolder(nextFolder), nextFolder);
+
+ AddSubFolders(nextFolder);
+ }
+ }
+}
+
+function AddSubFoldersToURI(folder)
+{
+ var returnString = "";
+
+ for (let nextFolder of folder.subFolders) {
+ {
+ if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual))
+ {
+ if (!nextFolder.noSelect && !nextFolder.isServer)
+ {
+ if (returnString.length > 0)
+ returnString += '|';
+ returnString += nextFolder.URI;
+ }
+ var subFoldersString = AddSubFoldersToURI(nextFolder);
+ if (subFoldersString.length > 0)
+ {
+ if (returnString.length > 0)
+ returnString += '|';
+ returnString += subFoldersString;
+ }
+ }
+ }
+ return returnString;
+}
+
+
+function GetScopeForFolder(folder)
+{
+ var searchLocalSystem = document.getElementById("menuSearchLocalSystem");
+ return searchLocalSystem && searchLocalSystem.value == "local" ?
+ nsMsgSearchScope.offlineMail :
+ folder.server.searchScope;
+}
+
+var nsMsgViewSortType = Ci.nsMsgViewSortType;
+var nsMsgViewSortOrder = Ci.nsMsgViewSortOrder;
+var nsMsgViewFlagsType = Ci.nsMsgViewFlagsType;
+var nsMsgViewCommandType = Ci.nsMsgViewCommandType;
+
+function goUpdateSearchItems(commandset)
+{
+ for (var i = 0; i < commandset.childNodes.length; i++)
+ {
+ var commandID = commandset.childNodes[i].getAttribute("id");
+ if (commandID)
+ {
+ goUpdateCommand(commandID);
+ }
+ }
+}
+
+function nsMsgSearchCommandUpdater()
+{}
+
+nsMsgSearchCommandUpdater.prototype =
+{
+ updateCommandStatus : function()
+ {
+ // the back end is smart and is only telling us to update command status
+ // when the # of items in the selection has actually changed.
+ document.commandDispatcher.updateCommands('mail-search');
+ },
+ displayMessageChanged : function(aFolder, aSubject, aKeywords)
+ {
+ },
+
+ updateNextMessageAfterDelete : function()
+ {
+ SetNextMessageAfterDelete();
+ },
+
+ summarizeSelection: function() {return false},
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIMsgDBViewCommandUpdater) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ }
+}
+
+function setupDatasource() {
+ gDBView = Cc["@mozilla.org/messenger/msgdbview;1?type=search"]
+ .createInstance(Ci.nsIMsgDBView);
+ var count = new Object;
+ var cmdupdator = new nsMsgSearchCommandUpdater();
+
+ gDBView.init(messenger, msgWindow, cmdupdator);
+ gDBView.open(null, nsMsgViewSortType.byId, nsMsgViewSortOrder.ascending, nsMsgViewFlagsType.kNone, count);
+
+ // the thread pane needs to use the search datasource (to get the
+ // actual list of messages) and the message datasource (to get any
+ // attributes about each message)
+ gSearchSession = Cc[searchSessionContractID].createInstance(Ci.nsIMsgSearchSession);
+
+ var nsIFolderListener = Ci.nsIFolderListener;
+ var notifyFlags = nsIFolderListener.event;
+ MailServices.mailSession.AddFolderListener(gFolderListener, notifyFlags);
+
+ // the datasource is a listener on the search results
+ gViewSearchListener = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
+ gSearchSession.registerListener(gViewSearchListener);
+}
+
+
+function setupSearchListener()
+{
+ // Setup the javascript object as a listener on the search results
+ gSearchSession.registerListener(gSearchNotificationListener);
+}
+
+// used to toggle functionality for Search/Stop button.
+function onSearchButton(event)
+{
+ if (event.target.label == gSearchBundle.getString("labelForSearchButton"))
+ onSearch();
+ else
+ onSearchStop();
+}
+
+// Stuff after this is implemented to make the thread pane work.
+function GetNumSelectedMessages()
+{
+ try {
+ return gDBView.numSelected;
+ }
+ catch (ex) {
+ return 0;
+ }
+}
+
+function GetDBView()
+{
+ return gDBView;
+}
+
+function MsgDeleteSelectedMessages(aCommandType)
+{
+ SetNextMessageAfterDelete();
+ gDBView.doCommand(aCommandType);
+}
+
+function SetNextMessageAfterDelete()
+{
+ gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
+}
+
+function HandleDeleteOrMoveMessageFailed(folder)
+{
+ gDBView.onDeleteCompleted(false);
+ gNextMessageViewIndexAfterDelete = -2;
+}
+
+function HandleDeleteOrMoveMessageCompleted(folder)
+{
+ gDBView.onDeleteCompleted(true);
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ var treeSelection = treeView.selection;
+ var viewSize = treeView.rowCount;
+
+ if (gNextMessageViewIndexAfterDelete == -2) {
+ // a move or delete can cause our selection can change underneath us.
+ // this can happen when the user
+ // deletes message from the stand alone msg window
+ // or the three pane
+ if (!treeSelection) {
+ // this can happen if you open the search window
+ // and before you do any searches
+ // and you do delete from another mail window
+ return;
+ }
+ else if (treeSelection.count == 0) {
+ // this can happen if you double clicked a message
+ // in the thread pane, and deleted it from the stand alone msg window
+ // see bug #185147
+ treeSelection.clearSelection();
+
+ UpdateMailSearch("delete from another view, 0 rows now selected");
+ }
+ else if (treeSelection.count == 1) {
+ // this can happen if you had two messages selected
+ // in the search results pane, and you deleted one of them from another view
+ // (like the view in the stand alone msg window or the three pane)
+ // since one item is selected, we should load it.
+ var startIndex = {};
+ var endIndex = {};
+ treeSelection.getRangeAt(0, startIndex, endIndex);
+
+ // select the selected item, so we'll load it
+ treeSelection.select(startIndex.value);
+ treeView.selectionChanged();
+
+ EnsureRowInThreadTreeIsVisible(startIndex.value);
+ UpdateMailSearch("delete from another view, 1 row now selected");
+ }
+ else {
+ // this can happen if you have more than 2 messages selected
+ // in the search results pane, and you deleted one of them from another view
+ // (like the view in the stand alone msg window or the three pane)
+ // since multiple messages are still selected, do nothing.
+ }
+ }
+ else {
+ if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None && gNextMessageViewIndexAfterDelete >= viewSize)
+ {
+ if (viewSize > 0)
+ gNextMessageViewIndexAfterDelete = viewSize - 1;
+ else
+ {
+ gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
+
+ // there is nothing to select since viewSize is 0
+ treeSelection.clearSelection();
+
+ UpdateMailSearch("delete from current view, 0 rows left");
+ }
+ }
+
+ // if we are about to set the selection with a new element then DON'T clear
+ // the selection then add the next message to select. This just generates
+ // an extra round of command updating notifications that we are trying to
+ // optimize away.
+ if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
+ {
+ treeSelection.select(gNextMessageViewIndexAfterDelete);
+ // since gNextMessageViewIndexAfterDelete probably has the same value
+ // as the last index we had selected, the tree isn't generating a new
+ // selectionChanged notification for the tree view. So we aren't loading the
+ // next message. to fix this, force the selection changed update.
+ if (treeView)
+ treeView.selectionChanged();
+
+ EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
+
+ // XXX TODO
+ // I think there is a bug in the suppression code above.
+ // what if I have two rows selected, and I hit delete,
+ // and so we load the next row.
+ // what if I have commands that only enable where
+ // exactly one row is selected?
+ UpdateMailSearch("delete from current view, at least one row selected");
+ }
+ }
+
+ // default value after delete/move/copy is over
+ gNextMessageViewIndexAfterDelete = -2;
+
+ // something might have been deleted, so update the status text
+ SetAdvancedSearchStatusText(viewSize);
+}
+
+function MoveMessageInSearch(destFolder)
+{
+ if (destFolder._folder)
+ {
+ try {
+ SetNextMessageAfterDelete();
+ gDBView.doCommandWithFolder(nsMsgViewCommandType.moveMessages,
+ destFolder._folder);
+ }
+ catch (ex) {
+ dump("MoveMessageInSearch failed: " + ex + "\n");
+ }
+ }
+}
+
+function GoToFolder()
+{
+ var hdr = gDBView.hdrForFirstSelectedMessage;
+ MsgOpenNewWindowForFolder(hdr.folder.URI, hdr.messageKey);
+}
+
+function saveAsVirtualFolder()
+{
+ let searchFolderURIs = window.arguments[0].folder.URI;
+
+ var searchSubfolders = document.getElementById("checkSearchSubFolders").checked;
+ if (gMsgFolderSelected && (searchSubfolders || gMsgFolderSelected.isServer || gMsgFolderSelected.noSelect))
+ {
+ var subFolderURIs = AddSubFoldersToURI(gMsgFolderSelected);
+ if (subFolderURIs.length > 0)
+ searchFolderURIs += '|' + subFolderURIs;
+ }
+
+ var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
+ "chrome,titlebar,modal,centerscreen",
+ {folder:window.arguments[0].folder,
+ searchTerms:gSearchSession.searchTerms,
+ searchFolderURIs: searchFolderURIs});
+}
+
+function OnTagsChange()
+{
+ // Dummy, called by RemoveAllMessageTags and ToggleMessageTag
+}
diff --git a/comm/suite/mailnews/content/SearchDialog.xul b/comm/suite/mailnews/content/SearchDialog.xul
new file mode 100644
index 0000000000..65fabb731b
--- /dev/null
+++ b/comm/suite/mailnews/content/SearchDialog.xul
@@ -0,0 +1,178 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+<?xml-stylesheet href="chrome://messenger/skin/searchDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/threadPane.xul"?>
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/mailKeysOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/SearchDialog.dtd">
+
+<dialog id="searchMailWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="mailnews:search"
+ title="&searchDialogTitle.label;"
+ style="width: 52em; height: 34em;"
+ persist="screenX screenY width height sizemode"
+ buttons="help"
+ ondialoghelp="return openHelp('search_messages');"
+ ondialogaccept="return false; /* allow Search on Enter */"
+ onload="searchOnLoad();"
+ onunload="onSearchStop(); searchOnUnload();">
+
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+
+ <script src="chrome://messenger/content/mailWindow.js"/>
+ <script src="chrome://messenger/content/msgMail3PaneWindow.js"/>
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://messenger/content/mailCommands.js"/>
+ <script src="chrome://messenger/content/mailWindowOverlay.js"/>
+ <script src="chrome://messenger/content/commandglue.js"/>
+ <script src="chrome://messenger/content/SearchDialog.js"/>
+ <script src="chrome://messenger/content/msgFolderPickerOverlay.js"/>
+ <script src="chrome://messenger/content/tabmail.js"/>
+ <script src="chrome://messenger/content/folderDisplay.js"/>
+ <script src="chrome://global/content/contentAreaUtils.js"/>
+ <script src="chrome://messenger-newsblog/content/newsblogOverlay.js"/>
+
+ <commands id="commands">
+ <commandset id="mailSearchItems"
+ commandupdater="true"
+ events="mail-search"
+ oncommandupdate="goUpdateSearchItems(this)">
+ <command id="cmd_openMessage" oncommand="goDoCommand('cmd_openMessage');" disabled="true"/>
+ <command id="button_delete" oncommand="goDoCommand('button_delete')" disabled="true"/>
+ <command id="goto_folder_button" oncommand="goDoCommand('goto_folder_button')" disabled="true"/>
+ <command id="saveas_vf_button" oncommand="goDoCommand('saveas_vf_button')" disabled="false"/>
+ <command id="file_message_button"/>
+ <command id="cmd_delete"/>
+ <command id="cmd_shiftDelete" oncommand="goDoCommand('cmd_shiftDelete');"/>
+ </commandset>
+ </commands>
+
+ <keyset id="mailKeys"/>
+
+ <broadcasterset id="mailBroadcasters">
+ <broadcaster id="Communicator:WorkMode"/>
+ </broadcasterset>
+
+ <dummy class="usesMailWidgets"/>
+
+ <vbox id="searchTerms" flex="3" persist="height">
+ <vbox>
+ <hbox align="center">
+ <label value="&searchHeading.label;" accesskey="&searchHeading.accesskey;"
+ control="searchableFolders"/>
+ <menulist id="searchableFolders" flex="2"
+ class="folderMenuItem"
+ displayformat="verbose">
+ <menupopup class="menulist-menupopup"
+ type="folder"
+ mode="search"
+ showAccountsFileHere="true"
+ showFileHereLabel="true"
+ oncommand="updateSearchFolderPicker(event.target.id);"/>
+ </menulist>
+ <checkbox id="checkSearchSubFolders"
+ label="&searchSubfolders.label;"
+ checked="true"
+ accesskey="&searchSubfolders.accesskey;"/>
+ <spacer flex="3"/>
+ <button id="search-button" oncommand="onSearchButton(event);" default="true"/>
+ </hbox>
+ <hbox align="center">
+ <label id="searchOnHeading"
+ value="&searchOnHeading.label;"
+ accesskey="&searchOnHeading.accesskey;"
+ control="menuSearchLocalSystem">
+ <observes element="menuSearchLocalSystem"
+ attribute="disabled"/>
+ </label>
+ <menulist id="menuSearchLocalSystem"
+ persist="value"
+ oncommand="updateSearchLocalSystem();">
+ <menupopup>
+ <menuitem id="menuOnRemote"
+ value="remote"
+ label="&searchOnRemote.label;"/>
+ <menuitem id="menuOnLocal"
+ value="local"
+ label="&searchOnLocal.label;"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <button label="&resetButton.label;" oncommand="onResetSearch(event);" accesskey="&resetButton.accesskey;"/>
+ </hbox>
+ </vbox>
+
+ <hbox flex="1">
+ <vbox id="searchTermListBox" flex="1"/>
+ </hbox>
+ </vbox>
+
+ <splitter id="gray_horizontal_splitter" persist="state">
+ <grippy/>
+ </splitter>
+
+ <vbox id="searchResults" flex="4" persist="height">
+ <vbox id="searchResultListBox" flex="1">
+ <tree id="threadTree"/>
+ </vbox>
+ <hbox align="center">
+
+ <button id="openButton"
+ label="&openButton.label;"
+ command="cmd_openMessage"
+ accesskey="&openButton.accesskey;"/>
+ <button id="fileMessageButton"
+ type="menu"
+ label="&moveButton.label;"
+ accesskey="&moveButton.accesskey;"
+ observes="file_message_button"
+ oncommand="MoveMessageInSearch(event.target);">
+ <menupopup type="folder"
+ showFileHereLabel="true"
+ mode="filing"
+ fileHereLabel="&moveHereMenu.label;"
+ fileHereAccessKey="&moveHereMenu.accesskey;"/>
+ </button>
+
+ <button id="deleteButton"
+ label="&deleteButton.label;"
+ accesskey="&deleteButton.accesskey;"
+ command="button_delete"/>
+ <button id="goToFolderButton"
+ label="&goToFolderButton.label;"
+ accesskey="&goToFolderButton.accesskey;"
+ command="goto_folder_button"/>
+ <button id="saveAsVFButton"
+ label="&saveAsVFButton.label;"
+ accesskey="&saveAsVFButton.accesskey;"
+ command="saveas_vf_button"/>
+ <spacer flex="1"/>
+ <button dlgtype="help" class="dialog-button"/>
+ </hbox>
+ </vbox>
+
+ <statusbar id="status-bar" class="chromeclass-status">
+ <statusbarpanel id="statusbar-progresspanel"
+ class="statusbarpanel-progress"
+ collapsed="true">
+ <progressmeter id="statusbar-icon"
+ class="progressmeter-statusbar"
+ mode="normal"
+ value="0"/>
+ </statusbarpanel>
+ <statusbarpanel id="statusText" crop="right" flex="1"/>
+ <statusbarpanel id="offline-status" class="statusbarpanel-iconic"/>
+ </statusbar>
+
+</dialog>
diff --git a/comm/suite/mailnews/content/browserRequest.js b/comm/suite/mailnews/content/browserRequest.js
new file mode 100644
index 0000000000..56ff0f8b9e
--- /dev/null
+++ b/comm/suite/mailnews/content/browserRequest.js
@@ -0,0 +1,107 @@
+/* -*- 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/. */
+
+const wpl = Ci.nsIWebProgressListener;
+
+var reporterListener = {
+ _isBusy: false,
+ get securityButton() {
+ delete this.securityButton;
+ return this.securityButton = document.getElementById("security-button");
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ onStateChange: function(/*in nsIWebProgress*/ aWebProgress,
+ /*in nsIRequest*/ aRequest,
+ /*in unsigned long*/ aStateFlags,
+ /*in nsresult*/ aStatus) {
+ },
+
+ onProgressChange: function(/*in nsIWebProgress*/ aWebProgress,
+ /*in nsIRequest*/ aRequest,
+ /*in long*/ aCurSelfProgress,
+ /*in long */aMaxSelfProgress,
+ /*in long */aCurTotalProgress,
+ /*in long */aMaxTotalProgress) {
+ },
+
+ onLocationChange: function(/*in nsIWebProgress*/ aWebProgress,
+ /*in nsIRequest*/ aRequest,
+ /*in nsIURI*/ aLocation) {
+ document.getElementById("headerMessage").textContent = aLocation.spec;
+ },
+
+ onStatusChange: function(/*in nsIWebProgress*/ aWebProgress,
+ /*in nsIRequest*/ aRequest,
+ /*in nsresult*/ aStatus,
+ /*in wstring*/ aMessage) {
+ },
+
+ onSecurityChange: function(/*in nsIWebProgress*/ aWebProgress,
+ /*in nsIRequest*/ aRequest,
+ /*in unsigned long*/ aState) {
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE;
+ var browser = document.getElementById("requestFrame");
+ var level;
+
+ switch (aState & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE:
+ level = "high";
+ break;
+ case wpl.STATE_IS_BROKEN:
+ level = "broken";
+ break;
+ }
+ if (level) {
+ this.securityButton.setAttribute("level", level);
+ this.securityButton.hidden = false;
+ } else {
+ this.securityButton.hidden = true;
+ this.securityButton.removeAttribute("level");
+ }
+ this.securityButton.setAttribute("tooltiptext",
+ browser.securityUI.tooltipText);
+ }
+}
+
+function cancelRequest()
+{
+ reportUserClosed();
+ window.close();
+}
+
+function reportUserClosed()
+{
+ let request = window.arguments[0].wrappedJSObject;
+ request.cancelled();
+}
+
+function loadRequestedUrl()
+{
+ let request = window.arguments[0].wrappedJSObject;
+ document.getElementById("headerMessage").textContent = request.promptText;
+ let account = request.account;
+ if (request.iconURI != "")
+ document.getElementById("headerImage").src = request.iconURI;
+
+ var browser = document.getElementById("requestFrame");
+ browser.addProgressListener(reporterListener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ var url = request.url;
+ if (url != "") {
+ browser.setAttribute("src", url);
+ document.getElementById("headerMessage").textContent = url;
+ }
+ request.loaded(window, browser.webProgress);
+}
diff --git a/comm/suite/mailnews/content/browserRequest.xul b/comm/suite/mailnews/content/browserRequest.xul
new file mode 100644
index 0000000000..9911601f86
--- /dev/null
+++ b/comm/suite/mailnews/content/browserRequest.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!--# 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/.
+ -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/browserRequest.css" type="text/css"?>
+
+<!DOCTYPE window>
+<window id="browserRequest"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons=","
+ onload="loadRequestedUrl()"
+ onclose="reportUserClosed()"
+ title=""
+ width="800"
+ height="500"
+ orient="vertical">
+
+ <script src="chrome://messenger/content/browserRequest.js"/>
+
+ <keyset id="mainKeyset">
+ <key id="key_close" key="w" modifiers="accel" oncommand="cancelRequest()"/>
+ <key id="key_close2" keycode="VK_ESCAPE" oncommand="cancelRequest()"/>
+ </keyset>
+ <hbox id="header">
+ <hbox id="addressbox" flex="1" disabled="true">
+ <image id="security-button"/>
+ <description id="headerMessage"/>
+ </hbox>
+ </hbox>
+ <browser type="content" src="about:blank" id="requestFrame" flex="1"/>
+</window>
diff --git a/comm/suite/mailnews/content/commandglue.js b/comm/suite/mailnews/content/commandglue.js
new file mode 100644
index 0000000000..a9a9332c64
--- /dev/null
+++ b/comm/suite/mailnews/content/commandglue.js
@@ -0,0 +1,989 @@
+/* -*- 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/. */
+
+/*
+ * Command-specific code. This stuff should be called by the widgets
+ */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+//NOTE: gMessengerBundle and gBrandBundle must be defined and set
+// for this Overlay to work properly
+
+var gFolderJustSwitched = false;
+var gBeforeFolderLoadTime;
+var gVirtualFolderTerms;
+var gXFVirtualFolderTerms;
+var gCurrentVirtualFolderUri;
+var gPrevFolderFlags;
+var gPrevSelectedFolder;
+var gMsgFolderSelected;
+
+function setTitleFromFolder(msgfolder, subject)
+{
+ var title = subject || "";
+
+ if (msgfolder)
+ {
+ if (title)
+ title += " - ";
+
+ title += msgfolder.prettyName;
+
+ if (!msgfolder.isServer)
+ {
+ var server = msgfolder.server;
+ var middle;
+ var end;
+ if (server.type == "nntp") {
+ // <folder> on <hostname>
+ middle = gMessengerBundle.getString("titleNewsPreHost");
+ end = server.hostName;
+ } else {
+ // <folder> for <accountname>
+ middle = gMessengerBundle.getString("titleMailPreHost");
+ end = server.prettyName;
+ }
+ if (middle) title += " " + middle;
+ if (end) title += " " + end;
+ }
+ }
+
+ if (AppConstants.platform != "macosx") {
+ title += " - " + gBrandBundle.getString("brandShortName");
+ }
+
+ document.title = title;
+
+ // Notify the current tab, it might want to update also.
+ var tabmail = GetTabMail();
+ if (tabmail)
+ {
+ tabmail.saveCurrentTabState(); // gDBView may have changed!
+ tabmail.setTabTitle();
+ }
+}
+
+function UpdateMailToolbar(caller)
+{
+ //dump("XXX update mail-toolbar " + caller + "\n");
+ document.commandDispatcher.updateCommands('mail-toolbar');
+
+ // hook for extra toolbar items
+ Services.obs.notifyObservers(window, "mail:updateToolbarItems");
+}
+
+/**
+ * @param folder - If viewFolder is a single folder saved
+ - search, this folder is the scope of the
+ - saved search, the real, underlying folder.
+ - Otherwise, it's the same as the viewFolder.
+ * @param viewFolder - nsIMsgFolder selected in the folder pane.
+ - Will be the same as folder, except if
+ - it's a single folder saved search.
+ * @param viewType - nsMsgViewType (see nsIMsgDBView.idl)
+ * @param viewFlags - nsMsgViewFlagsType (see nsIMsgDBView.idl)
+ * @param sortType - nsMsgViewSortType (see nsIMsgDBView.idl)
+ * @param sortOrder - nsMsgViewSortOrder (see nsIMsgDBView.idl)
+ **/
+function ChangeFolder(folder, viewFolder, viewType, viewFlags, sortType, sortOrder)
+{
+ if (folder.URI == gCurrentLoadingFolderURI)
+ return;
+
+ SetUpToolbarButtons(folder.URI);
+
+ // hook for extra toolbar items
+ Services.obs.notifyObservers(window, "mail:setupToolbarItems", folder.URI);
+
+ try {
+ setTitleFromFolder(viewFolder, null);
+ } catch (ex) {
+ dump("error setting title: " + ex + "\n");
+ }
+
+ //if it's a server, clear the threadpane and don't bother trying to load.
+ if (folder.isServer) {
+ msgWindow.openFolder = null;
+
+ ClearThreadPane();
+
+ // Load AccountCentral page here.
+ ShowAccountCentral(folder);
+
+ return;
+ }
+ else
+ {
+ if (folder.server.displayStartupPage)
+ {
+ gDisplayStartupPage = true;
+ folder.server.displayStartupPage = false;
+ }
+ }
+
+ // If the user clicks on folder, time to display thread pane and message pane.
+ ShowThreadPane();
+
+ gCurrentLoadingFolderURI = folder.URI;
+ gNextMessageAfterDelete = null; // forget what message to select, if any
+
+ gCurrentFolderToReroot = folder.URI;
+ gCurrentLoadingFolderViewFlags = viewFlags;
+ gCurrentLoadingFolderViewType = viewType;
+ gCurrentLoadingFolderSortType = sortType;
+ gCurrentLoadingFolderSortOrder = sortOrder;
+
+ var showMessagesAfterLoading;
+ try {
+ let server = folder.server;
+ if (Services.prefs.getBoolPref("mail.password_protect_local_cache"))
+ {
+ showMessagesAfterLoading = server.passwordPromptRequired;
+ // servers w/o passwords (like local mail) will always be non-authenticated.
+ // So we need to use the account manager for that case.
+ }
+ else
+ showMessagesAfterLoading = false;
+ }
+ catch (ex) {
+ showMessagesAfterLoading = false;
+ }
+
+ if (viewType != nsMsgViewType.eShowVirtualFolderResults &&
+ (folder.manyHeadersToDownload || showMessagesAfterLoading))
+ {
+ gRerootOnFolderLoad = true;
+ try
+ {
+ ClearThreadPane();
+ SetBusyCursor(window, true);
+ folder.startFolderLoading();
+ folder.updateFolder(msgWindow);
+ }
+ catch(ex)
+ {
+ SetBusyCursor(window, false);
+ dump("Error loading with many headers to download: " + ex + "\n");
+ }
+ }
+ else
+ {
+ if (viewType != nsMsgViewType.eShowVirtualFolderResults)
+ SetBusyCursor(window, true);
+ RerootFolder(folder.URI, folder, viewType, viewFlags, sortType, sortOrder);
+ gRerootOnFolderLoad = false;
+ folder.startFolderLoading();
+
+ //Need to do this after rerooting folder. Otherwise possibility of receiving folder loaded
+ //notification before folder has actually changed.
+ if (viewType != nsMsgViewType.eShowVirtualFolderResults)
+ folder.updateFolder(msgWindow);
+ }
+}
+
+function isNewsURI(uri)
+{
+ return ((/^news-message:/.test(uri)) || (/^news:/.test(uri)));
+}
+
+function RerootFolder(uri, newFolder, viewType, viewFlags, sortType, sortOrder)
+{
+ viewDebug("In reroot folder, sortType = " + sortType + "viewType = " + viewType + "\n");
+ if (sortType == 0)
+ {
+ try
+ {
+ var dbFolderInfo = newFolder.msgDatabase.dBFolderInfo;
+ sortType = dbFolderInfo.sortType;
+ sortOrder = dbFolderInfo.sortOrder;
+ viewFlags = dbFolderInfo.viewFlags;
+ viewType = dbFolderInfo.viewType;
+ dbFolderInfo = null;
+ }
+ catch(ex)
+ {
+ dump("invalid db in RerootFolder: " + ex + "\n");
+ }
+ }
+
+ // workaround for #39655
+ gFolderJustSwitched = true;
+
+ ClearThreadPaneSelection();
+
+ //Clear the new messages of the old folder
+ var oldFolder = gPrevSelectedFolder;
+ if (oldFolder) {
+ oldFolder.clearNewMessages();
+ oldFolder.hasNewMessages = false;
+ }
+
+ //Set the window's new open folder.
+ msgWindow.openFolder = newFolder;
+
+ //the new folder being selected should have its biff state get cleared.
+ if(newFolder)
+ {
+ newFolder.biffState =
+ Ci.nsIMsgFolder.nsMsgBiffState_NoMail;
+ }
+
+ //Clear out the thread pane so that we can sort it with the new sort id without taking any time.
+ // folder.setAttribute('ref', "");
+
+ // null this out, so we don't try sort.
+ if (gDBView) {
+ gDBView.close();
+ gDBView = null;
+ }
+
+ // cancel the pending mark as read timer
+ ClearPendingReadTimer();
+
+ // If this is the sent, drafts, templates, or send later folder,
+ // we show "Recipient" instead of "Author".
+ let outgoingFlags = Ci.nsMsgFolderFlags.SentMail |
+ Ci.nsMsgFolderFlags.Drafts |
+ Ci.nsMsgFolderFlags.Templates |
+ Ci.nsMsgFolderFlags.Queue;
+ SetSentFolderColumns(newFolder.isSpecialFolder(outgoingFlags, true));
+ ShowLocationColumn(viewType == nsMsgViewType.eShowVirtualFolderResults);
+ // Only show 'Received' column for e-mails. For newsgroup messages, the 'Date' header is as reliable as an e-mail's
+ // 'Received' header, as it is replaced with the news server's (more reliable) date.
+ UpdateReceivedColumn(newFolder);
+
+ // now create the db view, which will sort it.
+ CreateDBView(newFolder, viewType, viewFlags, sortType, sortOrder);
+ if (oldFolder)
+ {
+ /*disable quick search clear button if we were in the search view on folder switching*/
+ disableQuickSearchClearButton();
+
+ /*we don't null out the db reference for inbox because inbox is like the "main" folder
+ and performance outweighs footprint */
+ if (!oldFolder.isSpecialFolder(Ci.nsMsgFolderFlags.Inbox, false))
+ if (oldFolder.URI != newFolder.URI)
+ oldFolder.msgDatabase = null;
+ }
+ // that should have initialized gDBView, now re-root the thread pane
+ RerootThreadPane();
+
+ UpdateStatusMessageCounts(gMsgFolderSelected);
+
+ UpdateMailToolbar("reroot folder in 3 pane");
+ // hook for extra toolbar items
+ Services.obs.notifyObservers(window, "mail:updateToolbarItems");
+ // this is to kick off cross-folder searches for virtual folders.
+ if (gSearchSession && !gVirtualFolderTerms) // another var might be better...
+ {
+ viewDebug("doing a xf folder search in rerootFolder\n");
+ gCurrentLoadingFolderURI = "";
+ ViewChangeByFolder(newFolder);
+ gPreQuickSearchView = null; // don't remember the cross folder search
+ ScrollToMessageAfterFolderLoad(newFolder);
+ }
+}
+
+function SwitchView(command)
+{
+ // when switching thread views, we might be coming out of quick search
+ // or a message view.
+ // first set view picker to all
+ ViewChangeByValue(kViewItemAll);
+
+ // clear the QS text, if we need to
+ ClearQSIfNecessary();
+
+ // now switch views
+ var oldSortType = gDBView ? gDBView.sortType : nsMsgViewSortType.byThread;
+ var oldSortOrder = gDBView ? gDBView.sortOrder : nsMsgViewSortOrder.ascending;
+ var viewFlags = gDBView ? gDBView.viewFlags : gCurViewFlags;
+
+ // close existing view.
+ if (gDBView) {
+ gDBView.close();
+ gDBView = null;
+ }
+
+ switch(command)
+ {
+ // "All" threads and "Unread" threads don't change threading state
+ case "cmd_viewAllMsgs":
+ viewFlags = viewFlags & ~nsMsgViewFlagsType.kUnreadOnly;
+ CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowAllThreads, viewFlags,
+ oldSortType, oldSortOrder);
+ break;
+ case "cmd_viewUnreadMsgs":
+ viewFlags = viewFlags | nsMsgViewFlagsType.kUnreadOnly;
+ CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowAllThreads, viewFlags,
+ oldSortType, oldSortOrder );
+ break;
+ // "Threads with Unread" and "Watched Threads with Unread" force threading
+ case "cmd_viewThreadsWithUnread":
+ CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowThreadsWithUnread, nsMsgViewFlagsType.kThreadedDisplay,
+ oldSortType, oldSortOrder);
+ break;
+ case "cmd_viewWatchedThreadsWithUnread":
+ CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowWatchedThreadsWithUnread, nsMsgViewFlagsType.kThreadedDisplay,
+ oldSortType, oldSortOrder);
+ break;
+ // "Ignored Threads" toggles 'ignored' inclusion --
+ // but it also resets 'With Unread' views to 'All'
+ case "cmd_viewIgnoredThreads":
+ if (viewFlags & nsMsgViewFlagsType.kShowIgnored)
+ viewFlags = viewFlags & ~nsMsgViewFlagsType.kShowIgnored;
+ else
+ viewFlags = viewFlags | nsMsgViewFlagsType.kShowIgnored;
+ CreateDBView(msgWindow.openFolder, nsMsgViewType.eShowAllThreads, viewFlags,
+ oldSortType, oldSortOrder);
+ break;
+ }
+
+ RerootThreadPane();
+
+ // this is to kick off cross-folder searches for virtual folders.
+ if (gSearchSession && !gVirtualFolderTerms) // another var might be better...
+ {
+ gDBView.searchSession = gSearchSession;
+ gSearchSession.search(msgWindow);
+ }
+}
+
+function SetSentFolderColumns(isSentFolder)
+{
+ var tree = GetThreadTree();
+ var searchBox = document.getElementById("searchInput");
+
+ var lastFolderSent = tree.getAttribute("lastfoldersent") == "true";
+ if (isSentFolder != lastFolderSent)
+ {
+ var senderColumn = document.getElementById("senderCol");
+ var recipientColumn = document.getElementById("recipientCol");
+
+ var saveHidden = senderColumn.getAttribute("hidden");
+ senderColumn.setAttribute("hidden", senderColumn.getAttribute("swappedhidden"));
+ senderColumn.setAttribute("swappedhidden", saveHidden);
+
+ saveHidden = recipientColumn.getAttribute("hidden");
+ recipientColumn.setAttribute("hidden", recipientColumn.getAttribute("swappedhidden"));
+ recipientColumn.setAttribute("swappedhidden", saveHidden);
+ }
+
+ tree.setAttribute("lastfoldersent", isSentFolder ? "true" : "false");
+}
+
+function ShowLocationColumn(show)
+{
+ var col = document.getElementById("locationCol");
+ if (col) {
+ if (show) {
+ col.removeAttribute("hidden");
+ col.removeAttribute("ignoreincolumnpicker");
+ }
+ else {
+ col.setAttribute("hidden","true");
+ col.setAttribute("ignoreincolumnpicker","true");
+ }
+ }
+}
+
+function UpdateReceivedColumn(newFolder)
+{
+ // Only show 'Received' column for e-mails. For newsgroup messages, the 'Date' header is as reliable as an e-mail's
+ // 'Received' header, as it is replaced with the news server's (more reliable) date.
+ var receivedColumn = document.getElementById("receivedCol");
+
+ var newFolderShowsRcvd = (newFolder.flags & Ci.nsMsgFolderFlags.Mail) &&
+ !(newFolder.flags & (Ci.nsMsgFolderFlags.Queue |
+ Ci.nsMsgFolderFlags.Templates |
+ Ci.nsMsgFolderFlags.Drafts |
+ Ci.nsMsgFolderFlags.SentMail));
+
+ var tempHidden = receivedColumn.getAttribute("temphidden") == "true";
+ var isHidden = receivedColumn.getAttribute("hidden") == "true";
+
+ if (!newFolderShowsRcvd && !isHidden)
+ {
+ // Record state & hide
+ receivedColumn.setAttribute("temphidden", "true");
+ receivedColumn.setAttribute("hidden", "true");
+ }
+ else if (newFolderShowsRcvd && tempHidden && isHidden)
+ {
+ receivedColumn.setAttribute("hidden", "false");
+ }
+
+ if (newFolderShowsRcvd)
+ {
+ receivedColumn.removeAttribute("ignoreincolumnpicker");
+ receivedColumn.removeAttribute("temphidden");
+ }
+ else
+ receivedColumn.setAttribute("ignoreincolumnpicker", "true");
+}
+
+
+function SetNewsFolderColumns()
+{
+ var sizeColumn = document.getElementById("sizeCol");
+
+ if (gDBView.usingLines) {
+ sizeColumn.setAttribute("tooltiptext",gMessengerBundle.getString("linesColumnTooltip2"));
+ sizeColumn.setAttribute("label",gMessengerBundle.getString("linesColumnHeader"));
+ }
+ else {
+ sizeColumn.setAttribute("tooltiptext", gMessengerBundle.getString("sizeColumnTooltip2"));
+ sizeColumn.setAttribute("label", gMessengerBundle.getString("sizeColumnHeader"));
+ }
+}
+
+function UpdateStatusMessageCounts(folder)
+{
+ var unreadElement = GetUnreadCountElement();
+ var totalElement = GetTotalCountElement();
+ if(folder && unreadElement && totalElement)
+ {
+ var numSelected = GetNumSelectedMessages();
+
+ var numUnread = (numSelected > 1) ?
+ gMessengerBundle.getFormattedString("selectedMsgStatus",
+ [numSelected]) :
+ gMessengerBundle.getFormattedString("unreadMsgStatus",
+ [ folder.getNumUnread(false)]);
+ var numTotal =
+ gMessengerBundle.getFormattedString("totalMsgStatus",
+ [folder.getTotalMessages(false)]);
+
+ unreadElement.setAttribute("label", numUnread);
+ totalElement.setAttribute("label", numTotal);
+ unreadElement.hidden = false;
+ totalElement.hidden = false;
+
+ }
+
+}
+
+function ConvertSortTypeToColumnID(sortKey)
+{
+ var columnID;
+
+ // Hack to turn this into an integer, if it was a string.
+ // It would be a string if it came from xulstore.json
+ sortKey = sortKey - 0;
+
+ switch (sortKey) {
+ // In the case of None, we default to the date column
+ // This appears to be the case in such instances as
+ // Global search, so don't complain about it.
+ case nsMsgViewSortType.byNone:
+ case nsMsgViewSortType.byDate:
+ columnID = "dateCol";
+ break;
+ case nsMsgViewSortType.byReceived:
+ columnID = "receivedCol";
+ break;
+ case nsMsgViewSortType.byAuthor:
+ columnID = "senderCol";
+ break;
+ case nsMsgViewSortType.byRecipient:
+ columnID = "recipientCol";
+ break;
+ case nsMsgViewSortType.bySubject:
+ columnID = "subjectCol";
+ break;
+ case nsMsgViewSortType.byLocation:
+ columnID = "locationCol";
+ break;
+ case nsMsgViewSortType.byAccount:
+ columnID = "accountCol";
+ break;
+ case nsMsgViewSortType.byUnread:
+ columnID = "unreadButtonColHeader";
+ break;
+ case nsMsgViewSortType.byStatus:
+ columnID = "statusCol";
+ break;
+ case nsMsgViewSortType.byTags:
+ columnID = "tagsCol";
+ break;
+ case nsMsgViewSortType.bySize:
+ columnID = "sizeCol";
+ break;
+ case nsMsgViewSortType.byPriority:
+ columnID = "priorityCol";
+ break;
+ case nsMsgViewSortType.byFlagged:
+ columnID = "flaggedCol";
+ break;
+ case nsMsgViewSortType.byThread:
+ columnID = "threadCol";
+ break;
+ case nsMsgViewSortType.byId:
+ columnID = "idCol";
+ break;
+ case nsMsgViewSortType.byJunkStatus:
+ columnID = "junkStatusCol";
+ break;
+ case nsMsgViewSortType.byAttachments:
+ columnID = "attachmentCol";
+ break;
+ case nsMsgViewSortType.byCustom:
+ columnID = gDBView.db.dBFolderInfo.getProperty("customSortCol");
+ if (!columnID) {
+ dump("ConvertSortTypeToColumnID: custom sort key but columnID not found\n");
+ columnID = "dateCol";
+ }
+ break;
+ default:
+ dump("unsupported sort key: " + sortKey + "\n");
+ columnID = null;
+ break;
+ }
+ return columnID;
+}
+
+var nsMsgViewSortType = Ci.nsMsgViewSortType;
+var nsMsgViewSortOrder = Ci.nsMsgViewSortOrder;
+var nsMsgViewFlagsType = Ci.nsMsgViewFlagsType;
+var nsMsgViewCommandType = Ci.nsMsgViewCommandType;
+var nsMsgViewType = Ci.nsMsgViewType;
+var nsMsgNavigationType = Ci.nsMsgNavigationType;
+
+var gDBView = null;
+var gCurViewFlags;
+var gCurSortType;
+
+// CreateDBView is called when we have a thread pane. CreateBareDBView is called when there is no
+// tree associated with the view. CreateDBView will call into CreateBareDBView...
+
+function CreateBareDBView(originalView, msgFolder, viewType, viewFlags, sortType, sortOrder)
+{
+ var dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=";
+ // hack to turn this into an integer, if it was a string
+ // it would be a string if it came from xulstore.json
+ viewType = viewType - 0;
+
+ switch (viewType) {
+ case nsMsgViewType.eShowQuickSearchResults:
+ dbviewContractId += "quicksearch";
+ break;
+ case nsMsgViewType.eShowSearch:
+ dbviewContractId += "search";
+ break;
+ case nsMsgViewType.eShowThreadsWithUnread:
+ dbviewContractId += "threadswithunread";
+ break;
+ case nsMsgViewType.eShowWatchedThreadsWithUnread:
+ dbviewContractId += "watchedthreadswithunread";
+ break;
+ case nsMsgViewType.eShowVirtualFolderResults:
+ dbviewContractId += "xfvf";
+ break;
+ case nsMsgViewType.eShowAllThreads:
+ default:
+ if (viewFlags & nsMsgViewFlagsType.kGroupBySort)
+ dbviewContractId += "group";
+ else
+ dbviewContractId += "threaded";
+ break;
+ }
+
+// dump ("contract id = " + dbviewContractId + "original view = " + originalView + "\n");
+ if (!originalView)
+ gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView);
+
+ gCurViewFlags = viewFlags;
+ var count = new Object;
+ if (!gThreadPaneCommandUpdater)
+ gThreadPaneCommandUpdater = new nsMsgDBViewCommandUpdater();
+
+ gCurSortType = sortType;
+
+ if (!originalView) {
+ gDBView.init(messenger, msgWindow, gThreadPaneCommandUpdater);
+ gDBView.open(msgFolder, gCurSortType, sortOrder, viewFlags, count);
+ if (viewType == nsMsgViewType.eShowVirtualFolderResults)
+ {
+ // the view is a listener on the search results
+ gViewSearchListener = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
+ gSearchSession.registerListener(gViewSearchListener);
+ }
+ }
+ else {
+ gDBView = originalView.cloneDBView(messenger, msgWindow, gThreadPaneCommandUpdater);
+ }
+}
+
+function CreateDBView(msgFolder, viewType, viewFlags, sortType, sortOrder)
+{
+ // call the inner create method
+ CreateBareDBView(null, msgFolder, viewType, viewFlags, sortType, sortOrder);
+
+ // now do tree specific work
+
+ // based on the collapsed state of the thread pane/message pane splitter,
+ // suppress message display if appropriate.
+ gDBView.suppressMsgDisplay = IsMessagePaneCollapsed();
+
+ UpdateSortIndicators(gCurSortType, sortOrder);
+ Services.obs.notifyObservers(msgFolder, "MsgCreateDBView", viewType + ":" + viewFlags);
+}
+
+function FolderPaneSelectionChange()
+{
+ let folders = GetSelectedMsgFolders();
+ if (folders.length) {
+ let locationItem = document.getElementById("locationFolders");
+ if (locationItem &&
+ locationItem.parentNode.parentNode.localName != "toolbarpalette") {
+ let msgFolder = folders[0];
+ locationItem.setAttribute("label", msgFolder.prettyName);
+ document.getElementById("folderLocationPopup")
+ ._setCssSelectors(msgFolder, locationItem);
+ }
+ }
+
+ let folderSelection = gFolderTreeView.selection;
+
+ // This prevents a folder from being loaded in the case that the user
+ // has right-clicked on a folder different from the one that was
+ // originally highlighted. On a right-click, the highlight (selection)
+ // of a row will be different from the value of currentIndex, thus if
+ // the currentIndex is not selected, it means the user right-clicked
+ // and we don't want to load the contents of the folder.
+ if (!folderSelection.isSelected(folderSelection.currentIndex))
+ return;
+
+ gVirtualFolderTerms = null;
+ gXFVirtualFolderTerms = null;
+
+ if (folders.length == 1)
+ {
+ let msgFolder = folders[0];
+ let uriToLoad = msgFolder.URI;
+
+ if (msgFolder == gMsgFolderSelected)
+ return;
+ // If msgFolder turns out to be a single folder saved search, not a virtual folder,
+ // realFolder will get set to the underlying folder the saved search is based on.
+ let realFolder = msgFolder;
+ gPrevSelectedFolder = gMsgFolderSelected;
+ gMsgFolderSelected = msgFolder;
+ var folderFlags = msgFolder.flags;
+ const kVirtual = Ci.nsMsgFolderFlags.Virtual;
+ // if this is same folder, and we're not showing a virtual folder
+ // then do nothing.
+ if (msgFolder == msgWindow.openFolder &&
+ !(folderFlags & kVirtual) && !(gPrevFolderFlags & kVirtual))
+ return;
+
+ OnLeavingFolder(gPrevSelectedFolder); // mark all read in last folder
+ var sortType = 0;
+ var sortOrder = 0;
+ var viewFlags = 0;
+ var viewType = 0;
+ gDefaultSearchViewTerms = null;
+ gVirtualFolderTerms = null;
+ gXFVirtualFolderTerms = null;
+ gPrevFolderFlags = folderFlags;
+ gCurrentVirtualFolderUri = null;
+ // don't get the db if this folder is a server
+ // we're going to be display account central
+ if (!(msgFolder.isServer))
+ {
+ try
+ {
+ var msgDatabase = msgFolder.msgDatabase;
+ if (msgDatabase)
+ {
+ gSearchSession = null;
+ var dbFolderInfo = msgDatabase.dBFolderInfo;
+ sortType = dbFolderInfo.sortType;
+ sortOrder = dbFolderInfo.sortOrder;
+ viewType = dbFolderInfo.viewType;
+ viewFlags = dbFolderInfo.viewFlags;
+ if (folderFlags & kVirtual)
+ {
+ viewType = nsMsgViewType.eShowQuickSearchResults;
+ var searchTermString = dbFolderInfo.getCharProperty("searchStr");
+ // trick the view code into updating the real folder...
+ gCurrentVirtualFolderUri = uriToLoad;
+ var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
+ var srchFolderUriArray = srchFolderUri.split('|');
+ var searchOnline = dbFolderInfo.getBooleanProperty("searchOnline", false);
+ // cross folder search
+ var filterList = MailServices.filters.getTempFilterList(msgFolder);
+ var tempFilter = filterList.createFilter("temp");
+ filterList.parseCondition(tempFilter, searchTermString);
+ if (srchFolderUriArray.length > 1)
+ {
+ viewType = nsMsgViewType.eShowVirtualFolderResults;
+ gXFVirtualFolderTerms = CreateGroupedSearchTerms(tempFilter.searchTerms);
+ setupXFVirtualFolderSearch(srchFolderUriArray, gXFVirtualFolderTerms, searchOnline);
+ // need to set things up so that reroot folder issues the search
+ }
+ else
+ {
+ uriToLoad = srchFolderUri;
+ // we need to load the db for the actual folder so that many hdrs to download
+ // will return false...
+ realFolder = MailUtils.getFolderForURI(uriToLoad);
+ msgDatabase = realFolder.msgDatabase;
+// dump("search term string = " + searchTermString + "\n");
+
+ gVirtualFolderTerms = CreateGroupedSearchTerms(tempFilter.searchTerms);
+ }
+ }
+ msgDatabase = null;
+ dbFolderInfo = null;
+ }
+ }
+ catch (ex)
+ {
+ dump("failed to get view & sort values. ex = " + ex +"\n");
+ }
+ }
+ // clear cached view if we have no db or a pending quick search
+ if (!gDBView || gDBView.viewType == nsMsgViewType.eShowQuickSearchResults)
+ {
+ if (gPreQuickSearchView) //close cached view before quick search
+ {
+ gPreQuickSearchView.close();
+ gPreQuickSearchView = null;
+ }
+ var searchInput = document.getElementById("searchInput"); //reset the search input on folder switch
+ if (searchInput)
+ searchInput.value = "";
+ }
+ ClearMessagePane();
+
+ if (gXFVirtualFolderTerms)
+ viewType = nsMsgViewType.eShowVirtualFolderResults;
+ else if (gSearchEmailAddress || gVirtualFolderTerms)
+ viewType = nsMsgViewType.eShowQuickSearchResults;
+ else if (viewType == nsMsgViewType.eShowQuickSearchResults)
+ viewType = nsMsgViewType.eShowAllThreads; //override viewType - we don't want to start w/ quick search
+ ChangeFolder(realFolder, msgFolder, viewType, viewFlags, sortType, sortOrder);
+ if (gVirtualFolderTerms)
+ gDBView.viewFolder = msgFolder;
+
+ let tabmail = GetTabMail();
+ if (tabmail)
+ {
+ tabmail.saveCurrentTabState(); // gDBView may have changed!
+ tabmail.setTabTitle();
+ }
+ }
+ else
+ {
+ msgWindow.openFolder = null;
+ ClearThreadPane();
+ }
+
+ if (gAccountCentralLoaded)
+ UpdateMailToolbar("gAccountCentralLoaded");
+
+ if (gDisplayStartupPage)
+ {
+ loadStartPage();
+ gDisplayStartupPage = false;
+ UpdateMailToolbar("gDisplayStartupPage");
+ }
+}
+
+function ClearThreadPane()
+{
+ if (gDBView) {
+ gDBView.close();
+ gDBView = null;
+ }
+}
+
+var mailOfflineObserver = {
+ observe: function(subject, topic, state) {
+ // sanity checks
+ if (topic != "network:offline-status-changed") return;
+ MailOfflineStateChanged(state == "offline");
+ }
+}
+
+function AddMailOfflineObserver()
+{
+ Services.obs.addObserver(mailOfflineObserver, "network:offline-status-changed");
+}
+
+function RemoveMailOfflineObserver()
+{
+ Services.obs.removeObserver(mailOfflineObserver, "network:offline-status-changed");
+}
+
+function getSearchTermString(searchTerms)
+{
+ var searchIndex;
+ var condition = "";
+ var count = searchTerms.length;
+ for (searchIndex = 0; searchIndex < count; )
+ {
+ var term = searchTerms[searchIndex++];
+
+ if (condition.length > 1)
+ condition += ' ';
+
+ if (term.matchAll)
+ {
+ condition = "ALL";
+ break;
+ }
+ condition += (term.booleanAnd) ? "AND (" : "OR (";
+ condition += term.termAsString + ')';
+ }
+ return condition;
+}
+
+function CreateVirtualFolder(newName, parentFolder, searchFolderURIs, searchTerms, searchOnline)
+{
+ // ### need to make sure view/folder doesn't exist.
+ if (searchFolderURIs && (searchFolderURIs != "") && newName && (newName != ""))
+ {
+ var newFolder;
+ try
+ {
+ if (parentFolder instanceof(Ci.nsIMsgLocalMailFolder))
+ newFolder = parentFolder.createLocalSubfolder(newName);
+ else
+ newFolder = parentFolder.addSubfolder(newName);
+ newFolder.setFlag(Ci.nsMsgFolderFlags.Virtual);
+ var vfdb = newFolder.msgDatabase;
+ var searchTermString = getSearchTermString(searchTerms);
+ var dbFolderInfo = vfdb.dBFolderInfo;
+ // set the view string as a property of the db folder info
+ // set the original folder name as well.
+ dbFolderInfo.setCharProperty("searchStr", searchTermString);
+ dbFolderInfo.setCharProperty("searchFolderUri", searchFolderURIs);
+ dbFolderInfo.setBooleanProperty("searchOnline", searchOnline);
+ vfdb.summaryValid = true;
+ vfdb.Close(true);
+ parentFolder.notifyFolderAdded(newFolder);
+ MailServices.accounts.saveVirtualFolders();
+ }
+ catch(e)
+ {
+ throw(e); // so that the dialog does not automatically close
+ dump ("Exception : creating virtual folder \n");
+ }
+ }
+ else
+ {
+ dump("no name or nothing selected\n");
+ }
+}
+
+var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
+var gSearchSession;
+
+var nsMsgSearchScope = Ci.nsMsgSearchScope;
+
+var gMessengerBundle = null;
+
+var gViewSearchListener;
+
+function GetScopeForFolder(folder)
+{
+ return folder.server.searchScope;
+}
+
+function setupXFVirtualFolderSearch(folderUrisToSearch, searchTerms, searchOnline)
+{
+ var count = new Object;
+ var i;
+
+ gSearchSession = Cc[searchSessionContractID]
+ .createInstance(Ci.nsIMsgSearchSession);
+
+ for (i in folderUrisToSearch)
+ {
+ let realFolder = MailUtils.getFolderForURI(folderUrisToSearch[i]);
+ if (!realFolder.isServer)
+ gSearchSession.addScopeTerm(!searchOnline ? nsMsgSearchScope.offlineMail : GetScopeForFolder(realFolder), realFolder);
+ }
+
+ for (let term of searchTerms) {
+ gSearchSession.appendTerm(term);
+ }
+}
+
+/**
+ * Uses an array of search terms to produce a new list usable from quick search.
+ *
+ * @param searchTermsArray A nsIArray of terms to copy.
+ *
+ * @return nsIMutableArray of search terms
+ */
+function CreateGroupedSearchTerms(searchTermsArray)
+{
+
+ var searchSession = gSearchSession ||
+ Cc[searchSessionContractID].createInstance(Ci.nsIMsgSearchSession);
+
+ // Create a temporary nsIMutableArray to store our search terms
+ // since we will be modifying the terms so they work with quick search.
+ var searchTermsArrayForQS = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+
+ var numEntries = searchTermsArray.length;
+ for (let i = 0; i < numEntries; i++) {
+ let searchTerm = searchTermsArray[i];
+
+ // clone the term, since we might be modifying it
+ var searchTermForQS = searchSession.createTerm();
+ searchTermForQS.value = searchTerm.value;
+ searchTermForQS.attrib = searchTerm.attrib;
+ searchTermForQS.arbitraryHeader = searchTerm.arbitraryHeader
+ searchTermForQS.hdrProperty = searchTerm.hdrProperty;
+ searchTermForQS.customId = searchTerm.customId
+ searchTermForQS.op = searchTerm.op;
+
+ // mark the first node as a group
+ if (i == 0)
+ searchTermForQS.beginsGrouping = true;
+ else if (i == numEntries - 1)
+ searchTermForQS.endsGrouping = true;
+
+ // turn the first term to true to work with quick search...
+ searchTermForQS.booleanAnd = i ? searchTerm.booleanAnd : true;
+
+ searchTermsArrayForQS.appendElement(searchTermForQS);
+ }
+ return searchTermsArrayForQS;
+}
+
+function OnLeavingFolder(aFolder)
+{
+ try
+ {
+ // Mark all messages of aFolder as read:
+ // We can't use the command controller, because it is already tuned in to the
+ // new folder, so we just mimic its behaviour wrt goDoCommand('cmd_markAllRead').
+ if (gDBView && Services.prefs.getBoolPref("mailnews.mark_message_read." + aFolder.server.type))
+ {
+ gDBView.doCommand(nsMsgViewCommandType.markAllRead);
+ }
+ }
+ catch(e){/* ignore */}
+}
+
+var gViewDebug = false;
+
+function viewDebug(str)
+{
+ if (gViewDebug)
+ dump(str);
+}
+
diff --git a/comm/suite/mailnews/content/folderDisplay.js b/comm/suite/mailnews/content/folderDisplay.js
new file mode 100644
index 0000000000..0318fb2e66
--- /dev/null
+++ b/comm/suite/mailnews/content/folderDisplay.js
@@ -0,0 +1,142 @@
+/* -*- 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 gFolderDisplay =
+{
+ get selectedCount()
+ {
+ return gDBView ? gDBView.numSelected : 0;
+ },
+
+ get selectedMessage()
+ {
+ if (!this.selectedIndices.length)
+ return null;
+ return gDBView.hdrForFirstSelectedMessage;
+ },
+
+ get selectedMessageUri()
+ {
+ if (!this.selectedIndices.length)
+ return null;
+ return gDBView.URIForFirstSelectedMessage;
+ },
+
+ get selectedMessageIsFeed()
+ {
+ return FeedMessageHandler.isFeedMessage(this.selectedMessage);
+ },
+
+ get selectedMessageIsImap()
+ {
+ var message = this.selectedMessage;
+ return message && message.folder &&
+ (message.folder.flags & Ci.nsMsgFolderFlags.ImapBox) != 0;
+ },
+
+ get selectedMessageIsNews()
+ {
+ var message = this.selectedMessage;
+ return message && message.folder &&
+ (message.folder.flags & Ci.nsMsgFolderFlags.Newsgroup) != 0;
+ },
+
+ get selectedMessageIsExternal()
+ {
+ var message = this.selectedMessage;
+ return message && !message.folder;
+ },
+
+ get selectedIndices()
+ {
+ return gDBView ? gDBView.getIndicesForSelection() : [];
+ },
+
+ get selectedMessages()
+ {
+ return gDBView ? gDBView.getSelectedMsgHdrs() : [];
+ },
+
+ get selectedMessageUris()
+ {
+ if (!gDBView)
+ return null;
+ var messageArray = gDBView.getURIsForSelection();
+ return messageArray.length ? messageArray : null;
+ },
+
+ get canArchiveSelectedMessages()
+ {
+ if (!gDBView)
+ return false;
+ var selectedMessages = this.selectedMessages;
+ if (selectedMessages.length == 0)
+ return false;
+ return selectedMessages.every(function(aMsg) {
+ let identity = GetIdentityForHeader(aMsg);
+ return identity && identity.archiveEnabled;
+ });
+ },
+
+ get displayedFolder()
+ {
+ return gMsgFolderSelected;
+ },
+
+ /**
+ * Determine which pane currently has focus (one of the folder pane, thread
+ * pane, or message pane). When changing focus to the message pane, be sure
+ * to focus the appropriate content window in addition to the messagepanebox
+ * (doing both is required in order to blur the previously-focused chrome
+ * element).
+ *
+ * @return the focused pane
+ */
+ get focusedPane() {
+ let panes = ["threadTree", "folderTree", "messagepanebox"].map(id =>
+ document.getElementById(id));
+
+ let currentNode = top.document.activeElement;
+
+ while (currentNode) {
+ if (panes.includes(currentNode)) {
+ return currentNode;
+ }
+
+ currentNode = currentNode.parentNode;
+ }
+ return null;
+ },
+
+}
+
+var gMessageDisplay =
+{
+ get displayedMessage()
+ {
+ if (!gDBView)
+ return null;
+ var viewIndex = gDBView.currentlyDisplayedMessage;
+ return viewIndex == nsMsgViewIndex_None ? null :
+ gDBView.getMsgHdrAt(viewIndex);
+ },
+
+ get isDummy()
+ {
+ return gDBView && gDBView.keyForFirstSelectedMessage == nsMsgKey_None;
+ },
+
+ get visible()
+ {
+ return !GetMessagePane().collapsed;
+ },
+
+ set visible(aVisible)
+ {
+ return aVisible; // Fake setter for the time being.
+ }
+}
+
+gFolderDisplay.messageDisplay = gMessageDisplay;
diff --git a/comm/suite/mailnews/content/folderPane.js b/comm/suite/mailnews/content/folderPane.js
new file mode 100644
index 0000000000..35cdc8849a
--- /dev/null
+++ b/comm/suite/mailnews/content/folderPane.js
@@ -0,0 +1,2221 @@
+/* -*- 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/. */
+
+// Implements a tree of folders. It shows icons depending on folder type
+// and other fancy styling.
+// This is used in the main folder pane, but also some dialogs that need
+// to show a nice list of folders.
+
+var { FeedUtils } =
+ ChromeUtils.import("resource:///modules/FeedUtils.jsm");
+var { FolderUtils } =
+ ChromeUtils.import("resource:///modules/FolderUtils.jsm");
+var { IOUtils } =
+ ChromeUtils.import("resource:///modules/IOUtils.js");
+var { IteratorUtils } =
+ ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
+var { mailServices } =
+ ChromeUtils.import("resource:///modules/mailServices.js");
+var { MailUtils } =
+ ChromeUtils.import("resource:///modules/MailUtils.js");
+var { AppConstants } =
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+var { Services } =
+ ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+if (typeof FeedMessageHandler != "object") {
+ Services.scriptloader.loadSubScript("chrome://messenger-newsblog/content/newsblogOverlay.js");
+}
+
+const kDefaultMode = "all";
+
+/**
+ * This file contains the controls and functions for the folder pane.
+ * The following definitions will be useful to know:
+ *
+ * gFolderTreeView - the controller for the folder tree.
+ * ftvItem - folder tree view item, representing a row in the tree
+ * mode - folder view type, e.g., all folders, favorite folders, MRU...
+ */
+
+ /**
+ * An interface that needs to be implemented in order to add a new view to the
+ * folder tree. For default behavior, it is recommended that implementers
+ * subclass this interface instead of relying on duck typing.
+ *
+ * For implementation examples, see |gFolderTreeView._modes|. For how to
+ * register this mode with |gFolderTreeView|, see
+ * |gFolderTreeView.registerFolderTreeMode|.
+ */
+let IFolderTreeMode = {
+ /**
+ * Generates the folder map for this mode.
+ *
+ * @param aFolderTreeView The gFolderTreeView for which this mode is being
+ * activated.
+ *
+ * @returns An array containing ftvItem instances representing the top-level
+ * folders in this view.
+ */
+ generateMap: function IFolderTreeMode_generateMap(aFolderTreeView) {
+ return null;
+ },
+
+ /**
+ * Given an nsIMsgFolder, returns its parent in the map. The default behaviour
+ * is to return the folder's actual parent (aFolder.parent). Folder tree modes
+ * may decide to override it.
+ *
+ * If the parent isn't easily computable given just the folder, you may
+ * consider generating the entire ftvItem tree at once and using a map from
+ * folders to ftvItems.
+ *
+ * @returns an nsIMsgFolder representing the parent of the folder in the view,
+ * or null if the folder is a top-level folder in the map. It is expected
+ * that the returned parent will have the given folder as one of its
+ * children.
+ * @note This function need not guarantee that either the folder or its parent
+ * is actually in the view.
+ */
+ getParentOfFolder: function IFolderTreeMode_getParentOfFolder(aFolder) {
+ return aFolder.parent;
+ },
+
+ /**
+ * Given an nsIMsgDBHdr, returns the folder it is considered to be contained
+ * in, in this mode. This is usually just the physical folder it is contained
+ * in (aMsgHdr.folder), but some modes may decide to override this. For
+ * example, combined views like Smart Folders return the smart inbox for any
+ * messages in any inbox.
+ *
+ * The folder returned doesn't need to be in the view.
+ *
+ * @returns The folder the message header is considered to be contained in, in
+ * this mode. The returned folder may or may not actually be in the view
+ * -- however, given a valid nsIMsgDBHdr, it is expected that a) a
+ * non-null folder is returned, and that b) the folder that is returned
+ * actually does contain the message header.
+ */
+ getFolderForMsgHdr: function IFolderTreeMode_getFolderForMsgHdr(aMsgHdr) {
+ return aMsgHdr.folder;
+ },
+
+ /**
+ * Notified when a folder is added. The default behavior is to add it as a
+ * child of the parent item, but some views may decide to override this. For
+ * example, combined views like Smart Folders add any new inbox as a child of
+ * the smart inbox.
+ *
+ * @param aParent The parent of the folder that was added.
+ * @param aFolder The folder that was added.
+ */
+ onFolderAdded: function IFolderTreeMode_onFolderAdded(aParent, aFolder) {
+ gFolderTreeView.addFolder(aParent, aFolder);
+ },
+
+ /**
+ * Notified when a folder int property is changed.
+ *
+ * Returns true if the event was processed inside the function and no further
+ * default handling should be done in the caller. Otherwise false.
+ *
+ * @param aItem The folder with a change.
+ * @param aProperty The changed property string.
+ * @param aOld The old value of the property.
+ * @param aNew The new value of the property.
+ */
+ handleChangedIntProperty: function(aItem, aProperty, aOld, aNew) {
+ return false;
+ }
+};
+
+/**
+ * This is our controller for the folder-tree. It includes our nsITreeView
+ * implementation, as well as other control functions.
+ */
+let gFolderTreeView = {
+ messengerBundle: null,
+
+ /**
+ * Called when the window is initially loaded. This function initializes the
+ * folder-pane to the view last shown before the application was closed.
+ */
+ load: function ftv_load(aTree, aJSONFile) {
+ this._treeElement = aTree;
+ this.messengerBundle = document.getElementById("bundle_messenger");
+
+ // The folder pane can be used for other trees which may not have these
+ // elements.
+ if (document.getElementById("folderpane-splitter"))
+ document.getElementById("folderpane-splitter").collapsed = false;
+ if (document.getElementById("folderPaneBox"))
+ document.getElementById("folderPaneBox").collapsed = false;
+
+ if (aJSONFile) {
+ // Parse our persistent-open-state json file.
+ let data = IOUtils.loadFileToString(aJSONFile);
+ if (data) {
+ try {
+ this._persistOpenMap = JSON.parse(data);
+ } catch (x) {
+ Cu.reportError(gFolderTreeView.messengerBundle.getFormattedString("failedToReadFile", [aJSONFile, x]));
+ }
+ }
+ }
+
+ // Load our data.
+ this._rebuild();
+ // And actually draw the tree.
+ aTree.view = this;
+
+ gFolderStatsHelpers.init();
+
+ // Add this listener so that we can update the tree when things change.
+ MailServices.mailSession.AddFolderListener(this, Ci.nsIFolderListener.all);
+ },
+
+ /**
+ * Called when the window is being torn down. Here we undo everything we did
+ * onload. That means removing our listener and serializing our JSON.
+ */
+ unload: function ftv_unload(aJSONFile) {
+ // Remove our listener.
+ MailServices.mailSession.RemoveFolderListener(this);
+
+ if (aJSONFile) {
+ // Write out our json file...
+ let data = JSON.stringify(this._persistOpenMap);
+ IOUtils.saveStringToFile(aJSONFile, data);
+ }
+ },
+
+ /**
+ * Extensions can use this function to add a new mode to the folder pane.
+ *
+ * @param aCommonName an internal name to identify this mode. Must be unique
+ * @param aMode An implementation of |IFolderTreeMode| for this mode.
+ * @param aDisplayName a localized name for this mode
+ */
+ registerFolderTreeMode: function ftv_registerFolderTreeMode(aCommonName,
+ aMode,
+ aDisplayName) {
+ this._modeNames.push(aCommonName);
+ this._modes[aCommonName] = aMode;
+ this._modeDisplayNames[aCommonName] = aDisplayName;
+ },
+
+ /**
+ * Unregisters a previously registered mode. Since common-names must be unique
+ * this is all that need be provided to unregister.
+ * @param aCommonName the common-name with which the mode was previously
+ * registered
+ */
+ unregisterFolderTreeMode: function ftv_unregisterFolderTreeMode(aCommonName) {
+ this._modeNames.splice(this._modeNames.indexOf(aCommonName), 1);
+ delete this._modes[aCommonName];
+ delete this._modeDisplayNames[aCommonName];
+ if (this._mode == aCommonName)
+ this.mode = kDefaultMode;
+ },
+
+ /**
+ * Retrieves a specific mode object
+ * @param aCommonName the common-name with which the mode was previously
+ * registered
+ */
+ getFolderTreeMode: function ftv_getFolderTreeMode(aCommonName) {
+ return this._modes[aCommonName];
+ },
+
+ /**
+ * Called to move to the next/prev folder-mode in the list
+ *
+ * @param aForward whether or not we should move forward in the list
+ */
+ cycleMode: function ftv_cycleMode(aForward) {
+ let index = this._modeNames.indexOf(this.mode);
+ let offset = aForward ? 1 : this._modeNames.length - 1;
+ index = (index + offset) % this._modeNames.length;
+
+ this.mode = this._modeNames[index];
+ },
+
+ /**
+ * If the hidden pref is set, then double-clicking on a folder should open it
+ *
+ * @param event the double-click event
+ */
+ onDoubleClick: function ftv_onDoubleClick(aEvent) {
+ if (aEvent.button != 0 || aEvent.originalTarget.localName == "twisty" ||
+ aEvent.originalTarget.localName == "slider" ||
+ aEvent.originalTarget.localName == "scrollbarbutton")
+ return;
+
+ let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aEvent.clientX,
+ aEvent.clientY);
+ let folderItem = gFolderTreeView._rowMap[row];
+ if (folderItem)
+ folderItem.command();
+
+ // Don't let the double-click toggle the open state of the folder here.
+ aEvent.stopPropagation();
+ },
+
+ onKeyPress(event) {
+ if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
+ if ((AppConstants.platform == "macosx" ? event.metaKey : event.ctrlKey) &&
+ AllowOpenTabOnMiddleClick()) {
+ FolderPaneContextMenuNewTab(event);
+ let folderTree = document.getElementById("folderTree");
+ RestoreSelectionWithoutContentLoad(folderTree);
+ }
+ }
+ },
+
+ getFolderAtCoords: function ftv_getFolderAtCoords(aX, aY) {
+ let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aX, aY);
+ if (row in gFolderTreeView._rowMap)
+ return gFolderTreeView._rowMap[row]._folder;
+ return null;
+ },
+
+ /**
+ * A string representation for the current display-mode. Each value here must
+ * correspond to an entry in _modes
+ */
+ _mode: null,
+ get mode() {
+ if (!this._mode) {
+ this._mode = this._treeElement.getAttribute("mode");
+ // This can happen when an extension is removed.
+ if (!(this._mode in this._modes))
+ this._mode = kDefaultMode;
+ }
+ return this._mode;
+ },
+
+ /**
+ * @param aMode The final name of the mode to switch to.
+ */
+ set mode(aMode) {
+ // Ignore unknown modes.
+ if (!(aMode in this._modes))
+ return;
+
+ this._mode = aMode;
+
+ // Store current mode and actually build the folder pane.
+ this._treeElement.setAttribute("mode", this._mode);
+ this._rebuild();
+ },
+
+ /**
+ * Selects a given nsIMsgFolder in the tree. This function will also ensure
+ * that the folder is actually being displayed (that is, that none of its
+ * ancestors are collapsed.
+ *
+ * @param aFolder the nsIMsgFolder to select
+ * @param [aForceSelect] Whether we should switch to the default mode to
+ * select the folder in case we didn't find the folder in the current
+ * view. Defaults to false.
+ * @returns true if the folder selection was successful, false if it failed
+ * (probably because the folder isn't in the view at all)
+ */
+ selectFolder: function ftv_selectFolder(aFolder, aForceSelect = false) {
+ // "this" inside the nested function refers to the function...
+ // Also note that openIfNot is recursive.
+ let tree = this;
+ let folderTreeMode = this._modes[this._mode];
+ function openIfNot(aFolderToOpen) {
+ let index = tree.getIndexOfFolder(aFolderToOpen);
+ if (index != null) {
+ if (!tree._rowMap[index].open)
+ tree._toggleRow(index, false);
+ return true;
+ }
+
+ // Not found, so open the parent.
+ let parent = folderTreeMode.getParentOfFolder(aFolderToOpen);
+ if (parent && openIfNot(parent)) {
+ // Now our parent is open, so we can open ourselves.
+ index = tree.getIndexOfFolder(aFolderToOpen);
+ if (index != null) {
+ tree._toggleRow(index, false);
+ return true;
+ }
+ }
+
+ // No way we can find the folder now.
+ return false;
+ }
+ let parent = folderTreeMode.getParentOfFolder(aFolder);
+ if (parent)
+ openIfNot(parent);
+
+ let folderIndex = tree.getIndexOfFolder(aFolder);
+ if (folderIndex == null) {
+ if (aForceSelect) {
+ // Switch to the default mode. The assumption here is that the default
+ // mode can display every folder.
+ this.mode = kDefaultMode;
+ // We don't want to get stuck in an infinite recursion,
+ // so pass in false.
+ return this.selectFolder(aFolder, false);
+ }
+
+ return false;
+ }
+
+ this.selection.select(folderIndex);
+ this._treeElement.treeBoxObject.ensureRowIsVisible(folderIndex);
+ return true;
+ },
+
+ /**
+ * Returns the index of a folder in the current display.
+ *
+ * @param aFolder the folder whose index should be returned.
+ * @returns The index of the folder in the view (a number).
+ * @note If the folder is not in the display (perhaps because one of its
+ * anscetors is collapsed), this function returns null.
+ */
+ getIndexOfFolder: function ftv_getIndexOfFolder(aFolder) {
+ for (let [iRow, row] of this._rowMap.entries()) {
+ if (row.id == aFolder.URI)
+ return iRow;
+ }
+ return null;
+ },
+
+ /**
+ * Returns the folder for an index in the current display.
+ *
+ * @param aIndex the index for which the folder should be returned.
+ * @note If the index is out of bounds, this function returns null.
+ */
+ getFolderForIndex: function ftv_getFolderForIndex(aIndex) {
+ if (aIndex < 0 || aIndex >= this._rowMap.length)
+ return null;
+ return this._rowMap[aIndex]._folder;
+ },
+
+ /**
+ * Returns the parent of a folder in the current view. This may be, but is not
+ * necessarily, the actual parent of the folder (aFolder.parent). In
+ * particular, in the smart view, special folders are usually children of the
+ * smart folder of that kind.
+ *
+ * @param aFolder The folder to get the parent of.
+ * @returns The parent of the folder, or null if the parent wasn't found.
+ * @note This function does not guarantee that either the folder or its parent
+ * is actually in the view.
+ */
+ getParentOfFolder: function ftv_getParentOfFolder(aFolder) {
+ return this._modes[this._mode].getParentOfFolder(aFolder);
+ },
+
+ /**
+ * Given an nsIMsgDBHdr, returns the folder it is considered to be contained
+ * in, in the current mode. This is usually, but not necessarily, the actual
+ * folder the message is in (aMsgHdr.folder). For more details, see
+ * |IFolderTreeMode.getFolderForMsgHdr|.
+ */
+ getFolderForMsgHdr: function ftv_getFolderForMsgHdr(aMsgHdr) {
+ return this._modes[this._mode].getFolderForMsgHdr(aMsgHdr);
+ },
+
+ /**
+ * Returns the |ftvItem| for an index in the current display. Intended for use
+ * by folder tree mode implementers.
+ *
+ * @param aIndex The index for which the ftvItem should be returned.
+ * @note If the index is out of bounds, this function returns null.
+ */
+ getFTVItemForIndex: function ftv_getFTVItemForIndex(aIndex) {
+ return this._rowMap[aIndex];
+ },
+
+ /**
+ * Returns an array of nsIMsgFolders corresponding to the current selection
+ * in the tree
+ */
+ getSelectedFolders: function ftv_getSelectedFolders() {
+ let selection = this.selection;
+ if (!selection)
+ return [];
+
+ let folderArray = [];
+ let rangeCount = selection.getRangeCount();
+ for (let i = 0; i < rangeCount; i++) {
+ let startIndex = {};
+ let endIndex = {};
+ selection.getRangeAt(i, startIndex, endIndex);
+ for (let j = startIndex.value; j <= endIndex.value; j++) {
+ if (j < this._rowMap.length)
+ folderArray.push(this._rowMap[j]._folder);
+ }
+ }
+ return folderArray;
+ },
+
+ /**
+ * Adds a new child |ftvItem| to the given parent |ftvItem|. Intended for use
+ * by folder tree mode implementers.
+ *
+ * @param aParentItem The parent ftvItem. It is assumed that this is visible
+ * in the view.
+ * @param aParentIndex The index of the parent ftvItem in the view.
+ * @param aItem The item to add.
+ */
+ addChildItem: function ftv_addChildItem(aParentItem, aParentIndex, aItem) {
+ this._addChildToView(aParentItem, aParentIndex, aItem);
+ },
+
+ // ****************** Start of nsITreeView implementation **************** //
+
+ get rowCount() {
+ return this._rowMap.length;
+ },
+
+ /**
+ * drag drop interfaces
+ */
+ canDrop: function ftv_canDrop(aRow, aOrientation) {
+ let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
+ if (!targetFolder)
+ return false;
+ let dt = this._currentTransfer;
+ let types = Array.from(dt.mozTypesAt(0));
+ if (types.includes("text/x-moz-message")) {
+ if (aOrientation != Ci.nsITreeView.DROP_ON)
+ return false;
+ // Don't allow drop onto server itself.
+ if (targetFolder.isServer)
+ return false;
+ // Don't allow drop into a folder that cannot take messages.
+ if (!targetFolder.canFileMessages)
+ return false;
+ let messenger = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger);
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
+ // Don't allow drop onto original folder.
+ if (msgHdr.folder == targetFolder)
+ return false;
+ }
+ return true;
+ }
+ else if (types.includes("text/x-moz-folder")) {
+ if (aOrientation != Ci.nsITreeView.DROP_ON)
+ return false;
+ // If cannot create subfolders then don't allow drop here.
+ if (!targetFolder.canCreateSubfolders)
+ return false;
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let folder = dt.mozGetDataAt("text/x-moz-folder", i)
+ .QueryInterface(Ci.nsIMsgFolder);
+ // Don't allow to drop on itself.
+ if (targetFolder == folder)
+ return false;
+ // Don't copy within same server.
+ if ((folder.server == targetFolder.server) &&
+ (dt.dropEffect == 'copy'))
+ return false;
+ // Don't allow immediate child to be dropped onto its parent.
+ if (targetFolder == folder.parent)
+ return false;
+ // Don't allow dragging of virtual folders across accounts.
+ if ((folder.flags & Ci.nsMsgFolderFlags.Virtual) &&
+ folder.server != targetFolder.server)
+ return false;
+ // Don't allow parent to be dropped on its ancestors.
+ if (folder.isAncestorOf(targetFolder))
+ return false;
+ // If there is a folder that can't be renamed, don't allow it to be
+ // dropped if it is not to "Local Folders" or is to the same account.
+ if (!folder.canRename && (targetFolder.server.type != "none" ||
+ folder.server == targetFolder.server))
+ return false;
+ }
+ return true;
+ }
+ else if (types.includes("text/x-moz-newsfolder")) {
+ // Don't allow dragging onto element.
+ if (aOrientation == Ci.nsITreeView.DROP_ON)
+ return false;
+ // Don't allow drop onto server itself.
+ if (targetFolder.isServer)
+ return false;
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
+ .QueryInterface(Ci.nsIMsgFolder);
+ // Don't allow dragging newsgroup to other account.
+ if (targetFolder.rootFolder != folder.rootFolder)
+ return false;
+ // Don't allow dragging newsgroup to before/after itself.
+ if (targetFolder == folder)
+ return false;
+ // Don't allow dragging newsgroup to before item after or
+ // after item before.
+ let row = aRow + aOrientation;
+ if (row in gFolderTreeView._rowMap &&
+ (gFolderTreeView._rowMap[row]._folder == folder))
+ return false;
+ }
+ return true;
+ }
+ // Allow subscribing to feeds by dragging an url to a feed account.
+ else if (targetFolder.server.type == "rss" && dt.mozItemCount == 1)
+ return FeedUtils.getFeedUriFromDataTransfer(dt) ? true : false;
+ else if (types.includes("application/x-moz-file")) {
+ if (aOrientation != Ci.nsITreeView.DROP_ON)
+ return false;
+ // Don't allow drop onto server itself.
+ if (targetFolder.isServer)
+ return false;
+ // Don't allow drop into a folder that cannot take messages.
+ if (!targetFolder.canFileMessages)
+ return false;
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i);
+ if (!extFile) {
+ continue;
+ }
+ return extFile.QueryInterface(Ci.nsIFile).isFile();
+ }
+ }
+ return false;
+ },
+ drop: function ftv_drop(aRow, aOrientation) {
+ let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
+
+ let dt = this._currentTransfer;
+ let count = dt.mozItemCount;
+ let cs = MailServices.copy;
+
+ // This is a potential rss feed. A link image as well as link text url
+ // should be handled; try to extract a url from non moz apps as well.
+ let feedUri = targetFolder.server.type == "rss" && count == 1 ?
+ FeedUtils.getFeedUriFromDataTransfer(dt) : null;
+
+ // We only support drag of a single flavor at a time.
+ let types = Array.from(dt.mozTypesAt(0));
+ if (types.includes("text/x-moz-folder")) {
+ for (let i = 0; i < count; i++) {
+ let folder = dt.mozGetDataAt("text/x-moz-folder", i)
+ .QueryInterface(Ci.nsIMsgFolder);
+ cs.copyFolders(folder, targetFolder,
+ (folder.server == targetFolder.server), null,
+ msgWindow);
+ }
+ }
+ else if (types.includes("text/x-moz-newsfolder")) {
+ // Start by getting folders into order.
+ let folders = new Array;
+ for (let i = 0; i < count; i++) {
+ let folder = dt.mozGetDataAt("text/x-moz-newsfolder", i)
+ .QueryInterface(Ci.nsIMsgFolder);
+ folders[this.getIndexOfFolder(folder)] = folder;
+ }
+ let newsFolder = targetFolder.rootFolder
+ .QueryInterface(Ci.nsIMsgNewsFolder);
+ // When moving down, want to insert first one last.
+ // When moving up, want to insert first one first.
+ let i = (aOrientation == 1) ? folders.length - 1 : 0;
+ while (i >= 0 && i < folders.length) {
+ let folder = folders[i];
+ if (folder) {
+ newsFolder.moveFolder(folder, targetFolder, aOrientation);
+ this.selection.toggleSelect(this.getIndexOfFolder(folder));
+ }
+ i -= aOrientation;
+ }
+ }
+ else if (types.includes("text/x-moz-message")) {
+ let array = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ let sourceFolder;
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ for (let i = 0; i < count; i++) {
+ let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
+ if (!i)
+ sourceFolder = msgHdr.folder;
+ array.appendElement(msgHdr);
+ }
+ let isMove = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService).getCurrentSession()
+ .dragAction == Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
+ let isNews = sourceFolder.flags & Ci.nsMsgFolderFlags.Newsgroup;
+ if (!sourceFolder.canDeleteMessages || isNews)
+ isMove = false;
+
+ Services.prefs.setCharPref("mail.last_msg_movecopy_target_uri",
+ targetFolder.URI);
+ Services.prefs.setBoolPref("mail.last_msg_movecopy_was_move", isMove);
+ // ### ugh, so this won't work with cross-folder views. We would
+ // really need to partition the messages by folder.
+ cs.copyMessages(sourceFolder, array, targetFolder, isMove, null,
+ msgWindow, true);
+ }
+ else if (feedUri) {
+ Cc["@mozilla.org/newsblog-feed-downloader;1"]
+ .getService(Ci.nsINewsBlogFeedDownloader)
+ .subscribeToFeed(feedUri.spec, targetFolder, msgWindow);
+ }
+ else if (types.includes("application/x-moz-file")) {
+ for (let i = 0; i < count; i++) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i)
+ .QueryInterface(Ci.nsIFile);
+ if (extFile.isFile()) {
+ let len = extFile.leafName.length;
+ if (len > 4 && extFile.leafName.toLowerCase().endsWith(".eml"))
+ cs.copyFileMessage(extFile, targetFolder, null, false, 1, "", null,
+ msgWindow);
+ }
+ }
+ }
+ },
+
+ _onDragStart: function ftv_dragStart(aEvent) {
+ // Ugh, this is ugly but necessary.
+ let view = gFolderTreeView;
+
+ if (aEvent.originalTarget.localName != "treechildren")
+ return;
+
+ let folders = view.getSelectedFolders();
+ folders = folders.filter(function(f) { return !f.isServer; });
+ for (let i in folders) {
+ let flavor = folders[i].server.type == "nntp" ? "text/x-moz-newsfolder" :
+ "text/x-moz-folder";
+ aEvent.dataTransfer.mozSetDataAt(flavor, folders[i], i);
+ }
+ aEvent.dataTransfer.effectAllowed = "copyMove";
+ aEvent.dataTransfer.addElement(aEvent.originalTarget);
+ return;
+ },
+
+ _onDragOver: function ftv_onDragOver(aEvent) {
+ this._currentTransfer = aEvent.dataTransfer;
+ },
+
+ _onDragDrop: function ftv_onDragDrop(aEvent) {
+ this._currentTransfer = aEvent.dataTransfer;
+ },
+
+ /**
+ * CSS files will cue off of these. Note that we reach into the rowMap's
+ * items so that custom data-displays can define their own properties
+ */
+ getCellProperties: function ftv_getCellProperties(aRow, aCol) {
+ return this._rowMap[aRow].getProperties(aCol);
+ },
+
+ /**
+ * The actual text to display in the tree
+ */
+ getCellText: function ftv_getCellText(aRow, aCol) {
+ if ((aCol.id == "folderNameCol") ||
+ (aCol.id == "folderUnreadCol") ||
+ (aCol.id == "folderTotalCol") ||
+ (aCol.id == "folderSizeCol"))
+ return this._rowMap[aRow].getText(aCol.id);
+ return "";
+ },
+
+ /**
+ * The ftvItems take care of assigning this when created.
+ */
+ getLevel: function ftv_getLevel(aIndex) {
+ return this._rowMap[aIndex].level;
+ },
+
+ /**
+ * The ftvItems take care of assigning this when building children lists
+ */
+ getServerNameAdded: function ftv_getServerNameAdded(aIndex) {
+ return this._rowMap[aIndex].addServerName;
+ },
+
+ /**
+ * This is easy since the ftv items assigned the _parent property when making
+ * the child lists
+ */
+ getParentIndex: function ftv_getParentIndex(aIndex) {
+ return this._rowMap.indexOf(this._rowMap[aIndex]._parent);
+ },
+
+ /**
+ * This is duplicative for our normal ftv views, but custom data-displays may
+ * want to do something special here
+ */
+ getRowProperties: function ftv_getRowProperties(aRow) {
+ return this._rowMap[aRow].getProperties();
+ },
+
+ /**
+ * Check whether there are any more rows with our level before the next row
+ * at our parent's level
+ */
+ hasNextSibling: function ftv_hasNextSibling(aIndex, aNextIndex) {
+ var currentLevel = this._rowMap[aIndex].level;
+ for (var i = aNextIndex + 1; i < this._rowMap.length; i++) {
+ if (this._rowMap[i].level == currentLevel)
+ return true;
+ if (this._rowMap[i].level < currentLevel)
+ return false;
+ }
+ return false;
+ },
+
+ /**
+ * All folders are containers, so we can drag drop messages to them.
+ */
+ isContainer: function ftv_isContainer(aIndex) {
+ return true;
+ },
+
+ isContainerEmpty: function ftv_isContainerEmpty(aIndex) {
+ // If the folder has no children, the container is empty.
+ return !this._rowMap[aIndex].children.length;
+ },
+
+ /**
+ * Just look at the ftvItem here
+ */
+ isContainerOpen: function ftv_isContainerOpen(aIndex) {
+ return this._rowMap[aIndex].open;
+ },
+ getSummarizedCounts: function(aIndex, aColName) {
+ return this._rowMap[aIndex]._summarizedCounts.get(aColName);
+ },
+ isEditable: function ftv_isEditable(aRow, aCol) {
+ // We don't support editing rows in the tree yet. We may want to later as
+ // an easier way to rename folders.
+ return false;
+ },
+ isSeparator: function ftv_isSeparator(aIndex) {
+ // There are no separators in our trees.
+ return false;
+ },
+ isSorted: function ftv_isSorted() {
+ // We do our own customized sorting.
+ return false;
+ },
+ setTree: function ftv_setTree(aTree) {
+ this._tree = aTree;
+ },
+
+ /**
+ * Opens or closes a folder with children. The logic here is a bit hairy, so
+ * be very careful about changing anything.
+ */
+ toggleOpenState: function ftv_toggleOpenState(aIndex) {
+ this._toggleRow(aIndex, true);
+ },
+
+ recursivelyAddToMap: function ftv_recursivelyAddToMap(aChild, aNewIndex) {
+ // When we add sub-children, we're going to need to increase our index
+ // for the next add item at our own level.
+ let count = 0;
+ if (aChild.children.length && aChild.open) {
+ for (let [i, child] of Array.from(this._rowMap[aNewIndex].children).entries()) {
+ count++;
+ let index = Number(aNewIndex) + Number(i) + 1;
+ this._rowMap.splice(index, 0, child);
+
+ let kidsAdded = this.recursivelyAddToMap(child, index);
+ count += kidsAdded;
+ // Somehow the aNewIndex turns into a string without this.
+ aNewIndex = Number(aNewIndex) + kidsAdded;
+ }
+ }
+ return count;
+ },
+
+ _toggleRow: function toggleRow(aIndex, aExpandServer)
+ {
+ // Ok, this is a bit tricky.
+ this._rowMap[aIndex].open = !this._rowMap[aIndex].open;
+ if (!this._rowMap[aIndex].open) {
+ // We're closing the current container. Remove the children.
+
+ // Note that we can't simply splice out children.length, because some of
+ // them might have children too. Find out how many items we're actually
+ // going to splice.
+ let count = 0;
+ let i = aIndex + 1;
+ let row = this._rowMap[i];
+ while (row && row.level > this._rowMap[aIndex].level) {
+ count++;
+ row = this._rowMap[++i];
+ }
+ this._rowMap.splice(aIndex + 1, count);
+
+ // Remove us from the persist map.
+ this._persistItemClosed(this._rowMap[aIndex].id);
+
+ // Notify the tree of changes.
+ if (this._tree) {
+ this._tree.rowCountChanged(aIndex + 1, (-1) * count);
+ this._tree.invalidateRow(aIndex);
+ }
+ } else {
+ // We're opening the container. Add the children to our map.
+
+ // Note that these children may have been open when we were last closed,
+ // and if they are, we also have to add those grandchildren to the map.
+ let oldCount = this._rowMap.length;
+ this.recursivelyAddToMap(this._rowMap[aIndex], aIndex);
+
+ // Add this folder to the persist map.
+ this._persistItemOpen(this._rowMap[aIndex].id);
+
+ // Notify the tree of changes.
+ if (this._tree) {
+ this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
+ this._tree.invalidateRow(aIndex);
+ }
+
+ if (this._treeElement.getAttribute("simplelist") == "true")
+ return;
+
+ // If this was a server that was expanded, let it update its counts.
+ let folder = this._rowMap[aIndex]._folder;
+ if (aExpandServer) {
+ if (folder.isServer)
+ folder.server.performExpand(msgWindow);
+ else if (folder instanceof Ci.nsIMsgImapMailFolder)
+ folder.performExpand(msgWindow);
+ }
+ }
+ },
+
+ // We don't implement any of these at the moment.
+ performAction: function ftv_performAction(aAction) {},
+ performActionOnCell: function ftv_performActionOnCell(aAction, aRow, aCol) {},
+ performActionOnRow: function ftv_performActionOnRow(aAction, aRow) {},
+ selectionChanged: function ftv_selectionChanged() {},
+ setCellText: function ftv_setCellText(aRow, aCol, aValue) {},
+ setCellValue: function ftv_setCellValue(aRow, aCol, aValue) {},
+ getCellValue: function ftv_getCellValue(aRow, aCol) {},
+ getColumnProperties: function ftv_getColumnProperties(aCol) { return ""; },
+ getImageSrc: function ftv_getImageSrc(aRow, aCol) {},
+ getProgressMode: function ftv_getProgressMode(aRow, aCol) {},
+ cycleCell: function ftv_cycleCell(aRow, aCol) {},
+ cycleHeader: function ftv_cycleHeader(aCol) {},
+
+ // ****************** End of nsITreeView implementation **************** //
+
+ //
+ // WARNING: Everything below this point is considered private. Touch at your
+ // own risk.
+
+ /**
+ * This is an array of all possible modes for the folder tree. You should not
+ * modify this directly, but rather use registerFolderTreeMode.
+ *
+ * Internally each mode is defined separately. But in the UI we currently
+ * expose only the "base" name (see baseMode()) of the mode plus a
+ * "Compact view" option. The internal name of the mode to use is then
+ * constructed from the base name and "_compact" suffix if compact view is
+ * selected. See bug 978592.
+ */
+ _modeNames: ["all", "unread", "unread_compact", "favorite", "favorite_compact", "recent_compact"],
+ _modeDisplayNames: {},
+
+ /**
+ * This is a javascript map of which folders we had open, so that we can
+ * persist their state over-time. It is designed to be used as a JSON object.
+ */
+ _persistOpenMap: {},
+ _notPersistedModes: ["unread", "unread_compact", "favorite", "favorite_compact", "recent_compact"],
+
+ /**
+ * Iterate over the persistent list and open the items (folders) stored in it.
+ */
+ _restoreOpenStates: function ftv__persistOpenStates() {
+ let mode = this.mode;
+ // Remove any saved state of modes where open state should not be persisted.
+ // This is mostly for migration from older profiles that may have the info
+ // stored.
+ if (this._notPersistedModes.includes(mode)) {
+ delete this._persistOpenMap[mode];
+ }
+
+ let curLevel = 0;
+ let tree = this;
+ let map = tree._persistOpenMap[mode]; // may be undefined
+ function openLevel() {
+ let goOn = false;
+ // We can't use a js iterator because we're changing the array as we go.
+ // So fallback on old trick of going backwards from the end, which
+ // doesn't care when you add things at the end.
+ for (let i = tree._rowMap.length - 1; i >= 0; i--) {
+ let row = tree._rowMap[i];
+ if (row.level != curLevel)
+ continue;
+
+ // The initial state of all rows is closed,
+ // so toggle those we want open.
+ if (!map || map.includes(row.id)) {
+ tree._toggleRow(i, false);
+ goOn = true;
+ }
+ }
+
+ // If we opened up any new kids, we need to check their level as well.
+ curLevel++;
+ if (goOn)
+ openLevel();
+ }
+ openLevel();
+ },
+
+ /**
+ * Remove the item from the persistent list, meaning the item should
+ * be persisted as closed in the tree.
+ *
+ * @param aItemId The URI of the folder item.
+ */
+ _persistItemClosed: function ftv_unpersistItem(aItemId) {
+ let mode = this.mode;
+ if (this._notPersistedModes.includes(mode))
+ return;
+
+ // If the whole mode is not in the map yet,
+ // we can silently ignore the folder removal.
+ if (!this._persistOpenMap[mode])
+ return;
+
+ let persistMapIndex = this._persistOpenMap[mode].indexOf(aItemId);
+ if (persistMapIndex != -1)
+ this._persistOpenMap[mode].splice(persistMapIndex, 1);
+ },
+
+ /**
+ * Add the item from the persistent list, meaning the item should
+ * be persisted as open (expanded) in the tree.
+ *
+ * @param aItemId The URI of the folder item.
+ */
+ _persistItemOpen: function ftv_persistItem(aItemId) {
+ let mode = this.mode;
+ if (this._notPersistedModes.includes(mode))
+ return;
+
+ if (!this._persistOpenMap[mode])
+ this._persistOpenMap[mode] = [];
+
+ if (!this._persistOpenMap[mode].includes(aItemId))
+ this._persistOpenMap[mode].push(aItemId);
+ },
+
+ _tree: null,
+ selection: null,
+ /**
+ * An array of ftvItems, where each item corresponds to a row in the tree
+ */
+ _rowMap: null,
+
+ /**
+ * Completely discards the current tree and rebuilds it based on current
+ * settings
+ */
+ _rebuild: function ftv__rebuild() {
+ let newRowMap;
+ try {
+ newRowMap = this._modes[this.mode].generateMap(this);
+ } catch(ex) {
+ Services.console.logStringMessage("generator " + this.mode +
+ " failed with exception: " + ex);
+ this.mode = kDefaultMode;
+ newRowMap = this._modes[this.mode].generateMap(this);
+ }
+ let selectedFolders = this.getSelectedFolders();
+ if (this.selection)
+ this.selection.clearSelection();
+ // There's a chance the call to the map generator altered this._rowMap, so
+ // evaluate oldCount after calling it rather than before.
+ let oldCount = this._rowMap ? this._rowMap.length : null;
+ this._rowMap = newRowMap;
+
+ this._treeElement.dispatchEvent(new Event("mapRebuild",
+ { bubbles: true, cancelable: false }));
+
+ if (this._tree) {
+ if (oldCount !== null)
+ this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
+ this._tree.invalidate();
+ }
+
+ this._restoreOpenStates();
+ // Restore selection.
+ for (let folder of selectedFolders) {
+ if (folder) {
+ let index = this.getIndexOfFolder(folder);
+ if (index != null)
+ this.selection.toggleSelect(index);
+ }
+ }
+ },
+
+ _sortedAccounts: function ftv_getSortedAccounts() {
+ let accounts = FolderUtils.allAccountsSorted(true);
+
+ // Don't show deferred pop accounts.
+ accounts = accounts.filter(function isNotDeferred(a) {
+ let server = a.incomingServer;
+ return !(server instanceof Ci.nsIPop3IncomingServer &&
+ server.deferredToAccount);
+ });
+
+ return accounts;
+ },
+ /**
+ * Contains the set of modes registered with the folder tree, initially those
+ * included by default. This is a map from names of modes to their
+ * implementations of |IFolderTreeMode|.
+ */
+ _modes: {
+ /**
+ * The all mode returns all folders, arranged in a hierarchy
+ */
+ all: {
+ __proto__: IFolderTreeMode,
+
+ generateMap: function(ftv) {
+ let accounts = gFolderTreeView._sortedAccounts();
+ // Force each root folder to do its local subfolder discovery.
+ MailUtils.discoverFolders();
+
+ return accounts.map(acct => new ftvItem(acct.incomingServer.rootFolder));
+ }
+ },
+
+ /**
+ * The unread mode returns all folders that are not root-folders and that
+ * have unread items. Also always keep the currently selected folder
+ * so it doesn't disappear under the user.
+ * It also includes parent folders of the Unread folders so the hierarchy
+ * shown.
+ */
+ unread: {
+ __proto__: IFolderTreeMode,
+
+ generateMap: function(ftv) {
+ let filterUnread = function filterUnread(aFolder) {
+ let currentFolder = gFolderTreeView.getSelectedFolders()[0];
+ return ((aFolder.getNumUnread(true) > 0) ||
+ (aFolder == currentFolder));
+ }
+
+ let accounts = gFolderTreeView._sortedAccounts();
+ // Force each root folder to do its local subfolder discovery.
+ MailUtils.discoverFolders();
+
+ let unreadRootFolders = [];
+ for (let acct of accounts) {
+ let rootFolder = acct.incomingServer.rootFolder;
+ // Add rootFolders of accounts that contain at least one Favorite
+ // folder.
+ if (rootFolder.getNumUnread(true) > 0)
+ unreadRootFolders.push(new ftvItem(rootFolder, filterUnread));
+ }
+
+ return unreadRootFolders;
+ },
+
+ handleChangedIntProperty: function(aItem, aProperty, aOld, aNew) {
+ // We want to rebuild only if we have a newly unread folder
+ // and we didn't already have the folder.
+ if (aProperty == "TotalUnreadMessages" && aOld == 0 && aNew > 0 &&
+ gFolderTreeView.getIndexOfFolder(aItem) == null) {
+ gFolderTreeView._rebuild();
+ return true;
+ }
+ return false;
+ }
+ },
+
+ /**
+ * A variant of the 'unread' mode above. This does not include the parent
+ * folders and the unread folders are shown in a flat list with no
+ * hierarchy.
+ */
+ unread_compact: {
+ __proto__: IFolderTreeMode,
+
+ generateMap: function(ftv) {
+ let map = [];
+ let currentFolder = gFolderTreeView.getSelectedFolders()[0];
+ for (let folder of ftv._enumerateFolders) {
+ if ((!folder.isServer && folder.getNumUnread(false) > 0) ||
+ (folder == currentFolder))
+ map.push(new ftvItem(folder));
+ }
+
+ // There are no children in this view!
+ for (let folder of map) {
+ folder.__defineGetter__("children", () => []);
+ folder.addServerName = true;
+ }
+ sortFolderItems(map);
+ return map;
+ },
+
+ getParentOfFolder: function(aFolder) {
+ // This is a flat view, so no folders have parents.
+ return null;
+ },
+
+ handleChangedIntProperty: function(aItem, aProperty, aOld, aNew) {
+ // We want to rebuild only if we have a newly unread folder
+ // and we didn't already have the folder.
+ if (aProperty == "TotalUnreadMessages" && aOld == 0 && aNew > 0 &&
+ gFolderTreeView.getIndexOfFolder(aItem) == null) {
+ gFolderTreeView._rebuild();
+ return true;
+ }
+ return false;
+ }
+ },
+
+ /**
+ * The favorites mode returns all folders whose flags are set to include
+ * the favorite flag.
+ * It also includes parent folders of the Unread folders so the hierarchy
+ * shown.
+ */
+ favorite: {
+ __proto__: IFolderTreeMode,
+
+ generateMap: function(ftv) {
+ let accounts = gFolderTreeView._sortedAccounts();
+ // Force each root folder to do its local subfolder discovery.
+ MailUtils.discoverFolders();
+
+ let favRootFolders = [];
+ let filterFavorite = function filterFavorite(aFolder) {
+ return aFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Favorite) != null;
+ }
+ for (let acct of accounts) {
+ let rootFolder = acct.incomingServer.rootFolder;
+ // Add rootFolders of accounts that contain at least one Favorite folder.
+ if (filterFavorite(rootFolder))
+ favRootFolders.push(new ftvItem(rootFolder, filterFavorite));
+ }
+
+ return favRootFolders;
+ },
+
+ handleChangedIntProperty: function(aItem, aProperty, aOld, aNew) {
+ // We want to rebuild if the favorite status of a folder changed.
+ if (aProperty == "FolderFlag" &&
+ ((aOld & Ci.nsMsgFolderFlags.Favorite) !=
+ (aNew & Ci.nsMsgFolderFlags.Favorite))) {
+ gFolderTreeView._rebuild();
+ return true;
+ }
+ return false;
+ }
+ },
+
+ /**
+ * A variant of the 'favorite' mode above. This does not include the parent
+ * folders and the unread folders are shown in a compact list with no
+ * hierarchy.
+ */
+ favorite_compact: {
+ __proto__: IFolderTreeMode,
+
+ generateMap: function(ftv) {
+ let faves = [];
+ for (let folder of ftv._enumerateFolders) {
+ if (folder.flags & Ci.nsMsgFolderFlags.Favorite)
+ faves.push(new ftvItem(folder));
+ }
+
+ // We want to display the account name alongside folders that have
+ // duplicated folder names.
+ let uniqueNames = new Set(); // set of folder names seen at least once
+ let dupeNames = new Set(); // set of folders seen at least twice
+ for (let item of faves) {
+ let name = item._folder.abbreviatedName.toLocaleLowerCase();
+ if (uniqueNames.has(name)) {
+ if (!dupeNames.has(name))
+ dupeNames.add(name);
+ } else {
+ uniqueNames.add(name);
+ }
+ }
+
+ // There are no children in this view!
+ for (let item of faves) {
+ let name = item._folder.abbreviatedName.toLocaleLowerCase();
+ item.__defineGetter__("children", () => []);
+ item.addServerName = dupeNames.has(name);
+ }
+ sortFolderItems(faves);
+ return faves;
+ },
+
+ getParentOfFolder: function(aFolder) {
+ // This is a flat view, so no folders have parents.
+ return null;
+ },
+
+ handleChangedIntProperty: function(aItem, aProperty, aOld, aNew) {
+ // We want to rebuild if the favorite status of a folder changed.
+ if (aProperty == "FolderFlag" &&
+ ((aOld & Ci.nsMsgFolderFlags.Favorite) !=
+ (aNew & Ci.nsMsgFolderFlags.Favorite))) {
+ gFolderTreeView._rebuild();
+ return true;
+ }
+ return false;
+ }
+ },
+
+ /**
+ * The recent mode is a flat view of the 15 most recently used folders
+ */
+ recent_compact: {
+ __proto__: IFolderTreeMode,
+
+ generateMap: function(ftv) {
+ const MAXRECENT = 15;
+
+ // Get 15 (MAXRECENT) most recently accessed folders.
+ let recentFolders = FolderUtils.getMostRecentFolders(
+ ftv._enumerateFolders,
+ MAXRECENT,
+ "MRUTime",
+ null
+ );
+
+ // Sort the folder names alphabetically.
+ recentFolders.sort(function rf_sort(a, b){
+ let aLabel = a.prettyName;
+ let bLabel = b.prettyName;
+ if (aLabel == bLabel) {
+ aLabel = a.server.prettyName;
+ bLabel = b.server.prettyName;
+ }
+ return FolderUtils.folderNameCompare(aLabel, bLabel);
+ });
+
+ let items = recentFolders.map(f => new ftvItem(f));
+
+ // There are no children in this view!
+ // And we want to display the account name to distinguish folders w/
+ // the same name.
+ for (let folder of items) {
+ folder.__defineGetter__("children", () => []);
+ folder.addServerName = true;
+ }
+
+ return items;
+ },
+
+ getParentOfFolder: function(aFolder) {
+ // This is a flat view, so no folders have parents.
+ return null;
+ }
+ }
+ },
+
+ /**
+ * This is a helper attribute that simply returns a flat list of all folders
+ */
+ get _enumerateFolders() {
+ let folders = [];
+
+ for (let server of fixIterator(MailServices.accounts.allServers, Ci.nsIMsgIncomingServer)) {
+ // Skip deferred accounts.
+ if (server instanceof Ci.nsIPop3IncomingServer &&
+ server.deferredToAccount)
+ continue;
+
+ let rootFolder = server.rootFolder;
+ folders.push(rootFolder);
+ this.addSubFolders(rootFolder, folders);
+ }
+ return folders;
+ },
+
+ /**
+ * This is a recursive function to add all subfolders to the array. It
+ * assumes that the passed in folder itself has already been added.
+ *
+ * @param aFolder the folder whose subfolders should be added
+ * @param folders the array to add the folders to.
+ */
+ addSubFolders : function ftv_addSubFolders (folder, folders) {
+ for (let f of fixIterator(folder.subFolders, Ci.nsIMsgFolder)) {
+ folders.push(f);
+ this.addSubFolders(f, folders);
+ }
+ },
+
+ /**
+ * This updates the rowmap and invalidates the right row(s) in the tree
+ */
+ _addChildToView: function ftl_addChildToView(aParent, aParentIndex, aNewChild) {
+ if (aParent.open) {
+ let newChildIndex;
+ let newChildNum = aParent._children.indexOf(aNewChild);
+ // Only child - go right after our parent.
+ if (newChildNum == 0) {
+ newChildIndex = Number(aParentIndex) + 1
+ }
+ // If we're not the last child, insert ourselves before the next child.
+ else if (newChildNum < aParent._children.length - 1) {
+ newChildIndex = this.getIndexOfFolder(aParent._children[Number(newChildNum) + 1]._folder);
+ }
+ // Otherwise, go after the last child.
+ else {
+ let lastChild = aParent._children[newChildNum - 1];
+ let lastChildIndex = this.getIndexOfFolder(lastChild._folder);
+ newChildIndex = Number(lastChildIndex) + 1;
+ while (newChildIndex < this.rowCount &&
+ this._rowMap[newChildIndex].level > this._rowMap[lastChildIndex].level)
+ newChildIndex++;
+ }
+ this._rowMap.splice(newChildIndex, 0, aNewChild);
+ this._tree.rowCountChanged(newChildIndex, 1);
+ } else {
+ this._tree.invalidateRow(aParentIndex);
+ }
+ },
+
+ /**
+ * This is our implementation of nsIMsgFolderListener to watch for changes
+ */
+ onFolderAdded: function ftl_add(aParentItem, aItem) {
+ // Ignore this item if it's not a folder, or we knew about it.
+ if (this.getIndexOfFolder(aItem) != null)
+ return;
+
+ // If no parent, this is an account, so let's rebuild.
+ if (!aParentItem) {
+ if (!aItem.server.hidden) // Ignore hidden server items.
+ this._rebuild();
+ return;
+ }
+ this._modes[this._mode].onFolderAdded(
+ aParentItem.QueryInterface(Ci.nsIMsgFolder), aItem);
+ },
+ onMessageAdded: function(parentFolder, msg) {},
+
+ addFolder: function ftl_add_folder(aParentItem, aItem) {
+ // This intentionally adds any new folder even if it would not pass the
+ // _filterFunction. The idea is that the user can add new folders even
+ // in modes like "unread" or "favorite" and could wonder why they
+ // are not appearing (forgetting they do not meet the criteria of the view).
+ // The folders will be hidden properly next time the view is rebuilt.
+ let parentIndex = this.getIndexOfFolder(aParentItem);
+ let parent = this._rowMap[parentIndex];
+ if (!parent)
+ return;
+
+ // Getting these children might have triggered our parent to build its
+ // array just now, in which case the added item will already exist.
+ let children = parent.children;
+ var newChild;
+ for (let child of children) {
+ if (child._folder == aItem) {
+ newChild = child;
+ break;
+ }
+ }
+ if (!newChild) {
+ newChild = new ftvItem(aItem);
+ parent.children.push(newChild);
+ newChild._level = parent._level + 1;
+ newChild._parent = parent;
+ sortFolderItems(parent._children);
+ }
+ // If the parent is open, add the new child into the folder pane.
+ // Otherwise, just invalidate the parent row. Note that this code doesn't
+ // get called for the smart folder case.
+ if (!parent.open) {
+ // Special case adding a special folder when the parent is collapsed.
+ // Expand the parent so the user can see the special child.
+ // Expanding the parent is sufficient to add the folder to the view,
+ // because either we knew about it, or we will have added a child item
+ // for it above.
+ if (newChild._folder.flags & Ci.nsMsgFolderFlags.SpecialUse) {
+ this._toggleRow(parentIndex, false);
+ return;
+ }
+ }
+ this._addChildToView(parent, parentIndex, newChild);
+ },
+
+ onFolderRemoved: function ftl_remove(aRDFParentItem, aItem) {
+ this._persistItemClosed(aItem.URI);
+
+ let index = this.getIndexOfFolder(aItem);
+ if (index == null)
+ return;
+ // Forget our parent's children; they'll get rebuilt.
+ if (aRDFParentItem && this._rowMap[index]._parent)
+ this._rowMap[index]._parent._children = null;
+ let kidCount = 1;
+ let walker = Number(index) + 1;
+ while (walker < this.rowCount &&
+ this._rowMap[walker].level > this._rowMap[index].level) {
+ walker++;
+ kidCount++;
+ }
+ this._rowMap.splice(index, kidCount);
+ this._tree.rowCountChanged(index, -1 * kidCount);
+ this._tree.invalidateRow(index);
+ },
+
+ onMessageRemoved: function(parentFolder, msg) {},
+
+ onFolderPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ onFolderIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ // First try mode specific handling of the changed property.
+ if (this._modes[this.mode].handleChangedIntProperty(aItem, aProperty, aOld,
+ aNew))
+ return;
+
+ if (aItem instanceof Ci.nsIMsgFolder) {
+ let index = this.getIndexOfFolder(aItem);
+ let folder = aItem;
+ let folderTreeMode = this._modes[this._mode];
+ // Look for first visible ancestor.
+ while (index == null) {
+ folder = folderTreeMode.getParentOfFolder(folder);
+ if (!folder)
+ break;
+ index = this.getIndexOfFolder(folder);
+ }
+ if (index != null)
+ this._tree.invalidateRow(index);
+ }
+ },
+
+ onFolderBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ let index = this.getIndexOfFolder(aItem);
+ if (index != null)
+ this._tree.invalidateRow(index);
+ },
+ onFolderUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ onFolderPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
+ onFolderEvent: function(aFolder, aEvent) {
+ let index = this.getIndexOfFolder(aFolder);
+ if (index != null)
+ this._tree.invalidateRow(index);
+ }
+};
+
+/**
+ * The ftvItem object represents a single row in the tree view. Because I'm lazy
+ * I'm just going to define the expected interface here. You are free to return
+ * an alternative object, provided that it matches this interface:
+ *
+ * id (attribute) - a unique string for this object. Must persist over sessions
+ * text (attribute) - the text to display in the tree
+ * level (attribute) - the level in the tree to display the item at
+ * open (rw, attribute) - whether or not this container is open
+ * children (attribute) - an array of child items also conforming to this spec
+ * getProperties (function) - a call from getRowProperties or getCellProperties
+ * for this item will be passed into this function
+ * command (function) - this function will be called when the item is double-
+ * clicked
+ */
+
+/**
+ * The ftvItem constructor takes these arguments:
+ *
+ * @param aFolder The folder attached to this row in the tree.
+ * @param aFolderFilter When showing children folders of this one,
+ * only show those that pass this filter function.
+ * If unset, show all subfolders.
+ */
+function ftvItem(aFolder, aFolderFilter) {
+ this._folder = aFolder;
+ this._level = 0;
+ this._parent = null;
+ this._folderFilter = aFolderFilter;
+ // The map contains message counts for each folder column.
+ // Each key is a column name (ID) from the folder tree.
+ // Value is an array of the format
+ // "[value_for_folder, value_for_all_its_subfolders]".
+ this._summarizedCounts = new Map();
+}
+
+ftvItem.prototype = {
+ open: false,
+ addServerName: false,
+ useServerNameOnly: false,
+
+ get id() {
+ return this._folder.URI;
+ },
+ get text() {
+ return this.getText("folderNameCol");
+ },
+
+ getText(aColName) {
+ // Only show counts / total size of subtree if the pref is set,
+ // we are in "All folders" mode and this folder row is not expanded.
+ gFolderStatsHelpers.sumSubfolders =
+ gFolderStatsHelpers.sumSubfoldersPref &&
+ (gFolderTreeView.mode == kDefaultMode) &&
+ this._folder.hasSubFolders && !this.open;
+
+ this._summarizedCounts.delete(aColName);
+ switch (aColName) {
+ case "folderNameCol":
+ let text;
+ if (this.useServerNameOnly)
+ text = this._folder.server.prettyName;
+ else {
+ text = this._folder.abbreviatedName;
+ if (this.addServerName) {
+ text = gFolderTreeView.messengerBundle.getFormattedString(
+ "folderWithAccount", [text, this._folder.server.prettyName]);
+ }
+ }
+
+ // In a simple list tree we don't care for attributes other than folder
+ // name.
+ if (gFolderTreeView._treeElement.getAttribute("simplelist") == "true")
+ return text;
+
+ // If the unread column is shown, we don't need to add the count
+ // to the name.
+ if (!document.getElementById("folderUnreadCol").hidden)
+ return text;
+
+ let unread = this._folder.getNumUnread(false);
+ let totalUnread = gFolderStatsHelpers.sumSubfolders ?
+ this._folder.getNumUnread(true) : unread;
+ this._summarizedCounts.set(aColName, [unread, totalUnread - unread]);
+ if (totalUnread > 0) {
+ text = gFolderTreeView.messengerBundle.getFormattedString(
+ "folderWithUnreadMsgs",
+ [text,
+ gFolderStatsHelpers.addSummarizedPrefix(totalUnread,
+ unread != totalUnread)]);
+ }
+ return text;
+
+ case "folderUnreadCol":
+ let folderUnread = this._folder.getNumUnread(false);
+ let subfoldersUnread = gFolderStatsHelpers.sumSubfolders ?
+ this._folder.getNumUnread(true) : folderUnread;
+ this._summarizedCounts.set(aColName, [folderUnread,
+ subfoldersUnread - folderUnread]);
+ return gFolderStatsHelpers
+ .fixNum(subfoldersUnread, folderUnread != subfoldersUnread);
+
+ case "folderTotalCol":
+ let folderTotal = this._folder.getTotalMessages(false);
+ let subfoldersTotal = gFolderStatsHelpers.sumSubfolders ?
+ this._folder.getTotalMessages(true) : folderTotal;
+ this._summarizedCounts.set(aColName, [folderTotal,
+ subfoldersTotal - folderTotal]);
+ return gFolderStatsHelpers
+ .fixNum(subfoldersTotal, folderTotal != subfoldersTotal);
+
+ case "folderSizeCol":
+ let thisFolderSize = gFolderStatsHelpers.getFolderSize(this._folder);
+ let subfoldersSize = gFolderStatsHelpers.sumSubfolders ?
+ gFolderStatsHelpers.getSubfoldersSize(this._folder) : 0;
+
+ if (subfoldersSize == gFolderStatsHelpers.kUnknownSize ||
+ thisFolderSize == gFolderStatsHelpers.kUnknownSize)
+ return gFolderStatsHelpers.kUnknownSize;
+
+ let totalSize = thisFolderSize + subfoldersSize;
+ if (totalSize == 0)
+ return "";
+
+ let [totalText, folderUnit] = gFolderStatsHelpers.formatFolderSize(totalSize);
+ let folderText = (subfoldersSize == 0) ? totalText :
+ gFolderStatsHelpers.formatFolderSize(thisFolderSize, folderUnit)[0];
+ let subfoldersText = (subfoldersSize == 0) ? "" :
+ gFolderStatsHelpers.formatFolderSize(subfoldersSize, folderUnit)[0];
+ this._summarizedCounts.set(aColName, [folderText, subfoldersText]);
+ return gFolderStatsHelpers
+ .addSummarizedPrefix(totalText, totalSize != thisFolderSize);
+
+ default:
+ return "";
+ }
+ },
+
+ get level() {
+ return this._level;
+ },
+
+ getProperties: function (aColumn) {
+ if (aColumn && aColumn.id != "folderNameCol")
+ return "";
+
+ let properties = FolderUtils.getFolderProperties(this._folder, this.open);
+
+ return properties;
+ },
+
+ command: function fti_command() {
+ if (!Services.prefs.getBoolPref("mailnews.reuse_thread_window2")) {
+ MsgOpenNewWindowForFolder(this._folder.URI, -1 /* key */);
+ }
+ },
+
+ _children: null,
+ get children() {
+ // We're caching our child list to save perf.
+ if (!this._children) {
+ let iter;
+ try {
+ iter = fixIterator(this._folder.subFolders, Ci.nsIMsgFolder);
+ } catch (ex) {
+ Services.console.logStringMessage("Discovering children for " +
+ this._folder.URI + " failed with " +
+ "exception: " + ex);
+ iter = [];
+ }
+ this._children = [];
+ // Out of all children, only keep those that match the _folderFilter
+ // and those that contain such children.
+ for (let folder of iter) {
+ if (!this._folderFilter || this._folderFilter(folder)) {
+ this._children.push(new ftvItem(folder, this._folderFilter));
+ }
+ }
+ sortFolderItems(this._children);
+ // Each child is a level one below us.
+ for (let child of this._children) {
+ child._level = this._level + 1;
+ child._parent = this;
+ }
+ }
+ return this._children;
+ }
+};
+
+/**
+ * This handles the invocation of most commands dealing with folders, based off
+ * of the current selection, or a passed in folder.
+ */
+var gFolderTreeController = {
+ /**
+ * Opens the dialog to create a new sub-folder, and creates it if the user
+ * accepts
+ *
+ * @param aParent (optional) the parent for the new subfolder
+ */
+ newFolder(aParent) {
+ let folder = aParent || GetSelectedMsgFolders()[0];
+
+ // Make sure we actually can create subfolders.
+ if (!folder.canCreateSubfolders) {
+ // Check if we can create them at the root.
+ let rootMsgFolder = folder.server.rootMsgFolder;
+ if (rootMsgFolder.canCreateSubfolders)
+ folder = rootMsgFolder;
+ else // just use the default account
+ folder = GetDefaultAccountRootFolder();
+ }
+
+ let dualUseFolders = true;
+ if (folder.server instanceof Ci.nsIImapIncomingServer)
+ dualUseFolders = folder.server.dualUseFolders;
+
+ function newFolderCallback(aName, aFolder) {
+ // createSubfolder can throw an exception, causing the newFolder dialog
+ // to not close and wait for another input.
+ // TODO: Rewrite this logic and move the opening of alert dialogs from
+ // nsMsgLocalMailFolder::CreateSubfolderInternal to here (bug 831190#c16).
+ if (aName)
+ aFolder.createSubfolder(aName, msgWindow);
+ }
+
+ window.openDialog("chrome://messenger/content/newFolderDialog.xul",
+ "",
+ "chrome,modal,centerscreen",
+ {folder: folder,
+ dualUseFolders: dualUseFolders,
+ okCallback: newFolderCallback});
+ },
+
+ /**
+ * Opens the dialog to edit the properties for a folder
+ *
+ * @param aTabID (optional) the tab to show in the dialog
+ * @param aFolder (optional) the folder to edit, if not the selected one
+ */
+ editFolder(aTabID, aFolder) {
+ let folder = aFolder || GetSelectedMsgFolders()[0];
+
+ // If a server is selected, view settings for that account.
+ if (folder.isServer) {
+ MsgAccountManager(null, folder.server);
+ return;
+ }
+
+ if (folder.getFlag(Ci.nsMsgFolderFlags.Virtual)) {
+ // virtual folders get their own property dialog that contains all of the
+ // search information related to the virtual folder.
+ this.editVirtualFolder(folder);
+ return;
+ }
+
+ let title = gFolderTreeView.messengerBundle.getString("folderProperties");
+
+ function editFolderCallback(aNewName, aOldName, aUri) {
+ if (aNewName != aOldName)
+ folder.rename(aNewName, msgWindow);
+ }
+
+ function rebuildSummary(msgFolder) {
+ if (msgFolder.locked) {
+ msgFolder.throwAlertMsg("operationFailedFolderBusy", msgWindow);
+ return;
+ }
+ if (msgFolder.supportsOffline) {
+ // Remove the offline store, if any.
+ let offlineStore = msgFolder.filePath;
+ // XXX todo: figure out how to delete a maildir directory async. This
+ // delete causes main thread lockup for large maildir folders.
+ if (offlineStore.exists())
+ offlineStore.remove(true);
+ }
+
+ // Send a notification that we are triggering a database rebuild.
+ MailServices.mfn.notifyItemEvent(folder, "FolderReindexTriggered", null,
+ null);
+
+ msgFolder.msgDatabase.summaryValid = false;
+
+ try {
+ msgFolder.closeAndBackupFolderDB("");
+ }
+ catch(e) {
+ // In a failure, proceed anyway since we're dealing with problems
+ msgFolder.ForceDBClosed();
+ }
+ // these two lines will cause the thread pane to get reloaded
+ // when the download/reparse is finished. Only do this
+ // if the selected folder is loaded (i.e., not thru the
+ // context menu on a non-loaded folder).
+ if (msgFolder == GetLoadedMsgFolder()) {
+ gRerootOnFolderLoad = true;
+ gCurrentFolderToReroot = msgFolder.URI;
+ }
+ msgFolder.updateFolder(msgWindow);
+ }
+
+ window.openDialog("chrome://messenger/content/folderProps.xul",
+ "", "chrome,modal,centerscreen",
+ {folder: folder, serverType: folder.server.type,
+ msgWindow: msgWindow, title: title,
+ okCallback: editFolderCallback, tabID: aTabID,
+ name: folder.prettyName,
+ rebuildSummaryCallback: rebuildSummary});
+ },
+
+ /**
+ * Opens the dialog to rename a particular folder, and does the renaming if
+ * the user clicks OK in that dialog
+ *
+ * @param aFolder (optional) the folder to rename, if different than the
+ * currently selected one
+ */
+ renameFolder(aFolder) {
+ let folder = aFolder || GetSelectedMsgFolders()[0];
+
+ let controller = this;
+ function renameCallback(aName, aUri) {
+ if (aUri != folder.URI)
+ Cu.reportError("got back a different folder to rename!");
+
+ controller._resetThreadPane();
+ let folderTree = document.getElementById("folderTree");
+ folderTree.view.selection.clearSelection();
+
+ folder.rename(aName, msgWindow);
+ }
+
+ window.openDialog("chrome://messenger/content/renameFolderDialog.xul",
+ "", "chrome,modal,centerscreen",
+ {preselectedURI: folder.URI,
+ okCallback: renameCallback, name: folder.prettyName});
+ },
+
+ /**
+ * Deletes a folder from its parent. Also handles unsubscribe from newsgroups
+ * if the selected folder/s happen to be nntp.
+ *
+ * @param aFolder (optional) the folder to delete, if not the selected one
+ */
+ deleteFolder(aFolder) {
+ let folders = aFolder ? [aFolder] : GetSelectedMsgFolders();
+ let prompt = Services.prompt;
+ for (let folder of folders) {
+ // For newsgroups, "delete" means "unsubscribe".
+ if (folder.server.type == "nntp" &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual)) {
+ MsgUnsubscribe([folder]);
+ continue;
+ }
+
+ let canDelete = folder.isSpecialFolder(Ci.nsMsgFolderFlags.Junk, false) ?
+ CanRenameDeleteJunkMail(folder.URI) : folder.deletable;
+ if (!canDelete)
+ continue;
+
+ if (folder.getFlag(Ci.nsMsgFolderFlags.Virtual)) {
+ let confirmation = gMessengerBundle.getString("confirmSavedSearchDeleteMessage");
+ let title = gMessengerBundle.getString("confirmSavedSearchDeleteTitle");
+ let buttonTitle = gMessengerBundle.getString("confirmSavedSearchDeleteButton");
+ let buttonFlags = prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
+ prompt.BUTTON_TITLE_CANCEL * prompt.BUTTON_POS_1;
+ if (prompt.confirmEx(window, title, confirmation, buttonFlags, buttonTitle,
+ "", "", "", {}) != 0) /* the yes button is in position 0 */
+ continue;
+ if (gCurrentVirtualFolderUri == folder.URI)
+ gCurrentVirtualFolderUri = null;
+ }
+
+ // We can delete this folder.
+ try {
+ folder.deleteSelf(msgWindow);
+ }
+ // Ignore known errors from canceled warning dialogs.
+ catch (ex) {
+ const NS_MSG_ERROR_COPY_FOLDER_ABORTED = 0x8055001a;
+ if (ex.result != NS_MSG_ERROR_COPY_FOLDER_ABORTED) {
+ throw ex;
+ }
+ }
+ }
+ },
+
+ /**
+ * Prompts the user to confirm and empties the trash for the selected folder.
+ * The folder and its children are only emptied if it has the proper Trash
+ * flag.
+ *
+ * @param aFolder (optional) The trash folder to empty. If unspecified or not
+ * a trash folder, the currently selected server's
+ * trash folder is used.
+ */
+ emptyTrash(aFolder) {
+ let folder = aFolder || GetSelectedMsgFolders()[0];
+ if (!folder.getFlag(Ci.nsMsgFolderFlags.Trash))
+ folder = folder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ if (!folder)
+ return;
+
+ if (this._checkConfirmationPrompt("emptyTrash"))
+ folder.emptyTrash(null);
+ },
+
+ /**
+ * Deletes everything (folders and messages) in the selected folder.
+ * The folder is only emptied if it has the proper Junk flag.
+ *
+ * @param aFolder (optional) The folder to empty. If unspecified, the
+ * currently selected folder is used, if it
+ * is junk.
+ */
+ emptyJunk(aFolder) {
+ let folder = aFolder || GetSelectedMsgFolders()[0];
+
+ if (!folder || !folder.getFlag(Ci.nsMsgFolderFlags.Junk))
+ return;
+
+ if (!this._checkConfirmationPrompt("emptyJunk"))
+ return;
+
+ // Delete any sub-folders this folder might have.
+ for (let f of folder.subFolders) {
+ folder.propagateDelete(f, true);
+ }
+
+ // Now delete the messages.
+ folder.deleteMessages([...folder.messages], msgWindow, true, false, null, false);
+ },
+
+ /**
+ * Compacts either particular folder/s, or selected folders.
+ *
+ * @param aFolders (optional) the folders to compact, if different than the
+ * currently selected ones
+ */
+ compactFolders(aFolders) {
+ let folders = aFolders || GetSelectedMsgFolders();
+ for (let folder of folders) {
+ let isImapFolder = folder.server.type == "imap";
+ // Can't compact folders that have just been compacted
+ if (!isImapFolder && !folder.expungedBytes)
+ return;
+
+ // Reset thread pane for non-imap folders.
+ if (!isImapFolder && gDBView && gDBView.msgFolder == folder) {
+ this._resetThreadPane();
+ }
+
+ folder.compact(null, msgWindow);
+ }
+ },
+ /**
+ * Compacts all folders for accounts that the given folders belong
+ * to, or all folders for accounts of the currently selected folders.
+ *
+ * @param aFolders (optional) the folders for whose accounts we should compact
+ * all folders, if different than the currently
+ * selected ones
+ */
+ compactAllFoldersForAccount(aFolders) {
+ let folders = aFolders || GetSelectedMsgFolders();
+ for (let folder of folders) {
+ folder.compactAll(null, msgWindow);
+ // Reset thread pane for non-imap folders.
+ if (gDBView && folder.server.type != "imap")
+ this._resetThreadPane();
+ }
+ },
+
+ /**
+ * Opens the dialog to create a new virtual folder
+ *
+ * @param aName - the default name for the new folder
+ * @param aSearchTerms - the search terms associated with the folder
+ * @param aParent - the folder to run the search terms on
+ */
+ newVirtualFolder(aName, aSearchTerms, aParent) {
+ let folder = aParent || GetSelectedMsgFolders()[0];
+ if (!folder)
+ folder = GetDefaultAccountRootFolder();
+
+ let name = folder.prettyName;
+ if (aName)
+ name += "-" + aName;
+
+ window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
+ "", "chrome,modal,centerscreen",
+ {folder: folder, searchTerms: aSearchTerms,
+ newFolderName: name});
+ },
+
+ /**
+ * Opens the dialog to edit the properties for a virtual folder
+ *
+ * @param aFolder (optional) the folder to edit, if not the selected one
+ */
+ editVirtualFolder(aFolder) {
+ let folder = aFolder || GetSelectedMsgFolders()[0];
+
+ function editVirtualCallback(aURI) {
+ // we need to reload the folder if it is the currently loaded folder...
+ if (gMsgFolderSelected && aURI == gMsgFolderSelected.URI) {
+ // force the folder pane to reload the virtual folder
+ gMsgFolderSelected = null;
+ FolderPaneSelectionChange();
+ }
+ }
+ window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
+ "", "chrome,modal,centerscreen",
+ {folder: folder, editExistingFolder: true,
+ onOKCallback: editVirtualCallback,
+ msgWindow:msgWindow});
+ },
+
+ /**
+ * Opens a search window with the given folder, or the selected one if none
+ * is given.
+ *
+ * @param [aFolder] the folder to open the search window for, if different
+ * from the selected one
+ */
+ searchMessages(aFolder) {
+ MsgSearchMessages(aFolder || GetSelectedMsgFolders()[0]);
+ },
+
+ /**
+ * For certain folder commands, the thread pane needs to be invalidated, this
+ * takes care of doing so.
+ */
+ _resetThreadPane() {
+ if (gDBView)
+ gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
+
+ ClearThreadPaneSelection();
+ ClearThreadPane();
+ ClearMessagePane();
+ },
+
+ /**
+ * Prompts for confirmation, if the user hasn't already chosen the "don't ask
+ * again" option.
+ *
+ * @param aCommand - the command to prompt for
+ */
+ _checkConfirmationPrompt(aCommand) {
+ const kDontAskAgainPref = "mailnews." + aCommand + ".dontAskAgain";
+ // default to ask user if the pref is not set
+ if (!Services.prefs.getBoolPref(kDontAskAgainPref, false)) {
+ let checkbox = {value: false};
+ let choice = Services.prompt.confirmEx(
+ window,
+ gMessengerBundle.getString(aCommand + "Title"),
+ gMessengerBundle.getString(aCommand + "Message"),
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null, null, null,
+ gMessengerBundle.getString(aCommand + "DontAsk"),
+ checkbox);
+ if (checkbox.value)
+ Services.prefs.setBoolPref(kDontAskAgainPref, true);
+
+ if (choice != 0)
+ return false;
+ }
+ return true;
+ },
+}
+
+/**
+ * Sorts the passed in array of folder items using the folder sort key
+ *
+ * @param aFolders - the array of ftvItems to sort.
+ */
+function sortFolderItems (aFtvItems) {
+ function sorter(a, b) {
+ return a._folder.compareSortKeys(b._folder);
+ }
+ aFtvItems.sort(sorter);
+}
+
+var gFolderStatsHelpers = {
+ kUnknownSize: "-",
+ sumSubfoldersPref: false,
+ sumSubfolders: false,
+ sizeUnits: "",
+ kiloUnit: "KB",
+ megaUnit: "MB",
+
+ init: function() {
+ // We cache these values because the cells in the folder pane columns
+ // using these helpers can be redrawn often.
+ this.sumSubfoldersPref = Services.prefs.getBoolPref("mail.folderpane.sumSubfolders");
+ this.sizeUnits = Services.prefs.getCharPref("mail.folderpane.sizeUnits");
+ this.kiloUnit = gFolderTreeView.messengerBundle.getString("kiloByteAbbreviation2");
+ this.megaUnit = gFolderTreeView.messengerBundle.getString("megaByteAbbreviation2");
+ },
+
+ /**
+ * Add a prefix to denote the value is actually a sum of all the subfolders.
+ * The prefix is useful as this sum may not always be the exact sum of
+ * individual folders when they are shown expanded (due to rounding to a
+ * unit).
+ * E.g. folder1 600bytes -> 1KB, folder2 700bytes -> 1KB
+ * summarized at parent folder: 1300bytes -> 1KB
+ *
+ * @param aValue The value to be displayed.
+ * @param aSubfoldersContributed Boolean indicating whether subfolders
+ * contributed to the accumulated total value.
+ */
+ addSummarizedPrefix: function(aValue, aSubfoldersContributed) {
+ if (!this.sumSubfolders)
+ return aValue;
+
+ if (!aSubfoldersContributed)
+ return aValue;
+
+ return gFolderTreeView.messengerBundle.getFormattedString("folderSummarizedSymbolValue", [aValue]);
+ },
+
+ /**
+ * nsIMsgFolder uses -1 as a magic number to mean "I don't know". In those
+ * cases we indicate it to the user. The user has to open the folder
+ * so that the property is initialized from the DB.
+ *
+ * @param aNumber The number to translate for the user.
+ * @param aSubfoldersContributed Boolean indicating whether subfolders
+ * contributed to the accumulated total value.
+ */
+ fixNum: function(aNumber, aSubfoldersContributed) {
+ if (aNumber < 0)
+ return this.kUnknownSize;
+
+ return (aNumber == 0 ? ""
+ : this.addSummarizedPrefix(aNumber,
+ aSubfoldersContributed));
+ },
+
+ /**
+ * Get the size of the specified folder.
+ *
+ * @param aFolder The nsIMsgFolder to analyze.
+ */
+ getFolderSize: function(aFolder) {
+ let folderSize = 0;
+ try {
+ folderSize = aFolder.sizeOnDisk;
+ if (folderSize < 0)
+ return this.kUnknownSize;
+ } catch(ex) {
+ return this.kUnknownSize;
+ }
+ return folderSize;
+ },
+
+ /**
+ * Get the total size of all subfolders of the specified folder.
+ *
+ * @param aFolder The nsIMsgFolder to analyze.
+ */
+ getSubfoldersSize: function(aFolder) {
+ let folderSize = 0;
+ if (aFolder.hasSubFolders) {
+ let subFolders = aFolder.subFolders;
+ while (subFolders.hasMoreElements()) {
+ let subFolder = subFolders.getNext().QueryInterface(Ci.nsIMsgFolder);
+ let subSize = this.getFolderSize(subFolder);
+ let subSubSize = this.getSubfoldersSize(subFolder);
+ if (subSize == this.kUnknownSize || subSubSize == this.kUnknownSize)
+ return subSize;
+
+ folderSize += subSize + subSubSize;
+ }
+ }
+ return folderSize;
+ },
+
+ /**
+ * Format the given folder size into a string with an appropriate unit.
+ *
+ * @param aSize The size in bytes to format.
+ * @param aUnit Optional unit to use for the format.
+ * Possible values are "KB" or "MB".
+ * @return An array with 2 values.
+ * First is the resulting formatted strings.
+ * The second one is the final unit used to format the string.
+ */
+ formatFolderSize: function(aSize, aUnit = gFolderStatsHelpers.sizeUnits) {
+ let size = Math.round(aSize / 1024);
+ let unit = gFolderStatsHelpers.kiloUnit;
+ // If size is non-zero try to show it in a unit that fits in 3 digits,
+ // but if user specified a fixed unit, use that.
+ if (aUnit != "KB" && (size > 999 || aUnit == "MB")) {
+ size = Math.round(size / 1024);
+ unit = gFolderStatsHelpers.megaUnit;
+ aUnit = "MB";
+ }
+ // This needs to be updated if the "%.*f" placeholder string
+ // in "*ByteAbbreviation2" in messenger.properties changes.
+ return [unit.replace("%.*f", size).replace(" ",""), aUnit];
+ }
+};
diff --git a/comm/suite/mailnews/content/folderPane.xul b/comm/suite/mailnews/content/folderPane.xul
new file mode 100644
index 0000000000..c56c799071
--- /dev/null
+++ b/comm/suite/mailnews/content/folderPane.xul
@@ -0,0 +1,169 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPaneExtras.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % folderpaneDTD SYSTEM "chrome://messenger/locale/folderpane.dtd">
+ %folderpaneDTD;
+ <!ENTITY % msgViewPickerDTD SYSTEM "chrome://messenger/locale/msgViewPickerOverlay.dtd">
+ %msgViewPickerDTD;
+]>
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <tree id="folderTree"
+ class="plain focusring window-focusborder"
+ flex="1"
+ treelines="true"
+ persist="mode"
+ mode="all"
+ keepcurrentinview="true"
+ context="folderPaneContext"
+ focusring="false"
+ disableKeyNavigation="true"
+ ondragstart="gFolderTreeView._onDragStart(event);"
+ ondragover="gFolderTreeView._onDragOver(event);"
+ ondrop="gFolderTreeView._onDragDrop(event);"
+ ondblclick="gFolderTreeView.onDoubleClick(event);"
+ onkeypress="gFolderTreeView.onKeyPress(event);"
+ onselect="FolderPaneSelectionChange();">
+ <treecols id="folderPaneCols">
+ <treecol id="folderNameCol"
+ flex="5"
+ label="&nameColumn.label;"
+ crop="center"
+ persist="width"
+ ignoreincolumnpicker="true"
+ primary="true"
+ sortActive="true"
+ sortDirection="ascending"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="folderUnreadCol"
+ hidden="true"
+ persist="hidden width"
+ flex="1"
+ label="&unreadColumn.label;"
+ selectable="false"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="folderTotalCol"
+ hidden="true"
+ persist="hidden width"
+ flex="1"
+ label="&totalColumn.label;"
+ selectable="false"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="folderSizeCol"
+ hidden="true"
+ persist="hidden width"
+ flex="1"
+ label="&folderSizeColumn.label;"
+ selectable="false"/>
+ </treecols>
+ </tree>
+
+ <toolbarpalette id="MailToolbarPalette">
+ <toolbaritem id="folder-location-container"
+ title="&folderLocationToolbarItem.title;"
+ align="center"
+ context="folderPaneContext"
+ class="toolbaritem-noline chromeclass-toolbar-additional">
+ <image id="locationIcon" class="folderMenuItem"/>
+ <menulist id="locationFolders"
+ class="folderMenuItem"
+ label=" "
+ crop="center">
+ <menupopup id="folderLocationPopup"
+ class="menulist-menupopup"
+ type="folder"
+ flex="1"
+ mode="notDeferred"
+ showFileHereLabel="true"
+ oncommand="gFolderTreeView.selectFolder(event.target._folder, true);"/>
+ </menulist>
+ </toolbaritem>
+ <toolbaritem id="mailviews-container"
+ title="&mailViewsToolbarItem.title;"
+ observes="mailDisableViewsSearch"
+ align="center"
+ class="toolbaritem-noline chromeclass-toolbar-additional">
+ <label id="viewPickerLabel"
+ value="&viewPicker.label;"
+ accesskey="&viewPicker.accesskey;"
+ control="viewPicker">
+ <observes element="mailviews-container" attribute="disabled"/>
+ </label>
+ <menulist id="viewPicker"
+ oncommand="ViewChangeByMenuitem(event.target);">
+ <menupopup id="viewPickerPopup"
+ onpopupshowing="RefreshViewPopup(this);">
+ <menuitem id="viewPickerAll"
+ class="menuitem-iconic"
+ label="&viewAll.label;"
+ type="radio"
+ name="viewmessages"
+ value="0"/>
+ <menuitem id="viewPickerUnread"
+ class="menuitem-iconic"
+ label="&viewUnread.label;"
+ type="radio"
+ name="viewmessages"
+ value="1"/>
+ <menuitem id="viewPickerNotDeleted"
+ class="menuitem-iconic"
+ label="&viewNotDeleted.label;"
+ type="radio"
+ name="viewmessages"
+ value="3"/>
+ <menuseparator id="afterViewPickerUnreadSeparator"/>
+ <menu id="viewPickerTags"
+ class="menu-iconic"
+ label="&viewTags.label;">
+ <menupopup id="viewPickerTagsPopup"
+ class="menulist-menupopup"
+ onpopupshowing="RefreshTagsPopup(this);"/>
+ </menu>
+ <menu id="viewPickerCustomViews"
+ class="menu-iconic"
+ label="&viewCustomViews.label;">
+ <menupopup id="viewPickerCustomViewsPopup"
+ class="menulist-menupopup"
+ onpopupshowing="RefreshCustomViewsPopup(this);"/>
+ </menu>
+ <menuseparator id="afterViewPickerCustomViewsSeparator"/>
+ <menuitem id="viewPickerVirtualFolder"
+ class="menuitem-iconic"
+ label="&viewVirtualFolder.label;"
+ value="7"/>
+ <menuitem id="viewPickerCustomize"
+ class="menuitem-iconic"
+ label="&viewCustomizeView.label;"
+ value="8"/>
+ </menupopup>
+ <observes element="mailviews-container" attribute="disabled"/>
+ </menulist>
+ </toolbaritem>
+ <toolbaritem id="search-container"
+ title="&searchToolbarItem.title;"
+ observes="mailDisableViewsSearch"
+ align="center"
+ flex="1"
+ class="toolbaritem-noline chromeclass-toolbar-additional">
+ <textbox id="searchInput"
+ flex="1"
+ type="search"
+ aria-controls="threadTree"
+ placeholder="&searchSubjectOrAddress.placeholder;"
+ clickSelectsAll="true"
+ onkeypress="if (event.keyCode == KeyEvent.DOM_VK_RETURN) this.select();"
+ oncommand="onEnterInSearchBar();">
+ <observes element="search-container" attribute="disabled"/>
+ </textbox>
+ </toolbaritem>
+ </toolbarpalette>
+</overlay>
diff --git a/comm/suite/mailnews/content/mail-offline.js b/comm/suite/mailnews/content/mail-offline.js
new file mode 100644
index 0000000000..506c198c9a
--- /dev/null
+++ b/comm/suite/mailnews/content/mail-offline.js
@@ -0,0 +1,164 @@
+/* -*- 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 gOfflinePromptsBundle;
+var gOfflineManager;
+
+function MailOfflineStateChanged(goingOffline)
+{
+ // tweak any mail UI here that needs to change when we go offline or come back online
+ gFolderJustSwitched = true;
+}
+
+function MsgSettingsOffline()
+{
+ window.parent.MsgAccountManager('am-offline.xul');
+}
+
+// Check for unsent messages
+function CheckForUnsentMessages()
+{
+ return Cc["@mozilla.org/messengercompose/sendlater;1"]
+ .getService(Ci.nsIMsgSendLater)
+ .hasUnsentMessages();
+}
+
+// Init strings.
+function InitPrompts()
+{
+ if (!gOfflinePromptsBundle)
+ gOfflinePromptsBundle = document.getElementById("bundle_offlinePrompts");
+}
+
+// prompt for sending messages while going online, and go online.
+function PromptSendMessages()
+{
+ InitPrompts();
+ InitServices();
+
+ var checkValue = {value:true};
+ var buttonPressed = Services.prompt.confirmEx(
+ window,
+ gOfflinePromptsBundle.getString('sendMessagesWindowTitle'),
+ gOfflinePromptsBundle.getString('sendMessagesLabel2'),
+ Services.prompt.BUTTON_TITLE_IS_STRING * (Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_POS_1 + Services.prompt.BUTTON_POS_2),
+ gOfflinePromptsBundle.getString('sendMessagesSendButtonLabel'),
+ gOfflinePromptsBundle.getString('sendMessagesCancelButtonLabel'),
+ gOfflinePromptsBundle.getString('sendMessagesNoSendButtonLabel'),
+ gOfflinePromptsBundle.getString('sendMessagesCheckboxLabel'),
+ checkValue);
+ switch (buttonPressed) {
+ case 0:
+ Services.prefs.setIntPref("offline.send.unsent_messages", !checkValue.value);
+ gOfflineManager.goOnline(true, true, msgWindow);
+ return true;
+
+ case 2:
+ Services.prefs.setIntPref("offline.send.unsent_messages", 2*!checkValue.value);
+ gOfflineManager.goOnline(false, true, msgWindow);
+ return true;
+ }
+ return false;
+}
+
+// prompt for downlading messages while going offline, and synchronise
+function PromptDownloadMessages()
+{
+ InitPrompts();
+ InitServices();
+
+ var checkValue = {value:true};
+ var buttonPressed = Services.prompt.confirmEx(
+ window,
+ gOfflinePromptsBundle.getString('downloadMessagesWindowTitle'),
+ gOfflinePromptsBundle.getString('downloadMessagesLabel'),
+ Services.prompt.BUTTON_TITLE_IS_STRING * (Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_POS_1 + Services.prompt.BUTTON_POS_2),
+ gOfflinePromptsBundle.getString('downloadMessagesDownloadButtonLabel'),
+ gOfflinePromptsBundle.getString('downloadMessagesCancelButtonLabel'),
+ gOfflinePromptsBundle.getString('downloadMessagesNoDownloadButtonLabel'),
+ gOfflinePromptsBundle.getString('downloadMessagesCheckboxLabel'),
+ checkValue);
+ switch (buttonPressed) {
+ case 0:
+ Services.prefs.setIntPref("offline.download.download_messages", !checkValue.value);
+ gOfflineManager.synchronizeForOffline(true, true, false, true, msgWindow);
+ return true;
+
+ case 2:
+ Services.prefs.setIntPref("offline.download.download_messages", 2*!checkValue.value);
+ gOfflineManager.synchronizeForOffline(false, false, false, true, msgWindow);
+ return true;
+ }
+ return false;
+}
+
+// Init Pref Service & Offline Manager
+function InitServices()
+{
+ if (!gOfflineManager)
+ GetOfflineMgrService();
+}
+
+// Init Offline Manager
+function GetOfflineMgrService()
+{
+ if (!gOfflineManager) {
+ gOfflineManager = Cc["@mozilla.org/messenger/offline-manager;1"]
+ .getService(Ci.nsIMsgOfflineManager);
+ }
+}
+
+// This function must always return false to prevent toggling of offline state because
+// we change the offline state ourselves
+function MailCheckBeforeOfflineChange()
+{
+ InitServices();
+
+
+ if (Services.io.offline) {
+ switch(Services.prefs.getIntPref("offline.send.unsent_messages")) {
+ case 0:
+ if(CheckForUnsentMessages()) {
+ if(! PromptSendMessages())
+ return false;
+ }
+ else
+ gOfflineManager.goOnline(false /* sendUnsentMessages */,
+ true /* playbackOfflineImapOperations */,
+ msgWindow);
+ break;
+ case 1:
+ gOfflineManager.goOnline(CheckForUnsentMessages() /* sendUnsentMessages */,
+ true /* playbackOfflineImapOperations */,
+ msgWindow);
+ break;
+ case 2:
+ gOfflineManager.goOnline(false /* sendUnsentMessages */,
+ true /* playbackOfflineImapOperations */,
+ msgWindow);
+ break;
+ }
+ }
+ else {
+ // going offline
+ switch(Services.prefs.getIntPref("offline.download.download_messages")) {
+ case 0:
+ if(! PromptDownloadMessages()) return false;
+ break;
+ case 1:
+ // download news, download mail, send unsent messages, go offline when done, msg window
+ gOfflineManager.synchronizeForOffline(true, true, false, true, msgWindow);
+ break;
+ case 2:
+ // download news, download mail, send unsent messages, go offline when done, msg window
+ gOfflineManager.synchronizeForOffline(false, false, false, true, msgWindow);
+ break;
+ }
+ }
+ return false;
+}
+
diff --git a/comm/suite/mailnews/content/mail3PaneWindowCommands.js b/comm/suite/mailnews/content/mail3PaneWindowCommands.js
new file mode 100644
index 0000000000..6bd9f762d4
--- /dev/null
+++ b/comm/suite/mailnews/content/mail3PaneWindowCommands.js
@@ -0,0 +1,1057 @@
+/* -*- 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/. */
+
+/**
+ * Functionality for the main application window (aka the 3pane) usually
+ * consisting of folder pane, thread pane and message pane.
+ */
+
+const { MailServices } =
+ ChromeUtils.import("resource:///modules/MailServices.jsm");
+
+// Controller object for folder pane
+var FolderPaneController =
+{
+ supportsCommand: function(command)
+ {
+ switch ( command )
+ {
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ case "button_shiftDelete":
+ // Even if the folder pane has focus, don't do a folder delete if
+ // we have a selected message, but do a message delete instead.
+ // Return false here supportsCommand and let the command fall back
+ // to the DefaultController.
+ if (Services.prefs.getBoolPref("mailnews.ui.deleteAlwaysSelectedMessages") && (gFolderDisplay.selectedCount != 0))
+ return false;
+ // else fall through
+ //case "cmd_selectAll": the folder pane currently only handles single selection
+ case "cmd_cut":
+ case "cmd_copy":
+ case "cmd_paste":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch ( command )
+ {
+ case "cmd_cut":
+ case "cmd_copy":
+ case "cmd_paste":
+ return false;
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ case "button_shiftDelete":
+ {
+ // Make sure the button doesn't show "Undelete" for folders.
+ UpdateDeleteToolbarButton(true);
+ let folders = GetSelectedMsgFolders();
+ if (folders.length) {
+ let folder = folders[0];
+ // XXX Figure out some better way/place to update the folder labels.
+ UpdateDeleteLabelsFromFolderCommand(folder, command);
+ return CanDeleteFolder(folder) && folder.isCommandEnabled(command);
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ // if the user invoked a key short cut then it is possible that we got here for a command which is
+ // really disabled. kick out if the command should be disabled.
+ if (!this.isCommandEnabled(command)) return;
+
+ switch ( command )
+ {
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ case "button_shiftDelete":
+ gFolderTreeController.deleteFolder();
+ break;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ }
+};
+
+function UpdateDeleteLabelsFromFolderCommand(folder, command) {
+ if (command != "cmd_delete")
+ return;
+
+ if (folder.server.type == "nntp" &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual)) {
+ goSetMenuValue(command, "valueNewsgroup");
+ goSetAccessKey(command, "valueNewsgroupAccessKey");
+ }
+ else {
+ goSetMenuValue(command, "valueFolder");
+ }
+}
+
+// DefaultController object (handles commands when one of the trees does not have focus)
+var DefaultController =
+{
+ supportsCommand: function(command)
+ {
+
+ switch ( command )
+ {
+ case "cmd_createFilterFromPopup":
+ case "cmd_archive":
+ case "cmd_reply":
+ case "button_reply":
+ case "cmd_replyList":
+ case "cmd_replyGroup":
+ case "cmd_replySender":
+ case "cmd_replyall":
+ case "button_replyall":
+ case "cmd_replySenderAndGroup":
+ case "cmd_replyAllRecipients":
+ case "cmd_forward":
+ case "button_forward":
+ case "cmd_forwardInline":
+ case "cmd_forwardAttachment":
+ case "cmd_editAsNew":
+ case "cmd_editDraftMsg":
+ case "cmd_newMsgFromTemplate":
+ case "cmd_editTemplateMsg":
+ case "cmd_createFilterFromMenu":
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ case "button_shiftDelete":
+ case "button_junk":
+ case "cmd_nextMsg":
+ case "button_next":
+ case "cmd_nextUnreadMsg":
+ case "cmd_nextFlaggedMsg":
+ case "cmd_nextUnreadThread":
+ case "cmd_previousMsg":
+ case "cmd_previousUnreadMsg":
+ case "cmd_previousFlaggedMsg":
+ case "button_goBack":
+ case "cmd_goBack":
+ case "button_goForward":
+ case "cmd_goForward":
+ case "cmd_goStartPage":
+ case "cmd_viewAllMsgs":
+ case "cmd_viewUnreadMsgs":
+ case "cmd_viewThreadsWithUnread":
+ case "cmd_viewWatchedThreadsWithUnread":
+ case "cmd_viewIgnoredThreads":
+ case "cmd_stop":
+ case "cmd_undo":
+ case "cmd_redo":
+ case "cmd_expandAllThreads":
+ case "cmd_collapseAllThreads":
+ case "cmd_renameFolder":
+ case "cmd_sendUnsentMsgs":
+ case "cmd_subscribe":
+ case "cmd_openMessage":
+ case "button_print":
+ case "cmd_print":
+ case "cmd_printpreview":
+ case "cmd_printSetup":
+ case "cmd_saveAsFile":
+ case "cmd_saveAsTemplate":
+ case "cmd_properties":
+ case "cmd_viewPageSource":
+ case "cmd_setFolderCharset":
+ case "cmd_reload":
+ case "button_getNewMessages":
+ case "cmd_getNewMessages":
+ case "cmd_getMsgsForAuthAccounts":
+ case "cmd_getNextNMessages":
+ case "cmd_find":
+ case "cmd_findNext":
+ case "cmd_findPrev":
+ case "button_search":
+ case "cmd_search":
+ case "button_mark":
+ case "cmd_markAsRead":
+ case "cmd_markAsUnread":
+ case "cmd_markAllRead":
+ case "cmd_markThreadAsRead":
+ case "cmd_markReadByDate":
+ case "cmd_markAsFlagged":
+ case "cmd_markAsJunk":
+ case "cmd_markAsNotJunk":
+ case "cmd_recalculateJunkScore":
+ case "cmd_markAsShowRemote":
+ case "cmd_markAsNotPhish":
+ case "cmd_displayMsgFilters":
+ case "cmd_applyFiltersToSelection":
+ case "cmd_applyFilters":
+ case "cmd_runJunkControls":
+ case "cmd_deleteJunk":
+ case "button_file":
+ case "cmd_emptyTrash":
+ case "cmd_compactFolder":
+ case "cmd_settingsOffline":
+ case "cmd_selectAll":
+ case "cmd_selectThread":
+ case "cmd_selectFlagged":
+ case "cmd_viewAllHeader":
+ case "cmd_viewNormalHeader":
+ return true;
+ case "cmd_downloadFlagged":
+ case "cmd_downloadSelected":
+ case "cmd_synchronizeOffline":
+ return !Services.io.offline;
+ case "cmd_watchThread":
+ case "cmd_killThread":
+ case "cmd_killSubthread":
+ case "cmd_cancel":
+ return gFolderDisplay.selectedMessageIsNews;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ var enabled = new Object();
+ enabled.value = false;
+ var checkStatus = new Object();
+
+ switch ( command )
+ {
+ case "cmd_delete":
+ UpdateDeleteCommand();
+ // fall through
+ case "button_delete":
+ if (command == "button_delete")
+ UpdateDeleteToolbarButton(false);
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.deleteMsg, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_shiftDelete":
+ case "button_shiftDelete":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.deleteNoTrash, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_cancel":
+ return GetNumSelectedMessages() == 1 &&
+ gFolderDisplay.selectedMessageIsNews;
+ case "button_junk":
+ UpdateJunkToolbarButton();
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_killThread":
+ case "cmd_killSubthread":
+ return GetNumSelectedMessages() > 0;
+ case "cmd_watchThread":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.toggleThreadWatched, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_createFilterFromPopup":
+ case "cmd_createFilterFromMenu":
+ var loadedFolder = GetLoadedMsgFolder();
+ if (!(loadedFolder && loadedFolder.server.canHaveFilters))
+ return false; // else fall thru
+ case "cmd_saveAsFile":
+ return GetNumSelectedMessages() > 0;
+ case "cmd_saveAsTemplate":
+ var msgFolder = GetSelectedMsgFolders();
+ var target = msgFolder[0].server.localStoreType;
+ if (GetNumSelectedMessages() == 0 || target == "news")
+ return false; // else fall thru
+ case "cmd_reply":
+ case "button_reply":
+ case "cmd_replyList":
+ case "cmd_replyGroup":
+ case "cmd_replySender":
+ case "cmd_replyall":
+ case "button_replyall":
+ case "cmd_replySenderAndGroup":
+ case "cmd_replyAllRecipients":
+ case "cmd_forward":
+ case "button_forward":
+ case "cmd_forwardInline":
+ case "cmd_forwardAttachment":
+ case "cmd_editAsNew":
+ case "cmd_editDraftMsg":
+ case "cmd_newMsgFromTemplate":
+ case "cmd_editTemplateMsg":
+ case "cmd_openMessage":
+ case "button_print":
+ case "cmd_print":
+ case "cmd_viewPageSource":
+ case "cmd_reload":
+ case "cmd_applyFiltersToSelection":
+ if (command == "cmd_applyFiltersToSelection")
+ {
+ var whichText = "valueMessage";
+ if (GetNumSelectedMessages() > 1)
+ whichText = "valueSelection";
+ goSetMenuValue(command, whichText);
+ goSetAccessKey(command, whichText + "AccessKey");
+ }
+ if (GetNumSelectedMessages() > 0)
+ {
+ if (gDBView)
+ {
+ gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody, enabled, checkStatus);
+ return enabled.value;
+ }
+ }
+ return false;
+ case "cmd_printpreview":
+ if ( GetNumSelectedMessages() == 1 && gDBView)
+ {
+ gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody, enabled, checkStatus);
+ return enabled.value;
+ }
+ return false;
+ case "cmd_printSetup":
+ case "cmd_viewAllHeader":
+ case "cmd_viewNormalHeader":
+ return true;
+ case "cmd_markAsFlagged":
+ case "button_file":
+ return GetNumSelectedMessages() > 0;
+ case "cmd_archive":
+ return gFolderDisplay.canArchiveSelectedMessages;
+ case "cmd_markAsJunk":
+ case "cmd_markAsNotJunk":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_recalculateJunkScore":
+ // We're going to take a conservative position here, because we really
+ // don't want people running junk controls on folders that are not
+ // enabled for junk. The junk type picks up possible dummy message headers,
+ // while the runJunkControls will prevent running on XF virtual folders.
+ if (gDBView)
+ {
+ gDBView.getCommandStatus(nsMsgViewCommandType.runJunkControls, enabled, checkStatus);
+ if (enabled.value)
+ gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
+ }
+ return enabled.value;
+ case "cmd_markAsShowRemote":
+ return (GetNumSelectedMessages() > 0 && checkMsgHdrPropertyIsNot("remoteContentPolicy", kAllowRemoteContent));
+ case "cmd_markAsNotPhish":
+ return (GetNumSelectedMessages() > 0 && checkMsgHdrPropertyIsNot("notAPhishMessage", kNotAPhishMessage));
+ case "cmd_displayMsgFilters":
+ return MailServices.accounts.accounts.length > 0;
+ case "cmd_applyFilters":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.applyFilters, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_runJunkControls":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.runJunkControls, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_deleteJunk":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.deleteJunk, enabled, checkStatus);
+ return enabled.value;
+ case "button_mark":
+ case "cmd_markThreadAsRead":
+ return GetNumSelectedMessages() > 0;
+ case "cmd_markAsRead":
+ return CanMarkMsgAsRead(true);
+ case "cmd_markAsUnread":
+ return CanMarkMsgAsRead(false);
+ case "button_next":
+ return IsViewNavigationItemEnabled();
+ case "cmd_nextMsg":
+ case "cmd_nextUnreadMsg":
+ case "cmd_nextUnreadThread":
+ case "cmd_previousMsg":
+ case "cmd_previousUnreadMsg":
+ return IsViewNavigationItemEnabled();
+ case "button_goBack":
+ case "cmd_goBack":
+ return gDBView && gDBView.navigateStatus(nsMsgNavigationType.back);
+ case "button_goForward":
+ case "cmd_goForward":
+ return gDBView && gDBView.navigateStatus(nsMsgNavigationType.forward);
+ case "cmd_goStartPage":
+ return Services.prefs.getBoolPref("mailnews.start_page.enabled") && !IsMessagePaneCollapsed();
+ case "cmd_markAllRead":
+ return IsFolderSelected() && gDBView && gDBView.msgFolder.getNumUnread(false) > 0;
+ case "cmd_markReadByDate":
+ return IsFolderSelected();
+ case "cmd_find":
+ case "cmd_findNext":
+ case "cmd_findPrev":
+ return IsMessageDisplayedInMessagePane();
+ break;
+ case "button_search":
+ case "cmd_search":
+ return MailServices.accounts.accounts.length > 0;
+ case "cmd_selectAll":
+ case "cmd_selectFlagged":
+ return !!gDBView;
+ // these are enabled on when we are in threaded mode
+ case "cmd_selectThread":
+ if (GetNumSelectedMessages() <= 0) return false;
+ case "cmd_expandAllThreads":
+ case "cmd_collapseAllThreads":
+ return gDBView && (gDBView.viewFlags & nsMsgViewFlagsType.kThreadedDisplay);
+ break;
+ case "cmd_nextFlaggedMsg":
+ case "cmd_previousFlaggedMsg":
+ return IsViewNavigationItemEnabled();
+ case "cmd_viewAllMsgs":
+ case "cmd_viewUnreadMsgs":
+ case "cmd_viewIgnoredThreads":
+ return gDBView;
+ case "cmd_viewThreadsWithUnread":
+ case "cmd_viewWatchedThreadsWithUnread":
+ return gDBView && !(GetSelectedMsgFolders()[0].flags &
+ Ci.nsMsgFolderFlags.Virtual);
+ case "cmd_stop":
+ return true;
+ case "cmd_undo":
+ case "cmd_redo":
+ return SetupUndoRedoCommand(command);
+ case "cmd_renameFolder":
+ {
+ let folders = GetSelectedMsgFolders();
+ return folders.length == 1 && folders[0].canRename &&
+ folders[0].isCommandEnabled("cmd_renameFolder");
+ }
+ case "cmd_sendUnsentMsgs":
+ return IsSendUnsentMsgsEnabled(null);
+ case "cmd_subscribe":
+ return IsSubscribeEnabled();
+ case "cmd_properties":
+ return IsPropertiesEnabled(command);
+ case "button_getNewMessages":
+ case "cmd_getNewMessages":
+ case "cmd_getMsgsForAuthAccounts":
+ return IsGetNewMessagesEnabled();
+ case "cmd_getNextNMessages":
+ return IsGetNextNMessagesEnabled();
+ case "cmd_emptyTrash":
+ {
+ let folder = GetSelectedMsgFolders()[0];
+ return folder && folder.server.canEmptyTrashOnExit ?
+ IsMailFolderSelected() : false;
+ }
+ case "cmd_compactFolder":
+ {
+ let folders = GetSelectedMsgFolders();
+ let canCompactAll = function canCompactAll(folder) {
+ return folder.server.canCompactFoldersOnServer &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual) &&
+ folder.isCommandEnabled("cmd_compactFolder");
+ }
+ return folders && folders.every(canCompactAll);
+ }
+ case "cmd_setFolderCharset":
+ return IsFolderCharsetEnabled();
+ case "cmd_downloadFlagged":
+ return !Services.io.offline;
+ case "cmd_downloadSelected":
+ return IsFolderSelected() && !Services.io.offline &&
+ GetNumSelectedMessages() > 0;
+ case "cmd_synchronizeOffline":
+ return !Services.io.offline;
+ case "cmd_settingsOffline":
+ return IsAccountOfflineEnabled();
+ default:
+ return false;
+ }
+ return false;
+ },
+
+ doCommand: function(command)
+ {
+ // if the user invoked a key short cut then it is possible that we got here for a command which is
+ // really disabled. kick out if the command should be disabled.
+ if (!this.isCommandEnabled(command))
+ return;
+
+ switch (command)
+ {
+ case "button_getNewMessages":
+ case "cmd_getNewMessages":
+ MsgGetMessage();
+ break;
+ case "cmd_getMsgsForAuthAccounts":
+ MsgGetMessagesForAllAuthenticatedAccounts();
+ break;
+ case "cmd_getNextNMessages":
+ MsgGetNextNMessages();
+ break;
+ case "cmd_archive":
+ MsgArchiveSelectedMessages(null);
+ break;
+ case "cmd_reply":
+ MsgReplyMessage(null);
+ break;
+ case "cmd_replyList":
+ MsgReplyList(null);
+ break;
+ case "cmd_replyGroup":
+ MsgReplyGroup(null);
+ break;
+ case "cmd_replySender":
+ MsgReplySender(null);
+ break;
+ case "cmd_replyall":
+ MsgReplyToAllMessage(null);
+ break;
+ case "cmd_replySenderAndGroup":
+ MsgReplyToSenderAndGroup(null);
+ break;
+ case "cmd_replyAllRecipients":
+ MsgReplyToAllRecipients(null);
+ break;
+ case "cmd_forward":
+ MsgForwardMessage(null);
+ break;
+ case "cmd_forwardInline":
+ MsgForwardAsInline(null);
+ break;
+ case "cmd_forwardAttachment":
+ MsgForwardAsAttachment(null);
+ break;
+ case "cmd_editAsNew":
+ MsgEditMessageAsNew(null);
+ break;
+ case "cmd_editDraftMsg":
+ MsgEditDraftMessage(null);
+ break;
+ case "cmd_newMsgFromTemplate":
+ MsgNewMessageFromTemplate(null);
+ break;
+ case "cmd_editTemplateMsg":
+ MsgEditTemplateMessage(null);
+ break;
+ case "cmd_createFilterFromMenu":
+ MsgCreateFilter();
+ break;
+ case "cmd_createFilterFromPopup":
+ CreateFilter(document.popupNode);
+ break;
+ case "cmd_delete":
+ case "button_delete":
+ MsgDeleteMessage(false);
+ UpdateDeleteToolbarButton(false);
+ break;
+ case "cmd_shiftDelete":
+ case "button_shiftDelete":
+ MsgDeleteMessage(true);
+ UpdateDeleteToolbarButton(false);
+ break;
+ case "cmd_cancel":
+ let message = gFolderDisplay.selectedMessage;
+ message.folder.QueryInterface(Ci.nsIMsgNewsFolder)
+ .cancelMessage(message, msgWindow);
+ break;
+ case "cmd_killThread":
+ /* kill thread kills the thread and then does a next unread */
+ GoNextMessage(nsMsgNavigationType.toggleThreadKilled, true);
+ break;
+ case "cmd_killSubthread":
+ GoNextMessage(nsMsgNavigationType.toggleSubthreadKilled, true);
+ break;
+ case "cmd_watchThread":
+ gDBView.doCommand(nsMsgViewCommandType.toggleThreadWatched);
+ break;
+ case "button_next":
+ case "cmd_nextUnreadMsg":
+ GoNextMessage(nsMsgNavigationType.nextUnreadMessage, true);
+ break;
+ case "cmd_nextUnreadThread":
+ GoNextMessage(nsMsgNavigationType.nextUnreadThread, true);
+ break;
+ case "cmd_nextMsg":
+ GoNextMessage(nsMsgNavigationType.nextMessage, false);
+ break;
+ case "cmd_nextFlaggedMsg":
+ GoNextMessage(nsMsgNavigationType.nextFlagged, true);
+ break;
+ case "cmd_previousMsg":
+ GoNextMessage(nsMsgNavigationType.previousMessage, false);
+ break;
+ case "cmd_previousUnreadMsg":
+ GoNextMessage(nsMsgNavigationType.previousUnreadMessage, true);
+ break;
+ case "cmd_previousFlaggedMsg":
+ GoNextMessage(nsMsgNavigationType.previousFlagged, true);
+ break;
+ case "button_goForward":
+ case "cmd_goForward":
+ GoNextMessage(nsMsgNavigationType.forward, true);
+ break;
+ case "button_goBack":
+ case "cmd_goBack":
+ GoNextMessage(nsMsgNavigationType.back, true);
+ break;
+ case "cmd_goStartPage":
+ HideMessageHeaderPane();
+ loadStartPage();
+ break;
+ case "cmd_viewAllMsgs":
+ case "cmd_viewThreadsWithUnread":
+ case "cmd_viewWatchedThreadsWithUnread":
+ case "cmd_viewUnreadMsgs":
+ case "cmd_viewIgnoredThreads":
+ SwitchView(command);
+ break;
+ case "cmd_undo":
+ messenger.undo(msgWindow);
+ break;
+ case "cmd_redo":
+ messenger.redo(msgWindow);
+ break;
+ case "cmd_expandAllThreads":
+ gDBView.doCommand(nsMsgViewCommandType.expandAll);
+ break;
+ case "cmd_collapseAllThreads":
+ gDBView.doCommand(nsMsgViewCommandType.collapseAll);
+ break;
+ case "cmd_renameFolder":
+ gFolderTreeController.renameFolder();
+ return;
+ case "cmd_sendUnsentMsgs":
+ MsgSendUnsentMsgs();
+ return;
+ case "cmd_subscribe":
+ MsgSubscribe();
+ return;
+ case "cmd_openMessage":
+ MsgOpenSelectedMessages();
+ return;
+ case "cmd_printSetup":
+ PrintUtils.showPageSetup();
+ return;
+ case "cmd_print":
+ PrintEnginePrint();
+ return;
+ case "cmd_printpreview":
+ PrintEnginePrintPreview();
+ return;
+ case "cmd_saveAsFile":
+ MsgSaveAsFile();
+ return;
+ case "cmd_saveAsTemplate":
+ MsgSaveAsTemplate();
+ return;
+ case "cmd_viewPageSource":
+ MsgViewPageSource();
+ return;
+ case "cmd_setFolderCharset":
+ gFolderTreeController.editFolder();
+ return;
+ case "cmd_reload":
+ ReloadMessage();
+ return;
+ case "cmd_find":
+ MsgFind();
+ return;
+ case "cmd_findNext":
+ MsgFindAgain(false);
+ return;
+ case "cmd_findPrev":
+ MsgFindAgain(true);
+ return;
+ case "cmd_properties":
+ gFolderTreeController.editFolder();
+ return;
+ case "button_search":
+ case "cmd_search":
+ MsgSearchMessages();
+ return;
+ case "button_mark":
+ MsgMarkMsgAsRead();
+ return;
+ case "cmd_markAsRead":
+ MsgMarkMsgAsRead(true);
+ return;
+ case "cmd_markAsUnread":
+ MsgMarkMsgAsRead(false);
+ return;
+ case "cmd_markThreadAsRead":
+ MsgMarkThreadAsRead();
+ return;
+ case "cmd_markAllRead":
+ gDBView.doCommand(nsMsgViewCommandType.markAllRead);
+ return;
+ case "cmd_markReadByDate":
+ MsgMarkReadByDate();
+ return;
+ case "button_junk":
+ MsgJunk();
+ return;
+ case "cmd_stop":
+ msgWindow.StopUrls();
+ return;
+ case "cmd_markAsFlagged":
+ MsgMarkAsFlagged();
+ return;
+ case "cmd_viewAllHeader":
+ MsgViewAllHeaders();
+ return;
+ case "cmd_viewNormalHeader":
+ MsgViewNormalHeaders();
+ return;
+ case "cmd_markAsJunk":
+ JunkSelectedMessages(true);
+ return;
+ case "cmd_markAsNotJunk":
+ JunkSelectedMessages(false);
+ return;
+ case "cmd_recalculateJunkScore":
+ analyzeMessagesForJunk();
+ return;
+ case "cmd_markAsShowRemote":
+ LoadMsgWithRemoteContent();
+ return;
+ case "cmd_markAsNotPhish":
+ MsgIsNotAScam();
+ return;
+ case "cmd_displayMsgFilters":
+ MsgFilters(null, null);
+ return;
+ case "cmd_applyFiltersToSelection":
+ MsgApplyFiltersToSelection();
+ return;
+ case "cmd_applyFilters":
+ MsgApplyFilters(null);
+ return;
+ case "cmd_runJunkControls":
+ filterFolderForJunk();
+ return;
+ case "cmd_deleteJunk":
+ deleteJunkInFolder();
+ return;
+ case "cmd_emptyTrash":
+ gFolderTreeController.emptyTrash();
+ return;
+ case "cmd_compactFolder":
+ gFolderTreeController.compactAllFoldersForAccount();
+ return;
+ case "cmd_downloadFlagged":
+ MsgDownloadFlagged();
+ break;
+ case "cmd_downloadSelected":
+ MsgDownloadSelected();
+ break;
+ case "cmd_synchronizeOffline":
+ MsgSynchronizeOffline();
+ break;
+ case "cmd_settingsOffline":
+ MsgSettingsOffline();
+ break;
+ case "cmd_selectAll":
+ // move the focus so the user can delete the newly selected messages, not the folder
+ SetFocusThreadPane();
+ // if in threaded mode, the view will expand all before selecting all
+ gDBView.doCommand(nsMsgViewCommandType.selectAll)
+ if (gDBView.numSelected != 1) {
+ setTitleFromFolder(gDBView.msgFolder,null);
+ ClearMessagePane();
+ }
+ break;
+ case "cmd_selectThread":
+ gDBView.doCommand(nsMsgViewCommandType.selectThread);
+ break;
+ case "cmd_selectFlagged":
+ gDBView.doCommand(nsMsgViewCommandType.selectFlagged);
+ break;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ // on blur events set the menu item texts back to the normal values
+ if ( event == 'blur' )
+ {
+ goSetMenuValue('cmd_undo', 'valueDefault');
+ goSetMenuValue('cmd_redo', 'valueDefault');
+ }
+ }
+};
+
+function MsgCloseTabOrWindow()
+{
+ var tabmail = GetTabMail();
+ if (tabmail.tabInfo.length > 1)
+ tabmail.removeCurrentTab();
+ else
+ window.close();
+}
+
+function GetNumSelectedMessages()
+{
+ return gDBView ? gDBView.numSelected : 0;
+}
+
+var gLastFocusedElement=null;
+
+function FocusRingUpdate_Mail()
+{
+ // If the focusedElement is null, we're here on a blur.
+ // nsFocusController::Blur() calls nsFocusController::SetFocusedElement(null),
+ // which will update any commands listening for "focus".
+ // we really only care about nsFocusController::Focus() happens,
+ // which calls nsFocusController::SetFocusedElement(element)
+ var currentFocusedElement = gFolderDisplay.focusedPane;
+
+ if (currentFocusedElement != gLastFocusedElement) {
+ if (currentFocusedElement)
+ currentFocusedElement.setAttribute("focusring", "true");
+
+ if (gLastFocusedElement)
+ gLastFocusedElement.removeAttribute("focusring");
+
+ gLastFocusedElement = currentFocusedElement;
+
+ // since we just changed the pane with focus we need to update the toolbar to reflect this
+ // XXX TODO
+ // can we optimize
+ // and just update cmd_delete and button_delete?
+ UpdateMailToolbar("focus");
+ }
+}
+
+function SetupCommandUpdateHandlers()
+{
+ // folder pane
+ var widget = document.getElementById("folderTree");
+ if (widget)
+ widget.controllers.appendController(FolderPaneController);
+}
+
+// Called from <msgMail3PaneWindow.js>.
+function UnloadCommandUpdateHandlers()
+{
+ var widget = document.getElementById("folderTree");
+ if (widget)
+ widget.controllers.removeController(FolderPaneController);
+}
+
+function IsSendUnsentMsgsEnabled(folderResource)
+{
+ var msgSendLater =
+ Cc["@mozilla.org/messengercompose/sendlater;1"]
+ .getService(Ci.nsIMsgSendLater);
+
+ // If we're currently sending unsent msgs, disable this cmd.
+ if (msgSendLater.sendingMessages)
+ return false;
+
+ if (folderResource &&
+ folderResource instanceof Ci.nsIMsgFolder) {
+ // If unsentMsgsFolder is non-null, it is the "Outbox" folder.
+ // We're here because we've done a right click on the "Outbox"
+ // folder (context menu), so we can use the folder and return true/false
+ // straight away.
+ return folderResource.getTotalMessages(false) > 0;
+ }
+
+ // Otherwise, we don't know where we are, so use the current identity and
+ // find out if we have messages or not via that.
+ let identity = null;
+ let folders = GetSelectedMsgFolders();
+ if (folders.length > 0)
+ identity = getIdentityForServer(folders[0].server);
+
+ if (!identity) {
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ if (defaultAccount)
+ identity = defaultAccount.defaultIdentity;
+
+ if (!identity)
+ return false;
+ }
+
+ return msgSendLater.hasUnsentMessages(identity);
+}
+
+/**
+ * Determine whether there exists any server for which to show the Subscribe dialog.
+ */
+function IsSubscribeEnabled()
+{
+ // If there are any IMAP or News servers, we can show the dialog any time and
+ // it will properly show those.
+ for (let server of accountManager.allServers) {
+ if (server.type == "imap" || server.type == "nntp")
+ return true;
+ }
+
+ // RSS accounts use a separate Subscribe dialog that we can only show when
+ // such an account is selected.
+ let preselectedFolder = GetFirstSelectedMsgFolder();
+ if (preselectedFolder && preselectedFolder.server.type == "rss")
+ return true;
+
+ return false;
+}
+
+function IsFolderCharsetEnabled()
+{
+ return IsFolderSelected();
+}
+
+function IsPropertiesEnabled(command)
+{
+ let folders = GetSelectedMsgFolders();
+ if (!folders.length)
+ return false;
+
+ let folder = folders[0];
+ // When servers are selected, it should be "Edit | Properties...".
+ if (folder.isServer) {
+ goSetMenuValue(command, "valueGeneric");
+ } else if (folder.server.type == "nntp" &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual)) {
+ goSetMenuValue(command, "valueNewsgroup");
+ } else {
+ goSetMenuValue(command, "valueFolder");
+ }
+
+ return folders.length == 1;
+}
+
+function IsViewNavigationItemEnabled()
+{
+ return IsFolderSelected();
+}
+
+function IsFolderSelected()
+{
+ let folders = GetSelectedMsgFolders();
+ return folders.length == 1 && !folders[0].isServer;
+}
+
+function IsMessageDisplayedInMessagePane()
+{
+ return (!IsMessagePaneCollapsed() && (GetNumSelectedMessages() > 0));
+}
+
+function SetFocusThreadPaneIfNotOnMessagePane()
+{
+ var focusedElement = gFolderDisplay.focusedPane;
+
+ if((focusedElement != GetThreadTree()) &&
+ (focusedElement != GetMessagePane()))
+ SetFocusThreadPane();
+}
+
+function SwitchPaneFocus(event)
+{
+ var folderTree = document.getElementById("folderTree");
+ var threadTree = GetThreadTree();
+ var messagePane = GetMessagePane();
+
+ var folderPaneCollapsed = document.getElementById("folderPaneBox").collapsed;
+
+ // Although internally this is actually a four-pane window, it is presented as
+ // a three-pane -- the search pane is more of a toolbar. So, shift among the
+ // three main panes.
+
+ var focusedElement = gFolderDisplay.focusedPane;
+ if (focusedElement == null) // focus not on one of the main three panes?
+ focusedElement = threadTree; // treat as if on thread tree
+
+ if (event && event.shiftKey)
+ {
+ // Reverse traversal: Message -> Thread -> Folder -> Message
+ if (focusedElement == threadTree && !folderPaneCollapsed)
+ folderTree.focus();
+ else if (focusedElement != messagePane && !IsMessagePaneCollapsed())
+ SetFocusMessagePane();
+ else
+ threadTree.focus();
+ }
+ else
+ {
+ // Forward traversal: Folder -> Thread -> Message -> Folder
+ if (focusedElement == threadTree && !IsMessagePaneCollapsed())
+ SetFocusMessagePane();
+ else if (focusedElement != folderTree && !folderPaneCollapsed)
+ folderTree.focus();
+ else
+ threadTree.focus();
+ }
+}
+
+function SetFocusThreadPane()
+{
+ var threadTree = GetThreadTree();
+ threadTree.focus();
+}
+
+function SetFocusMessagePane()
+{
+ // XXX hack: to clear the focus on the previous element first focus
+ // on the message pane element then focus on the main content window
+ GetMessagePane().focus();
+ GetMessagePaneFrame().focus();
+}
+
+//
+// This function checks if the configured junk mail can be renamed or deleted.
+//
+function CanRenameDeleteJunkMail(aFolderUri)
+{
+ if (!aFolderUri)
+ return false;
+
+ // Go through junk mail settings for all servers and see if the folder is set/used by anyone.
+ try
+ {
+ var allServers = accountManager.allServers;
+
+ for (var i = 0; i < allServers.length; i++)
+ {
+ var currentServer =
+ allServers.queryElementAt(i, Ci.nsIMsgIncomingServer);
+ var settings = currentServer.spamSettings;
+ // If junk mail control or move junk mail to folder option is disabled then
+ // allow the folder to be removed/renamed since the folder is not used in this case.
+ if (!settings.level || !settings.moveOnSpam)
+ continue;
+ if (settings.spamFolderURI == aFolderUri)
+ return false;
+ }
+ }
+ catch(ex)
+ {
+ dump("Can't get all servers\n");
+ }
+ return true;
+}
+
+/** Check if this is a folder the user is allowed to delete. */
+function CanDeleteFolder(folder) {
+ if (folder.isServer)
+ return false;
+
+ var specialFolder = FolderUtils.getSpecialFolderString(folder);
+
+ if (specialFolder == "Inbox" || specialFolder == "Trash" ||
+ specialFolder == "Drafts" || specialFolder == "Sent" ||
+ specialFolder == "Templates" || specialFolder == "Outbox" ||
+ (specialFolder == "Junk" && !CanRenameDeleteJunkMail(folder.URI)))
+ return false;
+
+ return true;
+}
diff --git a/comm/suite/mailnews/content/mailCommands.js b/comm/suite/mailnews/content/mailCommands.js
new file mode 100644
index 0000000000..ae7e91a6cb
--- /dev/null
+++ b/comm/suite/mailnews/content/mailCommands.js
@@ -0,0 +1,415 @@
+/* -*- 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");
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+
+/**
+ * Get the identity that most likely is the best one to use, given the hint.
+ * @param {Array<nsIMsgIdentity> identities The candidates to pick from.
+ * @param {String} optionalHint String containing comma separated mailboxes
+ */
+function getBestIdentity(identities, optionalHint)
+{
+ let identityCount = identities.length;
+ if (identityCount < 1)
+ return null;
+
+ // If we have more than one identity and a hint to help us pick one.
+ if (identityCount > 1 && optionalHint) {
+ // Normalize case on the optional hint to improve our chances of
+ // finding a match.
+ let hints = optionalHint.toLowerCase().split(",");
+
+ for (let i = 0 ; i < hints.length; i++) {
+ for (let identity of identities) {
+ if (!identity.email)
+ continue;
+ if (hints[i].trim() == identity.email.toLowerCase() ||
+ hints[i].includes("<" + identity.email.toLowerCase() + ">"))
+ return identity;
+ }
+ }
+ }
+ // Return only found identity or pick the first one from list if no matches found.
+ return identities[0];
+}
+
+function getIdentityForServer(server, optionalHint)
+{
+ let identities = accountManager.getIdentitiesForServer(server);
+ return getBestIdentity(identities, optionalHint);
+}
+
+/**
+ * Get the identity for the given header.
+ * @param hdr nsIMsgHdr message header
+ * @param type nsIMsgCompType compose type the identity ise used for.
+ */
+
+function GetIdentityForHeader(aMsgHdr, aType)
+{
+ function findDeliveredToIdentityEmail() {
+ // Get the delivered-to headers.
+ let key = "delivered-to";
+ let deliveredTos = new Array();
+ let index = 0;
+ let header = "";
+ while (currentHeaderData[key]) {
+ deliveredTos.push(currentHeaderData[key].headerValue.toLowerCase().trim());
+ key = "delivered-to" + index++;
+ }
+
+ // Reverse the array so that the last delivered-to header will show at front.
+ deliveredTos.reverse();
+ for (let i = 0; i < deliveredTos.length; i++) {
+ for (let identity of accountManager.allIdentities) {
+ if (!identity.email)
+ continue;
+ // If the deliver-to header contains the defined identity, that's it.
+ if (deliveredTos[i] == identity.email.toLowerCase() ||
+ deliveredTos[i].includes("<" + identity.email.toLowerCase() + ">"))
+ return identity.email;
+ }
+ }
+ return "";
+ }
+
+ let hintForIdentity = "";
+ if (aType == Ci.nsIMsgCompType.ReplyToList)
+ hintForIdentity = findDeliveredToIdentityEmail();
+ else if (aType == Ci.nsIMsgCompType.Template ||
+ aType == Ci.nsIMsgCompType.EditTemplate ||
+ aType == Ci.nsIMsgCompType.EditAsNew)
+ hintForIdentity = aMsgHdr.author;
+ else
+ hintForIdentity = aMsgHdr.recipients + "," + aMsgHdr.ccList + "," +
+ findDeliveredToIdentityEmail();
+
+ let server = null;
+ let identity = null;
+ let folder = aMsgHdr.folder;
+ if (folder)
+ {
+ server = folder.server;
+ identity = folder.customIdentity;
+ }
+
+ if (!identity)
+ {
+ let accountKey = aMsgHdr.accountKey;
+ if (accountKey.length > 0)
+ {
+ let account = accountManager.getAccount(accountKey);
+ if (account)
+ server = account.incomingServer;
+ }
+
+ if (server)
+ identity = getIdentityForServer(server, hintForIdentity);
+
+ if (!identity)
+ identity = getBestIdentity(accountManager.allIdentities, hintForIdentity);
+ }
+ return identity;
+}
+
+function GetNextNMessages(folder)
+{
+ if (folder) {
+ var newsFolder = folder.QueryInterface(Ci.nsIMsgNewsFolder);
+ if (newsFolder) {
+ newsFolder.getNextNMessages(msgWindow);
+ }
+ }
+}
+
+// type is a nsIMsgCompType and format is a nsIMsgCompFormat
+function ComposeMessage(type, format, folder, messageArray)
+{
+ var msgComposeType = Ci.nsIMsgCompType;
+ var identity = null;
+ var newsgroup = null;
+ var hdr;
+
+ // dump("ComposeMessage folder=" + folder + "\n");
+ try
+ {
+ if (folder)
+ {
+ // Get the incoming server associated with this uri.
+ var server = folder.server;
+
+ // If they hit new or reply and they are reading a newsgroup,
+ // turn this into a new post or a reply to group.
+ if (!folder.isServer && server.type == "nntp" && type == msgComposeType.New)
+ {
+ type = msgComposeType.NewsPost;
+ newsgroup = folder.folderURL;
+ }
+
+ identity = getIdentityForServer(server);
+ // dump("identity = " + identity + "\n");
+ }
+ }
+ catch (ex)
+ {
+ dump("failed to get an identity to pre-select: " + ex + "\n");
+ }
+
+ // dump("\nComposeMessage from XUL: " + identity + "\n");
+
+ if (!msgComposeService)
+ {
+ dump("### msgComposeService is invalid\n");
+ return;
+ }
+
+ switch (type)
+ {
+ case msgComposeType.New: //new message
+ // dump("OpenComposeWindow with " + identity + "\n");
+ // If the addressbook sidebar panel is open and has focus, get
+ // the selected addresses from it.
+ if (document.commandDispatcher.focusedWindow &&
+ document.commandDispatcher.focusedWindow
+ .document.documentElement.hasAttribute("selectedaddresses"))
+ NewMessageToSelectedAddresses(type, format, identity);
+ else
+ msgComposeService.OpenComposeWindow(null, null, null, type,
+ format, identity, null, msgWindow);
+ return;
+ case msgComposeType.NewsPost:
+ // dump("OpenComposeWindow with " + identity + " and " + newsgroup + "\n");
+ msgComposeService.OpenComposeWindow(null, null, newsgroup, type,
+ format, identity, null, msgWindow);
+ return;
+ case msgComposeType.ForwardAsAttachment:
+ if (messageArray && messageArray.length)
+ {
+ // If we have more than one ForwardAsAttachment then pass null instead
+ // of the header to tell the compose service to work out the attachment
+ // subjects from the URIs.
+ hdr = messageArray.length > 1 ? null : messenger.msgHdrFromURI(messageArray[0]);
+ msgComposeService.OpenComposeWindow(null, hdr, messageArray.join(','),
+ type, format, identity, null, msgWindow);
+ return;
+ }
+ default:
+ if (!messageArray)
+ return;
+
+ // Limit the number of new compose windows to 8. Why 8 ?
+ // I like that number :-)
+ if (messageArray.length > 8)
+ messageArray.length = 8;
+
+ for (var i = 0; i < messageArray.length; ++i)
+ {
+ var messageUri = messageArray[i];
+ hdr = messenger.msgHdrFromURI(messageUri);
+ identity = GetIdentityForHeader(hdr, type);
+ if (FeedMessageHandler.isFeedMessage(hdr))
+ openComposeWindowForRSSArticle(null, hdr, messageUri, type,
+ format, identity, msgWindow);
+ else
+ msgComposeService.OpenComposeWindow(null, hdr, messageUri, type,
+ format, identity, null, msgWindow);
+ }
+ }
+}
+
+function NewMessageToSelectedAddresses(type, format, identity) {
+ var abSidebarPanel = document.commandDispatcher.focusedWindow;
+ var abResultsTree = abSidebarPanel.document.getElementById("abResultsTree");
+ var abResultsBoxObject = abResultsTree.treeBoxObject;
+ var abView = abResultsBoxObject.view;
+ abView = abView.QueryInterface(Ci.nsIAbView);
+ var addresses = abView.selectedAddresses;
+ var params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(Ci.nsIMsgComposeParams);
+ if (params) {
+ params.type = type;
+ params.format = format;
+ params.identity = identity;
+ var composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
+ if (composeFields) {
+ let addressList = [];
+ const nsISupportsString = Ci.nsISupportsString;
+ for (let i = 0; i < addresses.length; i++) {
+ addressList.push(addresses.queryElementAt(i, nsISupportsString).data);
+ }
+ composeFields.to = addressList.join(",");
+ params.composeFields = composeFields;
+ msgComposeService.OpenComposeWindowWithParams(null, params);
+ }
+ }
+}
+
+function Subscribe(preselectedMsgFolder)
+{
+ window.openDialog("chrome://messenger/content/subscribe.xul",
+ "subscribe", "chrome,modal,titlebar,resizable=yes",
+ {folder:preselectedMsgFolder,
+ okCallback:SubscribeOKCallback});
+}
+
+function SubscribeOKCallback(changeTable)
+{
+ for (var serverURI in changeTable) {
+ var folder = MailUtils.getFolderForURI(serverURI, true);
+ var server = folder.server;
+ var subscribableServer =
+ server.QueryInterface(Ci.nsISubscribableServer);
+
+ for (var name in changeTable[serverURI]) {
+ if (changeTable[serverURI][name] == true) {
+ try {
+ subscribableServer.subscribe(name);
+ }
+ catch (ex) {
+ dump("failed to subscribe to " + name + ": " + ex + "\n");
+ }
+ }
+ else if (changeTable[serverURI][name] == false) {
+ try {
+ subscribableServer.unsubscribe(name);
+ }
+ catch (ex) {
+ dump("failed to unsubscribe to " + name + ": " + ex + "\n");
+ }
+ }
+ else {
+ // no change
+ }
+ }
+
+ try {
+ subscribableServer.commitSubscribeChanges();
+ }
+ catch (ex) {
+ dump("failed to commit the changes: " + ex + "\n");
+ }
+ }
+}
+
+function SaveAsFile(aUris)
+{
+ if (/type=application\/x-message-display/.test(aUris[0]))
+ {
+ saveURL(aUris[0], null, "", true, false, null, document);
+ return;
+ }
+
+ var num = aUris.length;
+ var fileNames = [];
+ for (let i = 0; i < num; i++)
+ {
+ let subject = messenger.messageServiceFromURI(aUris[i])
+ .messageURIToMsgHdr(aUris[i])
+ .mime2DecodedSubject;
+ fileNames[i] = suggestUniqueFileName(subject.substr(0, 120), ".eml",
+ fileNames);
+ }
+ if (num == 1)
+ messenger.saveAs(aUris[0], true, null, fileNames[0]);
+ else
+ messenger.saveMessages(fileNames, aUris);
+}
+
+function saveAsUrlListener(aUri, aIdentity)
+{
+ this.uri = aUri;
+ this.identity = aIdentity;
+}
+
+saveAsUrlListener.prototype = {
+ OnStartRunningUrl: function(aUrl)
+ {
+ },
+ OnStopRunningUrl: function(aUrl, aExitCode)
+ {
+ messenger.saveAs(this.uri, false, this.identity, null);
+ }
+};
+
+function SaveAsTemplate(aUris)
+{
+ // For backwards compatibility check if the argument is a string and,
+ // if so, convert to an array.
+ if (typeof aUris == "string")
+ aUris = [aUris];
+
+ var num = aUris.length;
+ if (!num)
+ return;
+
+ for (let i = 0; i < num; i++)
+ {
+ let uri = aUris[i];
+ var hdr = messenger.msgHdrFromURI(uri);
+ var identity = GetIdentityForHeader(hdr, Ci.nsIMsgCompType.Template);
+ var templates = MailUtils.getFolderForURI(identity.stationeryFolder, false);
+ if (!templates.parent)
+ {
+ templates.setFlag(Ci.nsMsgFolderFlags.Templates);
+ let isAsync = templates.server.protocolInfo.foldersCreatedAsync;
+ templates.createStorageIfMissing(new saveAsUrlListener(uri, identity));
+ if (isAsync)
+ continue;
+ }
+ messenger.saveAs(uri, false, identity, null);
+ }
+}
+
+function MarkSelectedMessagesRead(markRead)
+{
+ ClearPendingReadTimer();
+ gDBView.doCommand(markRead ? nsMsgViewCommandType.markMessagesRead : nsMsgViewCommandType.markMessagesUnread);
+}
+
+function MarkSelectedMessagesFlagged(markFlagged)
+{
+ gDBView.doCommand(markFlagged ? nsMsgViewCommandType.flagMessages : nsMsgViewCommandType.unflagMessages);
+}
+
+function ViewPageSource(messages)
+{
+ var numMessages = messages.length;
+
+ if (numMessages == 0)
+ {
+ dump("MsgViewPageSource(): No messages selected.\n");
+ return false;
+ }
+
+ var browser = getBrowser();
+
+ try {
+ // First, get the mail session.
+ for (let i = 0; i < numMessages; i++) {
+ // Now, we need to get a URL from a URI.
+ var url = MailServices.mailSession.ConvertMsgURIToMsgURL(messages[i],
+ msgWindow);
+
+ // Strip out the message-display parameter to ensure that attached
+ // emails display the message source, not the processed HTML.
+ url = url.replace(/(\?|&)type=application\/x-message-display(&|$)/, "$1")
+ .replace(/\?$/, "");
+ window.openDialog("chrome://global/content/viewSource.xul", "_blank",
+ "all,dialog=no",
+ {URL: url, browser: browser,
+ outerWindowID: browser.outerWindowID});
+ }
+ return true;
+ } catch (e) {
+ // Couldn't get mail session.
+ return false;
+ }
+}
+
+function doHelpButton()
+{
+ openHelp("mail-offline-items");
+}
diff --git a/comm/suite/mailnews/content/mailContextMenus.js b/comm/suite/mailnews/content/mailContextMenus.js
new file mode 100644
index 0000000000..d70d7a31fc
--- /dev/null
+++ b/comm/suite/mailnews/content/mailContextMenus.js
@@ -0,0 +1,828 @@
+/* -*- 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/. */
+
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+
+//NOTE: gMessengerBundle must be defined and set or this Overlay won't work
+
+/**
+ * Function to change the highlighted row back to the row that is currently
+ * outline/dotted without loading the contents of either rows. This is
+ * triggered when the context menu for a given row is hidden/closed
+ * (onpopuphiding).
+ * @param tree the tree element to restore selection for
+ */
+function RestoreSelectionWithoutContentLoad(tree)
+{
+ // If a delete or move command had been issued, then we should
+ // reset gRightMouseButtonDown and gThreadPaneDeleteOrMoveOccurred
+ // and return (see bug 142065).
+ if(gThreadPaneDeleteOrMoveOccurred)
+ {
+ gRightMouseButtonDown = false;
+ gThreadPaneDeleteOrMoveOccurred = false;
+ return;
+ }
+
+ var treeSelection = tree.view.selection;
+
+ // make sure that currentIndex is valid so that we don't try to restore
+ // a selection of an invalid row.
+ if((!treeSelection.isSelected(treeSelection.currentIndex)) &&
+ (treeSelection.currentIndex >= 0))
+ {
+ treeSelection.selectEventsSuppressed = true;
+ treeSelection.select(treeSelection.currentIndex);
+ treeSelection.selectEventsSuppressed = false;
+
+ // Keep track of which row in the thread pane is currently selected.
+ // This is currently only needed when deleting messages. See
+ // declaration of var in msgMail3PaneWindow.js.
+ if(tree.id == "threadTree")
+ gThreadPaneCurrentSelectedIndex = treeSelection.currentIndex;
+ }
+ else if(treeSelection.currentIndex < 0)
+ // Clear the selection in the case of when a folder has just been
+ // loaded where the message pane does not have a message loaded yet.
+ // When right-clicking a message in this case and dismissing the
+ // popup menu (by either executing a menu command or clicking
+ // somewhere else), the selection needs to be cleared.
+ // However, if the 'Delete Message' or 'Move To' menu item has been
+ // selected, DO NOT clear the selection, else it will prevent the
+ // tree view from refreshing.
+ treeSelection.clearSelection();
+
+ // Need to reset gRightMouseButtonDown to false here because
+ // TreeOnMouseDown() is only called on a mousedown, not on a key down.
+ // So resetting it here allows the loading of messages in the messagepane
+ // when navigating via the keyboard or the toolbar buttons *after*
+ // the context menu has been dismissed.
+ gRightMouseButtonDown = false;
+}
+
+/**
+ * Function to clear out the global nsContextMenu, and in the case when we
+ * are a threadpane context menu, restore the selection so that a right-click
+ * on a non-selected row doesn't move the selection.
+ * @param aTarget the target of the popup event
+ */
+function MailContextOnPopupHiding(aTarget, aEvent) {
+ // Don't do anything if it's a submenu's onpopuphiding that's just bubbling
+ // up to the top.
+ if (aEvent.target != aTarget)
+ return;
+
+ gContextMenu.hiding();
+ gContextMenu = null;
+ if (InThreadPane(aTarget))
+ RestoreSelectionWithoutContentLoad(GetThreadTree());
+}
+
+/**
+ * Determines whether the context menu was triggered by a node that's a child
+ * of the threadpane by looking for an ancestor node with id="threadTree".
+ * @param aTarget the target of the popup event
+ * @return true if the popupNode is a child of the threadpane, otherwise false
+ */
+function InThreadPane(aTarget)
+{
+ var node = aTarget.triggerNode;
+ while (node)
+ {
+ if (node.id == "threadTree")
+ return true;
+ node = node.parentNode;
+ }
+ return false;
+}
+
+/**
+ * Function to set up the global nsContextMenu, and the mailnews overlay.
+ * @param aTarget the target of the popup event
+ * @return true always
+ */
+function FillMailContextMenu(aTarget, aEvent) {
+ // If the popupshowing was for a submenu, we don't need to do anything.
+ if (aEvent.target != aTarget)
+ return true;
+
+ var inThreadPane = InThreadPane(aTarget);
+ gContextMenu = new nsContextMenu(aTarget);
+
+ // Initialize gContextMenuContentData.
+ if (aEvent)
+ gContextMenu.initContentData(aEvent);
+
+ // Need to call nsContextMenu's initItems to hide what is not used.
+ gContextMenu.initItems();
+
+ var numSelected = GetNumSelectedMessages();
+ var oneOrMore = (numSelected > 0);
+ var single = (numSelected == 1);
+
+ var isNewsgroup = gFolderDisplay.selectedMessageIsNews;
+
+ // Clear the global var used to keep track if a 'Delete Message' or 'Move
+ // To' command has been triggered via the thread pane context menu.
+ gThreadPaneDeleteOrMoveOccurred = false;
+
+ // Don't show mail items for links/images, just show related items.
+ var showMailItems = inThreadPane ||
+ (!gContextMenu.onImage && !gContextMenu.onLink);
+
+ // Select-all and copy are only available in the message-pane
+ ShowMenuItem("context-selectall", single && !inThreadPane);
+ ShowMenuItem("context-copy", !inThreadPane);
+
+ ShowMenuItem("mailContext-openNewWindow", inThreadPane && single);
+ ShowMenuItem("mailContext-openNewTab", inThreadPane && single);
+ ShowMenuItem("mailContext-downloadflagged",
+ inThreadPane || (numSelected > 1));
+ ShowMenuItem("mailContext-downloadselected",
+ inThreadPane || (numSelected > 1));
+
+ ShowMenuItem("mailContext-editAsNew", showMailItems && oneOrMore);
+ // Show "Edit Draft Message" menus only in a drafts folder;
+ // otherwise hide them.
+ let showEditDraft = showCommandInSpecialFolder("cmd_editDraftMsg",
+ Ci.nsMsgFolderFlags.Drafts);
+ ShowMenuItem("mailContext-editDraftMsg",
+ showMailItems && oneOrMore && showEditDraft);
+ // Show "New Message from Template" and "Edit Template" menus only in a
+ // templates folder; otherwise hide them.
+ let showTemplates = showCommandInSpecialFolder("cmd_newMsgFromTemplate",
+ Ci.nsMsgFolderFlags.Templates);
+ ShowMenuItem("mailContext-newMsgFromTemplate",
+ showMailItems && oneOrMore && showTemplates);
+ showTemplates = showCommandInSpecialFolder("cmd_editTemplateMsg",
+ Ci.nsMsgFolderFlags.Templates);
+ ShowMenuItem("mailContext-editTemplateMsg",
+ showMailItems && oneOrMore && showTemplates);
+
+ ShowMenuItem("mailContext-replySender", showMailItems && single);
+ ShowMenuItem("mailContext-replyList",
+ showMailItems && single && !isNewsgroup && IsListPost());
+ ShowMenuItem("mailContext-replyNewsgroup",
+ showMailItems && single && isNewsgroup);
+ ShowMenuItem("mailContext-replySenderAndNewsgroup",
+ showMailItems && single && isNewsgroup);
+ ShowMenuItem("mailContext-replyAll", showMailItems && single);
+ ShowMenuItem("mailContext-forward", showMailItems && single);
+ ShowMenuItem("mailContext-forwardAsAttachment",
+ showMailItems && (numSelected > 1));
+ ShowMenuItem("mailContext-copyMessageUrl",
+ showMailItems && single && isNewsgroup);
+ ShowMenuItem("mailContext-archive", showMailItems && oneOrMore &&
+ gFolderDisplay.canArchiveSelectedMessages);
+
+ // Set up the move menu. We can't move from newsgroups.
+ // Disable move if we can't delete message(s) from this folder.
+ var msgFolder = GetLoadedMsgFolder();
+ ShowMenuItem("mailContext-moveMenu",
+ showMailItems && oneOrMore && !isNewsgroup);
+ EnableMenuItem("mailContext-moveMenu",
+ oneOrMore && msgFolder && msgFolder.canDeleteMessages);
+
+ // Copy is available as long as something is selected.
+ var canCopy = showMailItems && oneOrMore && (!gMessageDisplay.isDummy ||
+ window.arguments[0].scheme == "file");
+ ShowMenuItem("mailContext-copyMenu", canCopy);
+ ShowMenuItem("mailContext-tags", showMailItems && oneOrMore);
+ ShowMenuItem("mailContext-mark", showMailItems && oneOrMore);
+ ShowMenuItem("mailContext-saveAs", showMailItems && oneOrMore);
+ ShowMenuItem("mailContext-printpreview", showMailItems && single);
+
+ ShowMenuItem("mailContext-print", showMailItems);
+ EnableMenuItem("mailContext-print", oneOrMore);
+ ShowMenuItem("mailContext-delete", showMailItems);
+ EnableMenuItem("mailContext-delete", oneOrMore);
+ // This function is needed for the case where a folder is just loaded
+ // (while there isn't a message loaded in the message pane), a right-click
+ // is done in the thread pane. This function will disable enable the
+ // 'Delete Message' menu item.
+ goUpdateCommand('cmd_delete');
+
+ ShowMenuItem("context-addemail", gContextMenu.onMailtoLink);
+ ShowMenuItem("context-composeemailto", gContextMenu.onMailtoLink);
+ ShowMenuItem("context-createfilterfrom", gContextMenu.onMailtoLink);
+
+ // Figure out separators.
+ initSeparators();
+
+ return true;
+}
+
+/**
+ * Hide separators with no active menu items.
+ *
+ */
+function initSeparators() {
+ const mailContextSeparators = [
+ "mailContext-sep-link", "mailContext-sep-open",
+ "mailContext-sep-tags", "mailContext-sep-mark",
+ "mailContext-sep-move", "mailContext-sep-print",
+ "mailContext-sep-edit", "mailContext-sep-image",
+ "mailContext-sep-blockimage", "mailContext-sep-copy",
+ ];
+
+ mailContextSeparators.forEach(hideIfAppropriate);
+}
+
+/**
+ * Hide a separator based on whether there are any non-hidden items between
+ * it and the previous separator.
+ *
+ * @param aID The id of the separator element.
+ */
+function hideIfAppropriate(aID) {
+ let separator = document.getElementById(aID);
+
+ function hasAVisibleNextSibling(aNode) {
+ let sibling = aNode.nextSibling;
+ while (sibling) {
+ if (sibling.getAttribute("hidden") != "true" &&
+ sibling.localName != "menuseparator")
+ return true;
+ sibling = sibling.nextSibling;
+ }
+ return false;
+ }
+
+ let sibling = separator.previousSibling;
+ while (sibling) {
+ if (sibling.getAttribute("hidden") != "true") {
+ ShowMenuItem(aID, sibling.localName != "menuseparator" &&
+ hasAVisibleNextSibling(separator));
+ return;
+ }
+ sibling = sibling.previousSibling;
+ }
+ ShowMenuItem(aID, false);
+}
+
+function FolderPaneOnPopupHiding()
+{
+ RestoreSelectionWithoutContentLoad(document.getElementById("folderTree"));
+}
+
+function FillFolderPaneContextMenu()
+{
+ // Do not show menu if rows are selected.
+ let folders = gFolderTreeView.getSelectedFolders();
+ let numSelected = folders.length;
+ if (!numSelected)
+ return false;
+
+ function checkIsVirtualFolder(folder) {
+ return folder.getFlag(Ci.nsMsgFolderFlags.Virtual);
+ }
+ let haveAnyVirtualFolders = folders.some(checkIsVirtualFolder);
+
+ function checkIsServer(folder) {
+ return folder.isServer;
+ }
+ let selectedServers = folders.filter(checkIsServer);
+
+ let folder = folders[0];
+ let isServer = folder.isServer;
+ let serverType = folder.server.type;
+ let specialFolder = haveAnyVirtualFolders ? "Virtual" :
+ FolderUtils.getSpecialFolderString(folder);
+
+ function checkCanSubscribeToFolder(folder) {
+ if (checkIsVirtualFolder(folder))
+ return false;
+
+ // All feed account folders, besides Trash, are subscribable.
+ if (folder.server.type == "rss" &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Trash))
+ return true;
+
+ // We only want the subscribe item on the account nodes.
+ if (!folder.isServer)
+ return false;
+
+ return folder.server.type == "nntp" ||
+ folder.server.type == "imap";
+ }
+ let haveOnlySubscribableFolders = folders.every(checkCanSubscribeToFolder);
+
+ function checkIsNewsgroup(folder) {
+ return !folder.isServer && folder.server.type == "nntp" &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual);
+ }
+ let haveOnlyNewsgroups = folders.every(checkIsNewsgroup);
+
+ function checkIsMailFolder(folder) {
+ return !folder.isServer && folder.server.type != "nntp";
+ }
+ let haveOnlyMailFolders = folders.every(checkIsMailFolder);
+
+ function checkCanGetMessages(folder) {
+ return (folder.isServer && (folder.server.type != "none")) ||
+ checkIsNewsgroup(folder) ||
+ ((folder.server.type == "rss") &&
+ !folder.isSpecialFolder(Ci.nsMsgFolderFlags.Trash, true) &&
+ !checkIsVirtualFolder(folder));
+ }
+ let selectedFoldersThatCanGetMessages = folders.filter(checkCanGetMessages);
+
+ // --- Set up folder properties / account settings menu item.
+ if (numSelected != 1) {
+ ShowMenuItem("folderPaneContext-settings", false);
+ ShowMenuItem("folderPaneContext-properties", false);
+ }
+ else if (selectedServers.length != 1) {
+ ShowMenuItem("folderPaneContext-settings", false);
+ ShowMenuItem("folderPaneContext-properties", true);
+ }
+ else {
+ ShowMenuItem("folderPaneContext-properties", false);
+ ShowMenuItem("folderPaneContext-settings", true);
+ }
+
+ // --- Set up the get messages menu item.
+ // Show if only servers, or it's only newsgroups/feeds. We could mix,
+ // but it gets messy for situations where both server and a folder
+ // on the server are selected.
+ let showGet = selectedFoldersThatCanGetMessages.length == numSelected;
+ ShowMenuItem("folderPaneContext-getMessages", showGet);
+ if (showGet) {
+ if (selectedServers.length > 0 &&
+ selectedServers.length == selectedFoldersThatCanGetMessages.length) {
+ SetMenuItemLabel("folderPaneContext-getMessages",
+ gMessengerBundle.getString("getMessagesFor"));
+ }
+ else {
+ SetMenuItemLabel("folderPaneContext-getMessages",
+ gMessengerBundle.getString("getMessages"));
+ }
+ }
+
+ // --- Setup the Mark All Folders Read menu item.
+ // Show only in case the server item is selected.
+ ShowMenuItem("folderPaneContext-markAllFoldersRead",
+ selectedServers.length > 0);
+
+ // --- Set up new sub/folder menu item.
+ let isInbox = specialFolder == "Inbox";
+ let showNew =
+ (numSelected == 1) &&
+ ((serverType != "nntp" && folder.canCreateSubfolders) || isInbox);
+ ShowMenuItem("folderPaneContext-new", showNew);
+ if (showNew) {
+ EnableMenuItem("folderPaneContext-new",
+ serverType != "imap" || !Services.io.offline);
+ let label = (isServer || isInbox) ? "newFolder" : "newSubfolder";
+ SetMenuItemLabel("folderPaneContext-new",
+ gMessengerBundle.getString(label));
+ }
+
+ // --- Set up rename menu item.
+ let canRename = (numSelected == 1) && !isServer && folder.canRename &&
+ (specialFolder == "none" || specialFolder == "Virtual" ||
+ (specialFolder == "Junk" &&
+ CanRenameDeleteJunkMail(folder.URI)));
+ ShowMenuItem("folderPaneContext-rename", canRename);
+ if (canRename) {
+ EnableMenuItem("folderPaneContext-rename",
+ !isServer && folder.isCommandEnabled("cmd_renameFolder"));
+ SetMenuItemLabel("folderPaneContext-rename",
+ gMessengerBundle.getString("renameFolder"));
+ }
+
+ // --- Set up the delete folder menu item.
+ function checkCanDeleteFolder(folder) {
+ if (folder.isSpecialFolder(Ci.nsMsgFolderFlags.Junk, false))
+ return CanRenameDeleteJunkMail(folder.URI);
+ return folder.deletable;
+ }
+ let haveOnlyDeletableFolders = folders.every(checkCanDeleteFolder);
+ ShowMenuItem("folderPaneContext-remove",
+ haveOnlyDeletableFolders && numSelected == 1);
+ if (haveOnlyDeletableFolders && numSelected == 1)
+ SetMenuItemLabel("folderPaneContext-remove",
+ gMessengerBundle.getString("removeFolder"));
+
+ function checkIsDeleteEnabled(folder) {
+ return folder.isCommandEnabled("cmd_delete");
+ }
+ let haveOnlyDeleteEnabledFolders = folders.every(checkIsDeleteEnabled);
+ EnableMenuItem("folderPaneContext-remove", haveOnlyDeleteEnabledFolders);
+
+ // --- Set up the compact folder menu item.
+ function checkCanCompactFolder(folder) {
+ return folder.canCompact &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Virtual) &&
+ folder.isCommandEnabled("cmd_compactFolder");
+ }
+ let haveOnlyCompactableFolders = folders.every(checkCanCompactFolder);
+ ShowMenuItem("folderPaneContext-compact", haveOnlyCompactableFolders);
+ if (haveOnlyCompactableFolders)
+ SetMenuItemLabel("folderPaneContext-compact",
+ PluralForm.get(numSelected, gMessengerBundle.getString("compactFolders")));
+
+ function checkIsCompactEnabled(folder) {
+ return folder.isCommandEnabled("cmd_compactFolder");
+ }
+ let haveOnlyCompactEnabledFolders = folders.every(checkIsCompactEnabled);
+ EnableMenuItem("folderPaneContext-compact", haveOnlyCompactEnabledFolders);
+
+ // --- Set up favorite folder menu item.
+ let showFavorite = (numSelected == 1) && !isServer;
+ ShowMenuItem("folderPaneContext-favoriteFolder", showFavorite);
+ if (showFavorite) {
+ // Adjust the checked state on the menu item.
+ document.getElementById("folderPaneContext-favoriteFolder")
+ .setAttribute("checked",
+ folder.getFlag(Ci.nsMsgFolderFlags.Favorite));
+ }
+
+ // --- Set up the empty trash menu item.
+ ShowMenuItem("folderPaneContext-emptyTrash",
+ numSelected == 1 && specialFolder == "Trash");
+
+ // --- Set up the empty junk menu item.
+ ShowMenuItem("folderPaneContext-emptyJunk",
+ numSelected == 1 && specialFolder == "Junk");
+
+ // --- Set up the send unsent messages menu item.
+ let showSendUnsentMessages = numSelected == 1 && specialFolder == "Outbox";
+ ShowMenuItem("folderPaneContext-sendUnsentMessages", showSendUnsentMessages);
+ if (showSendUnsentMessages)
+ EnableMenuItem("folderPaneContext-sendUnsentMessages",
+ IsSendUnsentMsgsEnabled(folder));
+
+ // --- Set up the subscribe menu item.
+ ShowMenuItem("folderPaneContext-subscribe",
+ numSelected == 1 && haveOnlySubscribableFolders);
+
+ // --- Set up the unsubscribe menu item.
+ ShowMenuItem("folderPaneContext-newsUnsubscribe", haveOnlyNewsgroups);
+
+ // --- Set up the mark newsgroup/s read menu item.
+ ShowMenuItem("folderPaneContext-markNewsgroupAllRead", haveOnlyNewsgroups);
+ SetMenuItemLabel("folderPaneContext-markNewsgroupAllRead",
+ PluralForm.get(numSelected, gMessengerBundle.getString("markNewsgroupRead")));
+
+ // --- Set up the mark folder/s read menu item.
+ ShowMenuItem("folderPaneContext-markMailFolderAllRead",
+ haveOnlyMailFolders && !haveAnyVirtualFolders);
+ SetMenuItemLabel("folderPaneContext-markMailFolderAllRead",
+ PluralForm.get(numSelected, gMessengerBundle.getString("markFolderRead")));
+
+ // Set up the search menu item.
+ ShowMenuItem("folderPaneContext-searchMessages",
+ numSelected == 1 && !haveAnyVirtualFolders);
+ goUpdateCommand('cmd_search');
+
+ ShowMenuItem("folderPaneContext-openNewWindow", numSelected == 1);
+ ShowMenuItem("folderPaneContext-openNewTab", numSelected == 1);
+
+ // Hide / Show our menu separators based on the menu items we are showing.
+ hideIfAppropriate("folderPaneContext-sep1");
+ hideIfAppropriate("folderPaneContext-sep-edit");
+ hideIfAppropriate("folderPaneContext-sep4");
+
+ return true;
+}
+
+function ShowMenuItem(id, showItem)
+{
+ var item = document.getElementById(id);
+ if(item && item.hidden != "true")
+ item.hidden = !showItem;
+}
+
+function EnableMenuItem(id, enableItem)
+{
+ var item = document.getElementById(id);
+ if(item)
+ {
+ var enabled = (item.getAttribute('disabled') !='true');
+ if(enableItem != enabled)
+ {
+ item.setAttribute('disabled', enableItem ? '' : 'true');
+ }
+ }
+}
+
+function SetMenuItemLabel(id, label)
+{
+ var item = document.getElementById(id);
+ if(item)
+ item.setAttribute('label', label);
+}
+
+function SetMenuItemAccessKey(id, accessKey)
+{
+ var item = document.getElementById(id);
+ if(item)
+ item.setAttribute('accesskey', accessKey);
+}
+
+// message pane context menu helper methods
+function AddContact(aEmailAddressNode)
+{
+ if (aEmailAddressNode)
+ AddEmailToAddressBook(aEmailAddressNode.getAttribute("emailAddress"),
+ aEmailAddressNode.getAttribute("displayName"));
+}
+
+function AddEmailToAddressBook(primaryEmail, displayName)
+{
+ window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
+ "", "chrome,resizable=no,titlebar,modal,centerscreen",
+ {primaryEmail:primaryEmail, displayName:displayName});
+}
+
+function EditContact(aEmailAddressNode)
+{
+ if (aEmailAddressNode.cardDetails.card)
+ {
+ window.openDialog("chrome://messenger/content/addressbook/abEditCardDialog.xul",
+ "", "chrome,resizable=no,modal,titlebar,centerscreen",
+ { abURI: aEmailAddressNode.cardDetails.book.URI,
+ card: aEmailAddressNode.cardDetails.card });
+ }
+}
+
+/**
+ * SendMailToNode takes the email address title button, extracts the email address
+ * we stored in there and opens a compose window with that address.
+ *
+ * @param addressNode a node which has a "fullAddress" attribute
+ * @param aEvent the event object when user triggers the menuitem
+ */
+function SendMailToNode(emailAddressNode, aEvent)
+{
+ if (emailAddressNode)
+ SendMailTo(emailAddressNode.getAttribute("fullAddress"), aEvent);
+}
+
+function SendMailTo(fullAddress, aEvent)
+{
+ var fields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+ var params = Cc["@mozilla.org/messengercompose/composeparams;1"]
+ .createInstance(Ci.nsIMsgComposeParams);
+
+ var headerParser = MailServices.headerParser;
+ var addresses = headerParser.makeFromDisplayAddress(fullAddress);
+ fields.to = headerParser.makeMimeHeader([addresses[0]]);
+ params.type = Ci.nsIMsgCompType.New;
+
+ // If aEvent is passed, check if Shift key was pressed for composition in
+ // non-default format (HTML vs. plaintext).
+ params.format = (aEvent && aEvent.shiftKey) ?
+ Ci.nsIMsgCompFormat.OppositeOfDefault :
+ Ci.nsIMsgCompFormat.Default;
+
+ params.identity = accountManager.getFirstIdentityForServer(GetLoadedMsgFolder().server);
+ params.composeFields = fields;
+ MailServices.compose.OpenComposeWindowWithParams(null, params);
+}
+
+/**
+ * Takes the email address, extracts the address/name
+ * we stored in there and copies it to the clipboard.
+ *
+ * @param addressNode a node which has an "emailAddress"
+ * attribute
+ * @param aIncludeName when true, also copy the name onto the clipboard,
+ * otherwise only the email address
+ */
+function CopyEmailAddress(emailAddressNode, aIncludeName = false)
+{
+ if (emailAddressNode) {
+ let address = emailAddressNode.getAttribute(aIncludeName ? "fullAddress"
+ : "emailAddress");
+ CopyString(address);
+ }
+}
+
+// show the message id in the context menu
+function FillMessageIdContextMenu(messageIdNode)
+{
+ var msgId = messageIdNode.getAttribute("messageid");
+ document.getElementById("messageIdContext-messageIdTarget")
+ .setAttribute("label", msgId);
+
+ // We don't want to show "Open Message For ID" for the same message
+ // we're viewing.
+ var currentMsgId = "<" + gFolderDisplay.selectedMessage.messageId + ">";
+ document.getElementById("messageIdContext-openMessageForMsgId")
+ .hidden = (currentMsgId == msgId);
+
+ // We don't want to show "Open Browser With Message-ID" for non-nntp messages.
+ document.getElementById("messageIdContext-openBrowserWithMsgId")
+ .hidden = !gFolderDisplay.selectedMessageIsNews;
+}
+
+function GetMessageIdFromNode(messageIdNode, cleanMessageId)
+{
+ var messageId = messageIdNode.getAttribute("messageid");
+
+ // remove < and >
+ if (cleanMessageId)
+ messageId = messageId.substring(1, messageId.length - 1);
+
+ return messageId;
+}
+
+// take the message id from the messageIdNode and use the
+// url defined in the hidden pref "mailnews.messageid_browser.url"
+// to open it in a browser window (%mid is replaced by the message id)
+function OpenBrowserWithMessageId(messageId)
+{
+ var browserURL = GetLocalizedStringPref("mailnews.messageid_browser.url");
+ if (browserURL)
+ openAsExternal(browserURL.replace(/%mid/, messageId));
+}
+
+// take the message id from the messageIdNode, search for the
+// corresponding message in all folders starting with the current
+// selected folder, then the current account followed by the other
+// accounts and open corresponding message if found
+function OpenMessageForMessageId(messageId)
+{
+ var startServer = gDBView.msgFolder.server;
+ var messageHeader;
+
+ window.setCursor("wait");
+
+ // first search in current folder for message id
+ var messageHeader = CheckForMessageIdInFolder(gDBView.msgFolder, messageId);
+
+ // if message id not found in current folder search in all folders
+ if (!messageHeader)
+ {
+ messageHeader = SearchForMessageIdInSubFolder(startServer.rootFolder, messageId);
+
+ for (let currentServer of MailServices.accounts.allServers)
+ {
+ if (currentServer && startServer != currentServer &&
+ currentServer.canSearchMessages && !currentServer.isDeferredTo)
+ {
+ messageHeader = SearchForMessageIdInSubFolder(currentServer.rootFolder, messageId);
+ }
+ }
+ }
+ window.setCursor("auto");
+
+ // if message id was found open corresponding message
+ // else show error message
+ if (messageHeader)
+ OpenMessageByHeader(messageHeader, Services.prefs.getBoolPref("mailnews.messageid.openInNewWindow"));
+ else
+ {
+ var messageIdStr = "<" + messageId + ">";
+ var errorTitle = gMessengerBundle.getString("errorOpenMessageForMessageIdTitle");
+ var errorMessage = gMessengerBundle.getFormattedString("errorOpenMessageForMessageIdMessage",
+ [messageIdStr]);
+ Services.prompt.alert(window, errorTitle, errorMessage);
+ }
+}
+
+function OpenMessageByHeader(messageHeader, openInNewWindow)
+{
+ var folder = messageHeader.folder;
+ var folderURI = folder.URI;
+
+ if (openInNewWindow)
+ {
+ var messageURI = folder.getUriForMsg(messageHeader);
+
+ window.openDialog("chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar",
+ messageURI, folderURI, null);
+ }
+ else
+ {
+ if (msgWindow.openFolder != folderURI)
+ gFolderTreeView.selectFolder(folder)
+
+ var tree = null;
+ var wintype = document.documentElement.getAttribute('windowtype');
+ if (wintype != "mail:messageWindow")
+ {
+ tree = GetThreadTree();
+ tree.view.selection.clearSelection();
+ }
+
+ try
+ {
+ gDBView.selectMsgByKey(messageHeader.messageKey);
+ }
+ catch(e)
+ { // message not in the thread pane
+ try
+ {
+ goDoCommand("cmd_viewAllMsgs");
+ gDBView.selectMsgByKey(messageHeader.messageKey);
+ }
+ catch(e)
+ {
+ dump("select messagekey " + messageHeader.messageKey +
+ " failed in folder " + folder.URI);
+ }
+ }
+
+ if (tree && tree.currentIndex != -1)
+ tree.treeBoxObject.ensureRowIsVisible(tree.currentIndex);
+ }
+}
+
+// search for message by message id in given folder and its subfolders
+// return message header if message was found
+function SearchForMessageIdInSubFolder(folder, messageId)
+{
+ var messageHeader;
+
+ // search in folder
+ if (!folder.isServer)
+ messageHeader = CheckForMessageIdInFolder(folder, messageId);
+
+ // search subfolders recursively
+ for (let currentFolder of folder.subFolders) {
+ // search in current folder
+ messageHeader = CheckForMessageIdInFolder(currentFolder, messageId);
+
+ // search in its subfolder
+ if (!messageHeader && currentFolder.hasSubFolders)
+ messageHeader = SearchForMessageIdInSubFolder(currentFolder, messageId);
+ }
+
+ return messageHeader;
+}
+
+// check folder for corresponding message to given message id
+// return message header if message was found
+function CheckForMessageIdInFolder(folder, messageId)
+{
+ var messageDatabase = folder.msgDatabase;
+ var messageHeader;
+
+ try
+ {
+ messageHeader = messageDatabase.getMsgHdrForMessageID(messageId);
+ }
+ catch (ex)
+ {
+ dump("Failed to find message-id in folder!");
+ }
+
+ if (!MailServices.mailSession.IsFolderOpenInWindow(folder) &&
+ !folder.getFlag(Ci.nsMsgFolderFlags.Trash | Ci.nsMsgFolderFlags.Inbox))
+ {
+ folder.msgDatabase = null;
+ }
+
+ return messageHeader;
+}
+
+// CreateFilter opens the Message Filters and Filter Rules dialogs.
+//The Filter Rules dialog has focus. The window is prefilled with filtername <email address>
+//Sender condition is selected and the value is prefilled <email address>
+function CreateFilter(emailAddressNode)
+{
+ if (emailAddressNode)
+ CreateFilterFromMail(emailAddressNode.getAttribute("emailAddress"));
+}
+
+function CreateFilterFromMail(emailAddress)
+{
+ if (emailAddress)
+ top.MsgFilters(emailAddress, GetFirstSelectedMsgFolder());
+}
+
+function CopyMessageUrl()
+{
+ try
+ {
+ var hdr = gDBView.hdrForFirstSelectedMessage;
+ var server = hdr.folder.server;
+
+ // TODO let backend construct URL and return as attribute
+ var url = (server.socketType == Ci.nsMsgSocketType.SSL) ?
+ "snews://" : "news://";
+ url += server.hostName + ":" + server.port + "/" + hdr.messageId;
+ CopyString(url);
+ }
+ catch (ex)
+ {
+ dump("ex="+ex+"\n");
+ }
+}
+
+function CopyString(aString)
+{
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(aString);
+}
diff --git a/comm/suite/mailnews/content/mailEditorOverlay.xul b/comm/suite/mailnews/content/mailEditorOverlay.xul
new file mode 100644
index 0000000000..7eb6d651ad
--- /dev/null
+++ b/comm/suite/mailnews/content/mailEditorOverlay.xul
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<!-- retrieve generic commands -->
+<?xul-overlay href="chrome://messenger/content/mailOverlay.xul"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/mailEditorOverlay.dtd" >
+
+<overlay id="mailEditorOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script>
+ <![CDATA[
+
+ function openComposeWindow(pageUrl, pageTitle)
+ {
+ var params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(Ci.nsIMsgComposeParams);
+ if (params)
+ {
+ params.composeFields = Cc['@mozilla.org/messengercompose/composefields;1'].createInstance(Ci.nsIMsgCompFields);
+ if (params.composeFields)
+ {
+ params.composeFields.body = pageUrl;
+ params.composeFields.subject = pageTitle;
+ var attachmentData = Cc["@mozilla.org/messengercompose/attachment;1"].createInstance(Ci.nsIMsgAttachment);
+ if (attachmentData)
+ {
+ attachmentData.url = pageUrl;
+ params.composeFields.addAttachment(attachmentData);
+ }
+ params.bodyIsLink = true;
+
+ var composeService = Cc["@mozilla.org/messengercompose;1"].getService(Ci.nsIMsgComposeService);
+ if (composeService)
+ composeService.OpenComposeWindowWithParams(null, params);
+ }
+ }
+ }
+
+ ]]>
+ </script>
+
+ <!-- editor specific UI items -->
+ <menupopup id="menu_NewPopup">
+ <!-- Command nodes and implemention are in mailOverlay.xul -->
+ <menuitem id="menu_newMessage" insertafter="menu_newPrivateWindow"/>
+ <menuitem id="menu_newCard" insertafter="menu_newPrivateWindow"/>
+ </menupopup>
+
+ <menupopup id="menu_FilePopup">
+ <!-- The command node cmd_editSendPage is in editor.xul.
+ Implementation is in ComposerCommands.js
+ -->
+ <menuitem id="menu_sendPage" label="&sendPage.label;" accesskey="&sendPage.accesskey;" observes="cmd_editSendPage" insertafter="previewInBrowser"/>
+ </menupopup>
+
+</overlay>
+
diff --git a/comm/suite/mailnews/content/mailKeysOverlay.xul b/comm/suite/mailnews/content/mailKeysOverlay.xul
new file mode 100644
index 0000000000..e89cee3968
--- /dev/null
+++ b/comm/suite/mailnews/content/mailKeysOverlay.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/mailKeysOverlay.dtd">
+
+<overlay id="mailKeysOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <keyset id="mailKeys">
+ <key id="key_delete"/>
+ <key id="key_delete2"/> <!-- secondary delete key -->
+#ifdef XP_MACOSX
+ <!-- not all Mac keyboards have a VK_DELETE key, so we use VK_BACK as
+ the primary and provide VK_DELETE as a secondary key definition -->
+ <key id="key_shiftDelete" keycode="VK_BACK"
+ modifiers="shift" command="cmd_shiftDelete"/>
+ <key id="key_shiftDelete2" keycode="VK_DELETE"
+ modifiers="shift" command="cmd_shiftDelete"/>
+#else
+ <key id="key_shiftDelete" keycode="VK_DELETE"
+ modifiers="shift" command="cmd_shiftDelete"/>
+#endif
+ <key id="key_selectAll"/>
+
+ <key id="key_markAsRead"
+ key="&markAsReadCmd.key;"
+ oncommand="goDoCommand('cmd_markAsRead');"/>
+ <key id="key_markAsUnread"
+ key="&markAsUnreadCmd2.key;"
+ oncommand="goDoCommand('cmd_markAsUnread');"/>
+ <key id="key_toggleFlagged" key="&markFlaggedCmd.key;"
+ oncommand="goDoCommand('cmd_markAsFlagged');"/>
+ <key id="key_openMessage" key="&openMessageWindowCmd.key;"
+ modifiers="accel" oncommand="goDoCommand('cmd_openMessage');"/>
+
+ <!-- Tag Keys -->
+ <!-- Includes both shifted and not, for Azerty and other layouts where the
+ numeric keys are shifted. -->
+ <key id="key_tag0" key="&tagCmd0.key;" modifiers="shift any"
+ oncommand="RemoveAllMessageTags();"/>
+ <key id="key_tag1" key="&tagCmd1.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(1);"/>
+ <key id="key_tag2" key="&tagCmd2.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(2);"/>
+ <key id="key_tag3" key="&tagCmd3.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(3);"/>
+ <key id="key_tag4" key="&tagCmd4.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(4);"/>
+ <key id="key_tag5" key="&tagCmd5.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(5);"/>
+ <key id="key_tag6" key="&tagCmd6.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(6);"/>
+ <key id="key_tag7" key="&tagCmd7.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(7);"/>
+ <key id="key_tag8" key="&tagCmd8.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(8);"/>
+ <key id="key_tag9" key="&tagCmd9.key;" modifiers="shift any"
+ oncommand="ToggleMessageTagKey(9);"/>
+ </keyset>
+
+</overlay>
+
diff --git a/comm/suite/mailnews/content/mailOverlay.js b/comm/suite/mailnews/content/mailOverlay.js
new file mode 100644
index 0000000000..fd812eaa54
--- /dev/null
+++ b/comm/suite/mailnews/content/mailOverlay.js
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+function openNewCardDialog()
+{
+ window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
+ "", "chrome,modal,resizable=no,centerscreen");
+}
+
+function goOpenNewMessage()
+{
+ // if there is a MsgNewMessage function in scope
+ // and we should use it, so that we choose the proper
+ // identity, based on the selected message or folder
+ // if not, bring up the compose window to the default identity
+ if ("MsgNewMessage" in window)
+ {
+ MsgNewMessage(null);
+ return;
+ }
+
+ Cc["@mozilla.org/messengercompose;1"]
+ .getService(Ci.nsIMsgComposeService)
+ .OpenComposeWindow(null, null, null,
+ Ci.nsIMsgCompType.New,
+ Ci.nsIMsgCompFormat.Default,
+ null, null, null);
+}
diff --git a/comm/suite/mailnews/content/mailOverlay.xul b/comm/suite/mailnews/content/mailOverlay.xul
new file mode 100644
index 0000000000..b1379f6824
--- /dev/null
+++ b/comm/suite/mailnews/content/mailOverlay.xul
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/mailOverlay.dtd">
+<overlay id="mailOverlay.xul"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://messenger/content/mailOverlay.js"/>
+
+ <!-- generic commands -->
+ <commandset id="tasksCommands">
+ <command id="cmd_newMessage" oncommand="goOpenNewMessage();"/>
+ <command id="cmd_newCard" oncommand="openNewCardDialog()"/>
+ </commandset>
+ <menuitem id="menu_newCard" label="&newContactCmd.label;"
+ accesskey="&newContactCmd.accesskey;" command="cmd_newCard"/>
+ <menuitem id="menu_newMessage" label="&newMessageCmd.label;" accesskey="&newMessageCmd.accesskey;" key="key_newMessage" command="cmd_newMessage"/>
+ <keyset id="tasksKeys">
+#ifdef XP_MACOSX
+ <key id="key_newMessage" key="&newMessageCmd.key;"
+ modifiers="accel,shift" command="cmd_newMessage"/>
+#else
+ <key id="key_newMessage" key="&newMessageCmd.key;"
+ modifiers="accel" command="cmd_newMessage"/>
+#endif
+ </keyset>
+</overlay>
diff --git a/comm/suite/mailnews/content/mailTasksOverlay.js b/comm/suite/mailnews/content/mailTasksOverlay.js
new file mode 100644
index 0000000000..2033eb0651
--- /dev/null
+++ b/comm/suite/mailnews/content/mailTasksOverlay.js
@@ -0,0 +1,250 @@
+/* -*- 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 { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// biff observer topic
+const BIFF_TOPIC = "mail:biff-state-changed";
+
+// biff state constants used by themes
+const BIFF_STATE_MESSAGES = "NewMail";
+const BIFF_STATE_NOMESSAGES = "NoMail";
+const BIFF_STATE_UNKNOWN = "UnknownMail";
+
+
+// uses "toOpenWindowByType" function provided by tasksOverlay.js
+// which is included by most clients.
+function toMessengerWindow()
+{
+ toOpenWindowByType("mail:3pane", "chrome://messenger/content/");
+}
+
+function toAddressBook()
+{
+ toOpenWindowByType("mail:addressbook",
+ "chrome://messenger/content/addressbook/addressbook.xul");
+}
+
+function toNewsgroups()
+{
+ dump("Sorry, command not implemented.\n");
+}
+
+function toImport()
+{
+ window.openDialog("chrome://messenger/content/importDialog.xul",
+ "importDialog",
+ "chrome, modal, titlebar, centerscreen");
+}
+
+function CoalesceGetMsgsForPop3ServersByDestFolder(aCurrentServer,
+ aPOP3DownloadServersArray,
+ aLocalFoldersToDownloadTo)
+{
+ // coalesce the servers that download into the same folder...
+ var inbox = aCurrentServer.rootMsgFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
+ var index = aLocalFoldersToDownloadTo.indexOf(inbox);
+ if (index == -1)
+ {
+ inbox.biffState = Ci.nsIMsgFolder.nsMsgBiffState_NoMail;
+ inbox.clearNewMessages();
+ aLocalFoldersToDownloadTo.push(inbox);
+ index = aPOP3DownloadServersArray.length;
+ aPOP3DownloadServersArray.push([]);
+ }
+ aPOP3DownloadServersArray[index].push(aCurrentServer);
+}
+
+function MailTasksGetMessagesForAllServers(aBiff, aMsgWindow, aDefaultServer)
+{
+ // now log into any server
+ try
+ {
+ // array of array of servers for a particular folder
+ var pop3DownloadServersArray = [];
+ // parallel array of folders to download to...
+ var localFoldersToDownloadTo = [];
+ var pop3Server = null;
+ for (let currentServer of MailServices.accounts.allServers)
+ {
+ if (currentServer)
+ {
+ if (aBiff)
+ {
+ if (currentServer.protocolInfo.canLoginAtStartUp &&
+ currentServer.loginAtStartUp)
+ {
+ if (aDefaultServer &&
+ aDefaultServer.equals(currentServer) &&
+ !aDefaultServer.isDeferredTo &&
+ aDefaultServer.rootFolder == aDefaultServer.rootMsgFolder)
+ {
+ dump(currentServer.serverURI + " ... skipping, already opened\n");
+ }
+ else if (currentServer.type == "pop3" && currentServer.downloadOnBiff)
+ {
+ CoalesceGetMsgsForPop3ServersByDestFolder(currentServer,
+ pop3DownloadServersArray,
+ localFoldersToDownloadTo);
+ pop3Server = currentServer;
+ }
+ else
+ {
+ // check to see if there are new messages on the server
+ currentServer.performBiff(aMsgWindow);
+ }
+ }
+ }
+ else
+ {
+ if (currentServer.protocolInfo.canGetMessages &&
+ !currentServer.passwordPromptRequired)
+ {
+ if (currentServer.type == "pop3")
+ {
+ CoalesceGetMsgsForPop3ServersByDestFolder(currentServer,
+ pop3DownloadServersArray,
+ localFoldersToDownloadTo);
+ pop3Server = currentServer;
+ }
+ else
+ {
+ // get new messages on the server for IMAP or RSS
+ GetMessagesForInboxOnServer(currentServer);
+ }
+ }
+ }
+ }
+ }
+
+ if (pop3Server instanceof Ci.nsIPop3IncomingServer)
+ {
+ for (let i = 0; i < pop3DownloadServersArray.length; ++i)
+ {
+ // any ol' pop3Server will do -
+ // the serversArray specifies which servers to download from
+ pop3Server.downloadMailFromServers(pop3DownloadServersArray[i],
+ aMsgWindow,
+ localFoldersToDownloadTo[i],
+ null);
+ }
+ }
+ }
+ catch (e)
+ {
+ Cu.reportError(e);
+ }
+}
+
+var biffObserver =
+{
+ observe: function observe(subject, topic, state)
+ {
+ // sanity check
+ if (topic == BIFF_TOPIC)
+ {
+ var biffManager = Cc["@mozilla.org/messenger/statusBarBiffManager;1"]
+ .getService(Ci.nsIStatusBarBiffManager);
+ document.getElementById("mini-mail")
+ .setAttribute("BiffState",
+ [BIFF_STATE_MESSAGES,
+ BIFF_STATE_NOMESSAGES,
+ BIFF_STATE_UNKNOWN][biffManager.biffState]);
+ }
+ }
+};
+
+function MailTasksOnLoad(aEvent)
+{
+ // Without the mini-mail icon to show the biff state, there's no need to
+ // initialize this here. We won't start with the hidden window alone,
+ // so this early return doesn't break anything.
+ var miniMail = document.getElementById("mini-mail");
+ if (!miniMail)
+ return;
+
+ // initialize biff state
+ Services.obs.addObserver(biffObserver, BIFF_TOPIC);
+ biffObserver.observe(null, BIFF_TOPIC, null); // init mini-mail icon
+ addEventListener("unload", MailTasksOnUnload, false);
+
+ // don't try to biff if offline, but do so silently
+ if (Services.io.offline)
+ return;
+
+ // Performing biff here will mean performing it for all new windows opened!
+ // This might make non-users of mailnews unhappy...
+ if (!Services.prefs.getBoolPref("mail.biff.on_new_window"))
+ return;
+
+ // The MailNews main window will perform biff later in its onload handler,
+ // so we don't need to do this here.
+ if (Services.wm.getMostRecentWindow("mail:3pane"))
+ return;
+
+ // If we already have a defined biff-state set on the mini-mail icon,
+ // we know that biff is already running.
+ const kBiffState = Cc["@mozilla.org/messenger/statusBarBiffManager;1"]
+ .getService(Ci.nsIStatusBarBiffManager)
+ .biffState;
+ if (kBiffState != Ci.nsIMsgFolder.nsMsgBiffState_Unknown)
+ return;
+
+ // still no excuse to refuse to use this ruse
+ MailTasksGetMessagesForAllServers(true, null, null);
+}
+
+function MailTasksOnUnload(aEvent)
+{
+ Services.obs.removeObserver(biffObserver, BIFF_TOPIC);
+}
+
+/**
+ * This class implements nsIBadCertListener2. Its job is to prevent "bad cert"
+ * security dialogs from being shown to the user. Currently it puts up the
+ * cert override dialog, though we'd like to give the user more detailed
+ * information in the future.
+ */
+function nsMsgBadCertHandler() {
+}
+
+nsMsgBadCertHandler.prototype = {
+ // Suppress any certificate errors
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ if (!status)
+ return true;
+
+ setTimeout(InformUserOfCertError, 0, status, targetSite);
+ return true;
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsISupports
+ QueryInterface: function(iid) {
+ if (!iid.equals(Ci.nsIBadCertListener2) &&
+ !iid.equals(Ci.nsIInterfaceRequestor) &&
+ !iid.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+
+function InformUserOfCertError(status, targetSite)
+{
+ var params = { exceptionAdded : false,
+ sslStatus : status,
+ prefetchCert : true,
+ location : targetSite };
+ window.openDialog('chrome://pippki/content/exceptionDialog.xul',
+ '','chrome,centerscreen,modal', params);
+}
+
+addEventListener("load", MailTasksOnLoad, false);
diff --git a/comm/suite/mailnews/content/mailTasksOverlay.xul b/comm/suite/mailnews/content/mailTasksOverlay.xul
new file mode 100644
index 0000000000..9a208bb750
--- /dev/null
+++ b/comm/suite/mailnews/content/mailTasksOverlay.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/mailTasksOverlay.dtd">
+
+<overlay id="mailTasksOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://messenger/content/mailTasksOverlay.js"/>
+
+ <keyset id="tasksKeys">
+ <key id="key_mail"
+ command="Tasks:Mail"
+ key="&messengerCmd.commandkey;"
+ modifiers="accel"/>
+ <key id="key_addressbook"
+ command="Tasks:AddressBook"
+ key="&addressBookCmd.commandkey;"
+ modifiers="accel"/>
+ </keyset>
+
+ <commandset id="tasksCommands">
+ <command id="Tasks:Mail" oncommand="toMessengerWindow();"/>
+ <command id="Tasks:AddressBook" oncommand="toAddressBook();"/>
+ </commandset>
+
+ <statusbarpanel id="component-bar">
+ <toolbarbutton id="mini-mail"
+ class="taskbutton"
+ oncommand="toMessengerWindow()"
+ position="2"
+ tooltiptext="&taskMessenger.tooltip;"/>
+ <toolbarbutton id="mini-comp"
+ insertafter="mini-mail"/>
+ <toolbarbutton id="mini-addr"
+ class="taskbutton"
+ oncommand="toAddressBook();"
+ insertafter="mini-comp"
+ tooltiptext="&taskAddressBook.tooltip;"/>
+ </statusbarpanel>
+
+ <menupopup id="windowPopup">
+ <menuitem id="tasksMenuMail"
+ class="menuitem-iconic icon-mail16 menu-iconic"
+ label="&messengerCmd.label;"
+ accesskey="&messengerCmd.accesskey;"
+ key="key_mail"
+ command="Tasks:Mail"
+ insertafter="tasksMenuNavigator"/>
+ <menuitem id="tasksMenuEditor"
+ insertafter="tasksMenuMail"/>
+ <menuitem id="tasksMenuAddressBook"
+ class="menuitem-iconic icon-addressbook16 menu-iconic"
+ label="&addressBookCmd.label;"
+ accesskey="&addressBookCmd.accesskey;"
+ key="key_addressbook"
+ command="Tasks:AddressBook"
+ insertafter="tasksMenuEditor"/>
+ </menupopup>
+
+</overlay>
diff --git a/comm/suite/mailnews/content/mailViewList.js b/comm/suite/mailnews/content/mailViewList.js
new file mode 100644
index 0000000000..ef9589ba74
--- /dev/null
+++ b/comm/suite/mailnews/content/mailViewList.js
@@ -0,0 +1,161 @@
+/* -*- 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 gMailListView;
+var gListBox;
+var gEditButton;
+var gDeleteButton;
+var gMailViewListController =
+{
+ supportsCommand: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_new":
+ case "cmd_edit":
+ case "cmd_delete":
+ return true;
+ }
+ return false;
+ },
+
+ isCommandEnabled: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_new":
+ return true;
+ case "cmd_edit":
+ case "cmd_delete":
+ return gListBox.selectedIndex >= 0;
+ }
+ return false;
+ },
+
+ doCommand: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_new":
+ OnNewMailView();
+ break;
+ case "cmd_edit":
+ OnEditMailView();
+ break;
+ case "cmd_delete":
+ OnDeleteMailView();
+ break;
+ }
+ },
+
+ onEvent: function(aEvent) {},
+
+ onCommandUpdate: function()
+ {
+ for (let command of ["cmd_new", "cmd_edit", "cmd_delete"])
+ goUpdateCommand(command);
+ }
+};
+
+function MailViewListOnLoad()
+{
+ gMailListView = Cc["@mozilla.org/messenger/mailviewlist;1"]
+ .getService(Ci.nsIMsgMailViewList);
+ gListBox = document.getElementById('mailViewList');
+
+ window.controllers.insertControllerAt(0, gMailViewListController);
+
+ // Construct list view based on current mail view list data
+ RefreshListView(null);
+ gEditButton = document.getElementById('editButton');
+ gDeleteButton = document.getElementById('deleteButton');
+}
+
+function MailViewListOnUnload()
+{
+ window.controllers.removeController(gMailViewListController);
+}
+
+function RefreshListView(aSelectedMailView)
+{
+ // remove any existing items in the view...
+ for (let index = gListBox.getRowCount(); index > 0; index--)
+ gListBox.getItemAtIndex(index - 1).remove();
+
+ var numItems = gMailListView.mailViewCount;
+ for (let index = 0; index < numItems; index++)
+ {
+ let mailView = gMailListView.getMailViewAt(index);
+ gListBox.appendItem(mailView.prettyName, index);
+ if (aSelectedMailView && (mailView.prettyName == aSelectedMailView.prettyName))
+ gListBox.selectedIndex = index;
+ }
+}
+
+function OnNewMailView()
+{
+ window.openDialog('chrome://messenger/content/mailViewSetup.xul',
+ '',
+ 'centerscreen,resizable,modal,titlebar,chrome',
+ {onOkCallback: RefreshListView});
+}
+
+function OnDeleteMailView()
+{
+ let bundle = Services.strings.createBundle("chrome://messenger/locale/messenger.properties");
+
+ let ps = Services.prompt;
+ if (!ps.confirm(window, bundle.GetStringFromName("confirmViewDeleteTitle"),
+ bundle.GetStringFromName("confirmViewDeleteMessage")))
+ return;
+
+ // get the selected index
+ var selectedIndex = gListBox.selectedIndex;
+ if (selectedIndex >= 0)
+ {
+ var mailView = gMailListView.getMailViewAt(selectedIndex);
+ if (mailView)
+ {
+ gMailListView.removeMailView(mailView);
+ // now remove it from the view...
+ gListBox.selectedItem.remove();
+
+ // select the next item in the list..
+ if (selectedIndex < gListBox.getRowCount())
+ gListBox.selectedIndex = selectedIndex;
+ else
+ gListBox.selectedIndex = gListBox.getRowCount() - 1;
+
+ gMailListView.save();
+ }
+ }
+}
+
+function OnEditMailView()
+{
+ // get the selected index
+ var selectedIndex = gListBox.selectedIndex;
+ if (selectedIndex >= 0)
+ {
+ let selMailView = gMailListView.getMailViewAt(selectedIndex);
+ // open up the mail view setup dialog passing in the mail view as an argument
+ let args = {mailView: selMailView, onOkCallback: RefreshListView};
+ window.openDialog('chrome://messenger/content/mailViewSetup.xul',
+ '',
+ 'centerscreen,modal,resizable,titlebar,chrome',
+ args);
+ }
+}
+
+function OnMailViewSelect(aEvent)
+{
+ gMailViewListController.onCommandUpdate();
+}
+
+function OnMailViewDoubleClick(aEvent)
+{
+ if (aEvent.button == 0 && aEvent.target.selected)
+ OnEditMailView();
+}
diff --git a/comm/suite/mailnews/content/mailViewList.xul b/comm/suite/mailnews/content/mailViewList.xul
new file mode 100644
index 0000000000..c055bfd02a
--- /dev/null
+++ b/comm/suite/mailnews/content/mailViewList.xul
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
+<!-- Mac needs dialog.css to correctly style the moved Help button -->
+<?xml-stylesheet href="chrome://global/skin/dialog.css" type="text/css"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % mailViewLisDTD SYSTEM "chrome://messenger/locale/mailViewList.dtd">
+%mailViewLisDTD;
+<!ENTITY % FilterListDialogDTD SYSTEM "chrome://messenger/locale/FilterListDialog.dtd">
+%FilterListDialogDTD;
+]>
+
+<dialog id="mailViewListDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="MailViewListOnLoad();"
+ onunload="MailViewListOnUnload();"
+ ondialogaccept="return false;"
+ windowtype="mailnews:mailviewlist"
+ title="&mailViewListTitle.label;"
+ width="400" height="340"
+ buttons=","
+ persist="screenX screenY width height">
+
+ <script src="chrome://messenger/content/mailViewList.js"/>
+ <script src="chrome://global/content/globalOverlay.js"/>
+
+ <commandset id="mailViewCommands">
+ <command id="cmd_new" oncommand="goDoCommand('cmd_new');"/>
+ <command id="cmd_edit" oncommand="goDoCommand('cmd_edit');" disabled="true"/>
+ <command id="cmd_delete" oncommand="goDoCommand('cmd_delete');" disabled="true"/>
+ </commandset>
+
+ <keyset id="mailViewListKeys">
+ <key id="key_delete"/>
+ <key id="key_delete2"/>
+ <key id="key_open" keycode="VK_RETURN" command="cmd_edit"/>
+ </keyset>
+
+ <vbox flex="1">
+ <hbox flex="1">
+ <listbox id="mailViewList"
+ flex="1"
+ onselect="OnMailViewSelect(event);"
+ ondblclick="OnMailViewDoubleClick(event);">
+ <listcols>
+ <listcol flex="1" width="0"/>
+ </listcols>
+ <listhead>
+ <listheader label="&viewName.label;"/>
+ </listhead>
+ </listbox>
+
+ <vbox id="buttonCol">
+ <button id="newButton"
+ label="&newButton.label;"
+ accesskey="&newButton.accesskey;"
+ command="cmd_new"/>
+ <button id="editButton"
+ label="&editButton.label;"
+ accesskey="&editButton.accesskey;"
+ command="cmd_edit"/>
+ <button id="deleteButton"
+ label="&deleteButton.label;"
+ accesskey="&deleteButton.accesskey;"
+ command="cmd_delete"/>
+ <spacer flex="1"/>
+ <button id="helpButton"
+ dlgtype="help"
+ class="dialog-button"/>
+ </vbox>
+ </hbox>
+ </vbox>
+</dialog>
diff --git a/comm/suite/mailnews/content/mailViewSetup.js b/comm/suite/mailnews/content/mailViewSetup.js
new file mode 100644
index 0000000000..4c9b47f070
--- /dev/null
+++ b/comm/suite/mailnews/content/mailViewSetup.js
@@ -0,0 +1,119 @@
+/* -*- 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 nsMsgSearchScope = Ci.nsMsgSearchScope;
+var gMailView = null;
+
+var dialog;
+
+function mailViewOnLoad()
+{
+ initializeSearchWidgets();
+ initializeMailViewOverrides();
+ dialog = {};
+
+ if ("arguments" in window && window.arguments[0])
+ {
+ var args = window.arguments[0];
+ if ("mailView" in args)
+ gMailView = window.arguments[0].mailView;
+ if ("onOkCallback" in args)
+ dialog.okCallback = window.arguments[0].onOkCallback;
+ }
+
+ dialog.OKButton = document.documentElement.getButton("accept");
+ dialog.nameField = document.getElementById("name");
+ dialog.nameField.focus();
+
+ setSearchScope(nsMsgSearchScope.offlineMail);
+
+ if (gMailView)
+ {
+ dialog.nameField.value = gMailView.prettyName;
+ initializeSearchRows(nsMsgSearchScope.offlineMail, gMailView.searchTerms);
+ }
+ else
+ onMore(null);
+
+ doEnabling();
+}
+
+function mailViewOnUnLoad()
+{
+
+}
+
+function onOK()
+{
+ var mailViewList = Cc["@mozilla.org/messenger/mailviewlist;1"].getService(Ci.nsIMsgMailViewList);
+
+ // reflect the search widgets back into the search session
+ var newMailView = null;
+ if (gMailView)
+ {
+ gMailView.searchTerms = saveSearchTerms(gMailView.searchTerms, gMailView);
+ // if the name of the view has been changed...
+ if (gMailView.prettyName != dialog.nameField.value)
+ gMailView.mailViewName = dialog.nameField.value;
+ }
+ else
+ {
+ // otherwise, create a new mail view
+ newMailView = mailViewList.createMailView();
+
+ newMailView.searchTerms = saveSearchTerms(newMailView.searchTerms, newMailView);
+ newMailView.mailViewName = dialog.nameField.value;
+ // now add the mail view to our mail view list
+ mailViewList.addMailView(newMailView);
+ }
+
+ mailViewList.save();
+
+ if (dialog.okCallback)
+ dialog.okCallback(gMailView ? gMailView : newMailView);
+
+ return true;
+}
+
+function initializeMailViewOverrides()
+{
+ // replace some text with something we want. Need to add some ids to searchOverlay.js
+ //var orButton = document.getElementById('or');
+ //orButton.setAttribute('label', 'Any of the following');
+ //var andButton = document.getElementById('and');
+ //andButton.setAttribute('label', 'All of the following');
+ // matchAll doesn't make sense for views, since views are a single folder
+ hideMatchAllItem();
+
+}
+
+function UpdateAfterCustomHeaderChange()
+{
+ updateSearchAttributes();
+}
+
+function doEnabling()
+{
+ if (dialog.nameField.value)
+ {
+ if (dialog.OKButton.disabled)
+ dialog.OKButton.disabled = false;
+ } else
+ {
+ if (!dialog.OKButton.disabled)
+ dialog.OKButton.disabled = true;
+ }
+}
+
+function onEnterInSearchTerm()
+{
+ // no-op for us...
+}
+
+function doHelpButton()
+{
+ openHelp("message-views-create-new");
+}
+
diff --git a/comm/suite/mailnews/content/mailViewSetup.xul b/comm/suite/mailnews/content/mailViewSetup.xul
new file mode 100644
index 0000000000..203a8b8991
--- /dev/null
+++ b/comm/suite/mailnews/content/mailViewSetup.xul
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/searchDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/mailViewSetup.dtd" >
+
+<dialog id="mailViewSetupDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="mailViewOnLoad();"
+ onunload="mailViewOnUnLoad();"
+ ondialogaccept="return onOK();"
+ buttons="accept,cancel"
+ buttonalign="right"
+ windowtype="mailnews:mailview"
+ title="&mailViewSetupTitle.label;"
+ style="width: 52em; height: 22em;"
+ persist="screenX screenY width height">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://messenger/content/mailViewSetup.js"/>
+
+ <dummy class="usesMailWidgets"/>
+
+ <vbox flex="1">
+ <separator class="thin"/>
+ <vbox>
+ <hbox align="center">
+ <label value="&mailViewHeading.label;" accesskey="&mailViewHeading.accesskey;" control="name"/>
+ <textbox tabindex="0" id="name" oninput="doEnabling();"/>
+ </hbox>
+ </vbox>
+
+ <separator/>
+ <label value="&searchTermCaption.label;"/>
+ <hbox flex="1">
+ <vbox id="searchTermListBox" flex="1"/>
+ </hbox>
+ </vbox>
+
+</dialog>
diff --git a/comm/suite/mailnews/content/mailWidgets.xml b/comm/suite/mailnews/content/mailWidgets.xml
new file mode 100644
index 0000000000..29288b3b70
--- /dev/null
+++ b/comm/suite/mailnews/content/mailWidgets.xml
@@ -0,0 +1,1946 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<bindings id="mailBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <!-- dummy widget to force this file to load -->
+ <binding id="dummy" extends="xul:box"/>
+
+ <!-- temporary holding place for horizontal list -->
+
+ <binding id="extdescription" extends="chrome://global/content/bindings/listbox.xml#listbox-base">
+ <implementation>
+ <constructor><![CDATA[
+ this.children.filter(aChild => aChild.getAttribute("selected") == "true")
+ .forEach(this.selectedItems.append, this.selectedItems);
+ ]]></constructor>
+
+ <!-- ///////////////// public members ///////////////// -->
+
+ <property name="itemCount" readonly="true"
+ onget="return this.children.length;"/>
+
+ <method name="getIndexOfItem">
+ <parameter name="item"/>
+ <body><![CDATA[
+ return this.children.indexOf(item);
+ ]]></body>
+ </method>
+ <method name="getItemAtIndex">
+ <parameter name="index"/>
+ <body><![CDATA[
+ return this.children[index] || null;
+ ]]></body>
+ </method>
+ <method name="getRowCount">
+ <body><![CDATA[
+ return this.children.length;
+ ]]></body>
+ </method>
+ <method name="getNumberOfVisibleRows">
+ <body><![CDATA[
+ var firstItem = this.children[0] || null;
+ if (!firstItem)
+ return 0; // nothing to be visible
+ var itemsPerRow = Math.floor(this.boxObject.width / firstItem.boxObject.width);
+ var itemsPerCol = Math.floor(this.boxObject.height / firstItem.boxObject.height);
+ return Math.max(itemsPerRow, 1) * Math.max(itemsPerCol, 1);
+ ]]></body>
+ </method>
+ <method name="getIndexOfFirstVisibleRow">
+ <body><![CDATA[
+ //XXXzeniko unimplementable without a way to scroll
+ ]]></body>
+ </method>
+
+ <method name="ensureIndexIsVisible">
+ <parameter name="index"/>
+ <body><![CDATA[
+ this.ensureElementIsVisible(this.getItemAtIndex(index));
+ ]]></body>
+ </method>
+ <method name="ensureElementIsVisible">
+ <parameter name="item"/>
+ <body><![CDATA[
+ //XXXzeniko unimplementable without a way to scroll
+ ]]></body>
+ </method>
+ <method name="scrollToIndex">
+ <parameter name="index"/>
+ <body><![CDATA[
+ //XXXzeniko unimplementable without a way to scroll
+ ]]></body>
+ </method>
+
+ <method name="appendItem">
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <body><![CDATA[
+ // -1 appends due to the way getItemAtIndex is implemented
+ return this.insertItemAt(-1, label, value);
+ ]]></body>
+ </method>
+ <method name="insertItemAt">
+ <parameter name="index"/>
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <body><![CDATA[
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var item = document.createElementNS(XULNS, "descriptionitem");
+ item.setAttribute("label", label);
+ this.insertBefore(item, this.getItemAtIndex(index));
+ return item;
+ ]]></body>
+ </method>
+
+ <method name="scrollOnePage">
+ <parameter name="direction"/>
+ <body><![CDATA[
+ return direction * this.getNumberOfVisibleRows();
+ ]]></body>
+ </method>
+
+ <!-- ///////////////// private members ///////////////// -->
+
+ <property name="children" readonly="true"
+ onget="return Array.from(this.getElementsByTagName('descriptionitem'));"/>
+
+ <method name="_fireOnSelect">
+ <body><![CDATA[
+ if (!this._suppressOnSelect && !this.suppressOnSelect) {
+ this.dispatchEvent(new Event("select",
+ { bubbles: false, cancelable: true }));
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control shift any"
+ action="this.moveByOffset(-1, !event.ctrlKey, event.shiftKey);"
+ phase="target" preventdefault="true"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control shift any"
+ action="this.moveByOffset(1, !event.ctrlKey, event.shiftKey);"
+ phase="target" preventdefault="true"/>
+ <handler event="click" button="0" phase="target"><![CDATA[
+ if (this.selType != "multiple" || (!event.ctrlKey && !event.shiftKey && !event.metaKey))
+ this.clearSelection();
+ ]]></handler>
+ <!-- make sure we keep the focus... -->
+ <handler event="mousedown" button="0"
+ action="if (document.commandDispatcher.focusedElement != this) this.focus();"/>
+ </handlers>
+ </binding>
+
+ <binding id="descriptionitem" extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <xul:hbox class="attachmentBox" xbl:inherits="orient" align="start">
+ <xul:label class="descriptioncell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled,context" flex="1" dir="ltr" crop="center"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="descriptionitem-iconic" extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <xul:hbox class="attachmentBox" xbl:inherits="orient" align="center">
+ <xul:image class="descriptioncell-icon" xbl:inherits="src=image"/>
+ <xul:label class="descriptioncell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled,context" flex="1" dir="ltr" crop="center"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <!-- Message Pane Widgets -->
+
+ <!-- mail-toggle-headerfield: Non-email addrs headers which have a toggle
+ associated with them (i.e. the subject).
+ Use label to set the header name.
+ Use headerValue to set the header value. -->
+ <binding id="mail-toggle-headerfield">
+ <content>
+ <xul:hbox class="headerNameBox" align="start">
+ <xul:image class="expandHeaderViewButton" xbl:inherits="onclick=ontwistyclick"/>
+ <xul:spacer flex="1"/>
+ <xul:label class="headerName" xbl:inherits="value=label" control="headerValue"/>
+ </xul:hbox>
+ <xul:hbox class="headerValueBox" flex="1" align="start">
+ <xul:textbox class="headerValue plain" anonid="headerValue" flex="1" readonly="true"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <property name="headerValue" onset="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue').value = val;"/>
+ </implementation>
+ </binding>
+
+ <!-- mail-headerfield: presents standard text header name & value pairs. Don't use this for email addresses.
+ use label to set the header name.
+ use headerValue to set the header value. -->
+ <binding id="mail-headerfield">
+ <content>
+ <xul:hbox class="headerNameBox" align="start">
+ <xul:label class="headerName" xbl:inherits="value=label" control="headerValue" flex="1"/>
+ </xul:hbox>
+ <xul:hbox class="headerValueBox" flex="1" align="start">
+ <xul:textbox class="headerValue plain" anonid="headerValue" flex="1" readonly="true"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <property name="headerValue" onset="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue').value = val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="mail-urlfield" extends="chrome://messenger/content/mailWidgets.xml#mail-headerfield">
+ <content>
+ <xul:hbox class="headerNameBox" align="start">
+ <xul:label class="headerName" xbl:inherits="value=label" flex="1"/>
+ </xul:hbox>
+ <xul:hbox class="headerValueBox" flex="1" align="start">
+ <xul:label onclick="if (event.button != 2) openAsExternal(event.target.value);"
+ ondragstart="this.parentNode.setDataTransfer(event);"
+ class="headerValue plain text-link headerValueUrl"
+ anonid="headerValue" flex="1" readonly="true" context="copyUrlPopup"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="setDataTransfer">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var dt = aEvent.dataTransfer;
+ var val = aEvent.target.value;
+ dt.setData('text/x-moz-url', val + "\n" + val);
+ dt.setData('text/uri-list', val);
+ dt.setData('text/plain', val);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="mail-emailheaderfield">
+ <content>
+ <xul:hbox class="headerNameBox" align="start">
+ <xul:label class="headerName" xbl:inherits="value=label" flex="1"/>
+ </xul:hbox>
+ <xul:hbox class="headerValueBox" flex="1" align="start">
+ <xul:mail-emailaddress class="headerValue" anonid="emailAddressNode"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <property name="emailAddressNode" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'emailAddressNode');"
+ readonly="true"/>
+ </implementation>
+ </binding>
+
+ <!-- multi-emailHeaderField: presents multiple emailheaderfields with a toggle -->
+ <binding id="mail-multi-emailHeaderField">
+ <content>
+ <xul:hbox class="headerNameBox" align="start" pack="end">
+ <xul:image class="addresstwisty" anonid="toggleIcon"
+ collapsed="true" onclick="toggleWrap();"/>
+ <xul:label class="headerName" xbl:inherits="value=label"/>
+ </xul:hbox>
+
+ <xul:hbox class="headerValueBox" anonid="longEmailAddresses" flex="1" align="start"
+ onoverflow="if (event.detail != 1) this.parentNode.toggleIcon.collapsed = false;"
+ onunderflow="if (event.detail != 1) this.parentNode.toggleIcon.collapsed = true;">
+ <xul:description class="headerValue" anonid="emailAddresses" flex="1"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.mAddresses = new Array;
+ ]]>
+ </constructor>
+
+ <field name="mAddresses"/>
+ <!-- as a perf optimization we are going to keep a cache of email address nodes which we've
+ created around for the lifetime of the widget. mSizeOfAddressCache controls how many of these
+ elements we keep around -->
+ <field name="mSizeOfAddressCache">3</field>
+
+ <!-- addAddressView: a public method used to add an address to this widget.
+ aAddresses is an object with 3 properties: displayName, emailAddress and fullAddress
+ -->
+ <method name="addAddressView">
+ <parameter name="aAddress"/>
+ <body>
+ <![CDATA[
+ this.mAddresses.push(aAddress);
+ ]]>
+ </body>
+ </method>
+
+ <!-- updateEmailAddressNode: private method used to set properties on an address node -->
+ <method name="updateEmailAddressNode">
+ <parameter name="aEmailNode"/>
+ <parameter name="aAddress"/>
+ <body>
+ <![CDATA[
+ if (aEmailNode.parentNode.useShortView && aAddress.displayName)
+ {
+ aEmailNode.setAttribute("label", aAddress.displayName);
+ aEmailNode.setAttribute("tooltiptext", aAddress.fullAddress);
+ }
+ else
+ {
+ aEmailNode.setAttribute("label", aAddress.fullAddress || aAddress.displayName);
+ aEmailNode.removeAttribute("tooltiptext");
+ }
+ aEmailNode.setAttribute("emailAddress", aAddress.emailAddress);
+ aEmailNode.setAttribute("fullAddress", aAddress.fullAddress);
+ aEmailNode.setAttribute("displayName", aAddress.displayName);
+
+ // Add aria-label with header field type and header field content
+ // for better accessibility.
+ // Note: No extra colon and space needed, since it is
+ // already provided by this object's label attribute.
+ var ariaLabel = this.getAttribute("label") +
+ aEmailNode.getAttribute("label");
+ aEmailNode.setAttribute("aria-label", ariaLabel);
+
+ try
+ {
+ if ("UpdateEmailNodeDetails" in top)
+ UpdateEmailNodeDetails(aAddress.emailAddress, aEmailNode);
+ }
+ catch(ex)
+ {
+ dump("UpdateEmailNodeDetails failed: " + ex + "\n");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- fillCachedAddresses: private method used to fill up any cached pre-existing
+ emailAddress fields without creating new email address fields. Returns a remainder
+ for the # of addresses which require new addresses being created.
+ Invariants: 1) aNumAddressesToShow >= 0 && it is <= mAddresses.length -->
+ <method name="fillCachedAddresses">
+ <parameter name="aAddressesNode"/>
+ <parameter name="aNumAddressesToShow"/>
+ <body>
+ <![CDATA[
+ var numExistingCachedAddresses = aAddressesNode.childNodes.length;
+ if (!numExistingCachedAddresses)
+ return this.mAddresses.length; // we couldn't pre fill anything
+ else if (numExistingCachedAddresses > 1)
+ numExistingCachedAddresses = (numExistingCachedAddresses + 1)/ 2;
+
+ var index = 0;
+ var numAddressesAdded = 0;
+ var emailAddressNode;
+ var commaNode;
+ while (numAddressesAdded < numExistingCachedAddresses && numAddressesAdded < aNumAddressesToShow)
+ {
+ if (index && numExistingCachedAddresses > 1)
+ {
+ commaNode = aAddressesNode.childNodes[index++];
+ if (commaNode)
+ commaNode.hidden = false;
+ }
+
+ // get the node pointed to by index
+ emailAddressNode = aAddressesNode.childNodes[index++];
+ this.updateEmailAddressNode(emailAddressNode, this.mAddresses[numAddressesAdded]);
+ emailAddressNode.hidden = false;
+ numAddressesAdded++;
+ }
+
+ // if we have added all of our elements but we still have more cached items in this address node
+ // then make sure the extra cached copies are hidden...
+ numExistingCachedAddresses = aAddressesNode.childNodes.length; // reset
+ while (index < numExistingCachedAddresses)
+ {
+ aAddressesNode.childNodes[index++].hidden = true;
+ }
+
+ return this.mAddresses.length - numAddressesAdded;
+ ]]>
+ </body>
+ </method>
+
+ <!-- fillAddressesNode: private method used to create email address nodes for either our short
+ or long view. aAddressesNode: the div we want to add addresses too.
+ aNumAddressesToShow: number of addresses to put into the list -->
+ <method name="fillAddressesNode">
+ <parameter name="aAddressesNode"/>
+ <parameter name="aNumAddressesToShow"/>
+ <body>
+ <![CDATA[
+ var numAddresses = this.mAddresses.length;
+ if (aNumAddressesToShow <= 0 || aNumAddressesToShow > numAddresses) // then show all
+ aNumAddressesToShow = numAddresses;
+
+ // before we try to create email address nodes, try to leverage any cached nodes...
+ var remainder = this.fillCachedAddresses(aAddressesNode, aNumAddressesToShow);
+ var index = numAddresses - remainder;
+ while (index < numAddresses && index < aNumAddressesToShow)
+ {
+ var newAddressNode = document.createElement("mail-emailaddress");
+
+ // Stash the headerName somewhere that UpdateEmailNodeDetails
+ // will be able to find it.
+ newAddressNode.setAttribute("headerName", this.headerName);
+
+ if (index)
+ {
+ var textNode = document.createElement("text");
+ textNode.setAttribute("value", ", ");
+ textNode.setAttribute("class", "emailSeparator");
+ aAddressesNode.appendChild(textNode);
+ }
+
+ var itemInDocument = aAddressesNode.appendChild(newAddressNode);
+ this.updateEmailAddressNode(itemInDocument, this.mAddresses[index]);
+ index++;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="emailAddresses" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'emailAddresses');"
+ readonly="true"/>
+ <property name="longEmailAddresses" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'longEmailAddresses');"
+ readonly="true"/>
+ <property name="toggleIcon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'toggleIcon');"
+ readonly="true"/>
+
+ <!-- buildView: public method used by callers when they are done adding all the email addresses to the widget
+ aNumAddressesToShow: total # of addresses to show in the short view -->
+ <method name="buildViews">
+ <body>
+ <![CDATA[
+ this.fillAddressesNode(this.emailAddresses, -1);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Updates the nodes of this field with a call to
+ UpdateExtraAddressProcessing. The parameters are optional fields
+ that can contain extra information to be passed to
+ UpdateExtraAddressProcessing, the implementation of that function
+ should be checked to determine what it requires -->
+ <method name="updateExtraAddressProcessing">
+ <parameter name="aParam1"/>
+ <parameter name="aParam2"/>
+ <parameter name="aParam3"/>
+ <body>
+ <![CDATA[
+ if (UpdateExtraAddressProcessing) {
+ var childNodes = this.emailAddresses.childNodes;
+ for (let i = 0; i < this.mAddresses.length; i++) {
+ UpdateExtraAddressProcessing(this.mAddresses[i],
+ childNodes[i * 2],
+ aParam1, aParam2, aParam3);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="toggleWrap">
+ <body>
+ <![CDATA[
+ if (this.toggleIcon.hasAttribute("open")) {
+ this.toggleIcon.removeAttribute("open");
+ this.longEmailAddresses.setAttribute("singleline", "true");
+ } else {
+ this.toggleIcon.setAttribute("open", "true");
+ this.longEmailAddresses.removeAttribute("singleline");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- internal method used to clear both our divs -->
+ <method name="clearChildNodes">
+ <parameter name="aParentNode"/>
+ <body>
+ <![CDATA[
+ // we want to keep around the first mSizeOfAddressCache email address nodes
+ // don't forget that we have comma text nodes in there too so really we want to keep
+ // around cache size * 2 - 1.
+ var numItemsToPreserve = this.mSizeOfAddressCache * 2 - 1;
+ var numItemsInNode = aParentNode.childNodes.length;
+
+ while (numItemsInNode && (numItemsInNode > numItemsToPreserve))
+ {
+ aParentNode.childNodes[numItemsInNode - 1].remove();
+ numItemsInNode = numItemsInNode - 1;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="clearHeaderValues">
+ <body>
+ <![CDATA[
+ // clear out our local state
+ this.mAddresses = new Array;
+ if (this.toggleIcon.hasAttribute("open"))
+ // no automatic overflow tracking in this case
+ this.toggleIcon.collapsed = true;
+ this.toggleIcon.removeAttribute("open");
+ this.longEmailAddresses.setAttribute("singleline", "true");
+ // remove anything inside of each of our labels....
+ this.clearChildNodes(this.emailAddresses);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="mail-emailaddress">
+ <content popup="emailAddressPopup" context="emailAddressPopup">
+ <xul:description anonid="emailValue" class="emailDisplayButton plain"
+ xbl:inherits="xbl:text=label,crop,aria-label" flex="1"/>
+ <xul:image class="emailDisplayImage" anonid="emailImage"
+ xbl:inherits="src=image"/>
+ </content>
+
+ <implementation>
+ <property name="label" onset="this.getPart('emailValue').setAttribute('label',val); return val;"
+ onget="return this.getPart('emailValue').getAttribute('label');"/>
+ <property name="crop" onset="this.getPart('emailValue').setAttribute('crop',val); return val;"
+ onget="return this.getPart('emailValue').getAttribute('crop');"/>
+ <property name="disabled" onset="this.getPart('emailValue').setAttribute('disabled',val); return val;"
+ onget="return this.getPart('emailValue').getAttribute('disabled');"/>
+ <property name="src" onset="this.getPart('emailImage').setAttribute('src',val); return val;"
+ onget="return this.getPart('emailImage').getAttribute('src');"/>
+ <property name="imgalign" onset="this.getPart('emailImage').setAttribute('imgalign',val); return val;"
+ onget="return this.getPart('emailImage').getAttribute('imgalign');"/>
+
+ <method name="getPart">
+ <parameter name="aPartId"/>
+ <body><![CDATA[
+ return document.getAnonymousElementByAttribute(this, "anonid", aPartId);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="mail-messageids-headerfield">
+ <content>
+ <xul:hbox class="headerNameBox" align="start" pack="end">
+ <xul:image class="addresstwisty" anonid="toggleIcon"
+ onclick="toggleWrap();"/>
+ <xul:label class="headerName" xbl:inherits="value=label"/>
+ </xul:hbox>
+ <xul:hbox class="headerValueBox" flex="1" align="start">
+ <xul:label class="headerValue" anonid="headerValue" flex="1"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.mMessageIds = [];
+ this.showFullMessageIds = false;
+ ]]>
+ </constructor>
+
+ <property name="headerValue" readonly="true"
+ onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue');"/>
+ <property name="toggleIcon" readonly="true"
+ onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'toggleIcon');"/>
+
+ <field name="mMessageIds"/>
+
+ <!-- addMessageIdView: a public method used to add a message-id to this widget. -->
+ <method name="addMessageIdView">
+ <parameter name="aMessageId"/>
+ <body>
+ <![CDATA[
+ this.mMessageIds.push(aMessageId);
+ ]]>
+ </body>
+ </method>
+
+ <!-- updateMessageIdNode: private method used to set properties on an MessageId node -->
+ <method name="updateMessageIdNode">
+ <parameter name="aMessageIdNode"/>
+ <parameter name="aIndex"/>
+ <parameter name="aMessageId"/>
+ <parameter name="aLastId"/>
+ <body>
+ <![CDATA[
+ var showFullMessageIds = this.showFullMessageIds;
+
+ if (showFullMessageIds || aIndex == aLastId)
+ {
+ aMessageIdNode.setAttribute("label", aMessageId);
+ aMessageIdNode.removeAttribute("tooltiptext");
+ }
+ else
+ {
+ aMessageIdNode.setAttribute("label", aIndex);
+ aMessageIdNode.setAttribute("tooltiptext", aMessageId);
+ }
+
+ aMessageIdNode.setAttribute("index", aIndex);
+ aMessageIdNode.setAttribute("messageid", aMessageId);
+ ]]>
+ </body>
+ </method>
+
+ <method name="fillMessageIdNodes">
+ <body>
+ <![CDATA[
+ var headerValue = this.headerValue;
+ var messageIdNodes = headerValue.childNodes;
+ var numMessageIds = this.mMessageIds.length;
+ var index = 0;
+
+ while (messageIdNodes.length > numMessageIds * 2 - 1)
+ headerValue.lastChild.remove();
+
+ this.toggleIcon.hidden = numMessageIds <= 1;
+
+ for (var index = 0; index < numMessageIds; index++)
+ {
+ if (index * 2 <= messageIdNodes.length - 1)
+ {
+ this.updateMessageIdNode(messageIdNodes[index * 2], index + 1,
+ this.mMessageIds[index], numMessageIds);
+ }
+ else
+ {
+ var newMessageIdNode = document.createElement("mail-messageid");
+
+ if (index)
+ {
+ var textNode = document.createElement("text");
+ textNode.setAttribute("value", ", ");
+ textNode.setAttribute("class", "messageIdSeparator");
+ headerValue.appendChild(textNode);
+ }
+ var itemInDocument = headerValue.appendChild(newMessageIdNode);
+ this.updateMessageIdNode(itemInDocument, index + 1,
+ this.mMessageIds[index], numMessageIds);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="toggleWrap">
+ <body>
+ <![CDATA[
+ var headerValue = this.headerValue;
+ var messageIdNodes = headerValue.childNodes;
+ var showFullMessageIds = !this.showFullMessageIds;
+ var messageIds = this.mMessageIds
+
+ for (var i = 0; i < messageIdNodes.length; i += 2)
+ {
+ if (showFullMessageIds)
+ {
+ this.toggleIcon.setAttribute("open", "true");
+ messageIdNodes[i].setAttribute("label", messageIds[i / 2]);
+ messageIdNodes[i].removeAttribute("tooltiptext");
+ headerValue.removeAttribute("singleline");
+ } else
+ {
+ this.toggleIcon.removeAttribute("open");
+ messageIdNodes[i].setAttribute("label", i / 2 + 1);
+ messageIdNodes[i].setAttribute("tooltiptext", messageIds[i / 2]);
+ }
+ }
+
+ this.showFullMessageIds = showFullMessageIds;
+ ]]>
+ </body>
+ </method>
+
+ <method name="clearHeaderValues">
+ <body>
+ <![CDATA[
+ // clear out our local state
+ this.mMessageIds = new Array;
+ if (this.showFullMessageIds)
+ {
+ this.showFullMessageIds = false;
+ this.toggleIcon.removeAttribute("open");
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="mail-messageid">
+ <content context="messageIdContext" onclick="MessageIdClick(this, event);">
+ <xul:label anonid="messageIdValue" class="messageIdDisplayButton plain"
+ xbl:inherits="value=label"/>
+ <xul:image class="messageIdDisplayImage" anonid="messageIdImage"/>
+ </content>
+
+ <implementation>
+ <property name="label" onset="this.getPart().setAttribute('label',val); return val;"
+ onget="return this.getPart('messageIdValue').getAttribute('label');"/>
+
+ <method name="getPart">
+ <parameter name="aPartId"/>
+ <body><![CDATA[
+ return document.getAnonymousElementByAttribute(this, "anonid", 'messageIdValue');
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <!-- Header field for showing the tags associated with a message -->
+ <binding id="mail-headerfield-tags">
+ <content>
+ <xul:hbox class="headerNameBox" align="start">
+ <xul:label class="headerName" xbl:inherits="value=label" flex="1"/>
+ </xul:hbox>
+ <xul:hbox class="headerValueBox" flex="1" align="start">
+ <xul:label class="headerValue plain" anonid="headerValue" flex="1"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <property name="headerValue" onset="return this.buildTags(val);"/>
+ <method name="buildTags">
+ <parameter name="aTags"/>
+ <body>
+ <![CDATA[
+ // aTags contains a list of actual tag names (not the keys), delimited by spaces
+ // each tag name is encoded.
+
+ // remove any existing tag items we've appended to the list
+ var headerValueNode = document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue');
+ for (var i = headerValueNode.childNodes.length - 1; i >= 0; --i)
+ headerValueNode.childNodes[i].remove();
+
+ // tokenize the keywords based on ' '
+ var tagsArray = aTags.split(' ');
+ for (var index = 0; index < tagsArray.length; index++)
+ {
+ // for each tag, create a label, give it the font color that corresponds to the
+ // color of the tag and append it.
+ var tagName;
+ try {
+ // if we got a bad tag name, getTagForKey will throw an exception, skip it
+ // and go to the next one.
+ tagName = MailServices.tags.getTagForKey(tagsArray[index]);
+ } catch (ex) { continue; }
+
+ var color = MailServices.tags.getColorForKey(tagsArray[index]);
+
+ // now create a label for the tag name, and set the color
+ var label = document.createElement("label");
+ label.setAttribute('value', tagName);
+ label.style.color = color;
+ label.className = "tagvalue blc-" + color.substr(1);
+ headerValueNode.appendChild(label);
+ }
+ ]]>
+ </body>
+ </method>
+ <constructor>
+ <![CDATA[
+ var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="search-menulist-abstract" name="searchMenulistAbstract" extends="xul:box">
+ <content>
+ <xul:menulist class="search-menulist" xbl:inherits="flex,disabled" oncommand="this.parentNode.onSelect(event)">
+ <xul:menupopup class="search-menulist-popup"/>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <field name="internalScope">null</field>
+ <field name="internalValue">-1</field>
+ <field readonly="true" name="validityManager">
+ <![CDATA[
+ Cc['@mozilla.org/mail/search/validityManager;1'].getService(Ci.nsIMsgSearchValidityManager);
+ ]]>
+ </field>
+ <property name="searchScope" onget="return this.internalScope;">
+ <!-- scope ID - retrieve the table -->
+ <setter>
+ <![CDATA[
+ // if scope isn't changing this is a noop
+ if (this.internalScope == val) return val;
+
+ this.internalScope = val;
+ this.refreshList();
+ var targets = this.targets;
+ if (targets) {
+ for (var i=0; i< targets.length; i++) {
+ targets[i].searchScope = val;
+ }
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="validityTable" readonly="true" onget="return this.validityManager.getTable(this.searchScope)"/>
+
+ <property name="targets" readonly="true">
+ <getter>
+ <![CDATA[
+ var forAttrs = this.getAttribute("for");
+ if (!forAttrs) return null;
+ var targetIds = forAttrs.split(",");
+ if (targetIds.length == 0) return null;
+
+ var targets = new Array;
+ for (let j = 0, i = 0; i < targetIds.length; i++) {
+ var target = document.getElementById(targetIds[i]);
+ if (target) targets[j++] = target;
+ }
+ return targets;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="optargets" readonly="true">
+ <getter>
+ <![CDATA[
+ var forAttrs = this.getAttribute("opfor");
+ if (!forAttrs) return null;
+ var optargetIds = forAttrs.split(",");
+ if (optargetIds.length == 0) return null;
+
+ var optargets = new Array;
+ var j=0;
+ for (var i=0; i<optargetIds.length;i++) {
+ var optarget = document.getElementById(optargetIds[i]);
+ if (optarget) optargets[j++] = optarget;
+ }
+ return optargets;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="value" onget="return this.internalValue;">
+ <setter>
+ <![CDATA[
+ if (this.internalValue == val)
+ return val;
+ this.internalValue = val;
+ var menulist = document.getAnonymousNodes(this)[0];
+ menulist.selectedItem = this.validMenuitem;
+
+ // now notify targets of new parent's value
+ var targets = this.targets;
+ if (targets) {
+ for (var i=0; i < targets.length; i++) {
+ targets[i].parentValue = val;
+ }
+ }
+
+ // now notify optargets of new op parent's value
+ var optargets = this.optargets;
+ if (optargets) {
+ for (i=0; i < optargets.length; i++) {
+ optargets[i].opParentValue = val;
+ }
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <!-- label forwards to the internal menulist's "label" attribute -->
+ <property name="label" onget="return document.getAnonymousNodes(this)[0].selectedItem.getAttribute('label');">
+ </property>
+ <property name="validMenuitem" readonly="true">
+ <!-- Prepare menulist selection, adding a missing hidden menuitem if needed, and
+ updating the disabled state of the menulist label. -->
+ <getter>
+ <![CDATA[
+ if (this.value == -1) // -1 means not initialized
+ return null;
+
+ let menulist = document.getAnonymousNodes(this)[0];
+ let isCustom = isNaN(this.value);
+ let typedValue = isCustom ? this.value : parseInt(this.value);
+
+ // custom attribute to style the unavailable menulist item
+ menulist.setAttribute("unavailable",
+ !this.valueIds.includes(typedValue));
+
+ // add a hidden menulist item if value is missing
+ let menuitem = menulist.getElementsByAttribute("value", this.value).item(0);
+ if (!menuitem)
+ { // need to add a hidden menuitem
+ menuitem = menulist.appendItem(this.valueLabel, this.value);
+ menuitem.hidden = true;
+ }
+ return menuitem;
+ ]]>
+ </getter>
+ </property>
+ <method name="refreshList">
+ <parameter name="dontRestore"/> <!-- should we not restore old selection? -->
+ <body>
+ <![CDATA[
+ var menuItemIds = this.valueIds;
+ var menuItemStrings = this.valueStrings;
+
+ var menulist = document.getAnonymousNodes(this)[0];
+ var popup = menulist.firstChild;
+
+ // save our old "value" so we can restore it later
+ var oldData;
+ if (!dontRestore)
+ oldData = menulist.value;
+
+ // remove the old popup children
+ while (popup.hasChildNodes())
+ popup.lastChild.remove();
+
+ var newSelection;
+ var customizePos=-1;
+ for (var i = 0; i < menuItemIds.length; ++i)
+ {
+ // create the menuitem
+ if (Ci.nsMsgSearchAttrib.OtherHeader == menuItemIds[i].toString())
+ customizePos = i;
+ else
+ {
+ var menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", menuItemStrings[i]);
+ menuitem.setAttribute("value", menuItemIds[i]);
+ popup.appendChild(menuitem);
+ // try to restore the selection
+ if (!newSelection || oldData == menuItemIds[i].toString())
+ newSelection = menuitem;
+ }
+ }
+ if (customizePos != -1)
+ {
+ var separator = document.createElement("menuseparator");
+ popup.appendChild(separator);
+ menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", menuItemStrings[customizePos]);
+ menuitem.setAttribute("value", menuItemIds[customizePos]);
+ popup.appendChild(menuitem);
+ }
+ //
+ // If we are either uninitialized, or if we are called because
+ // of a change in our parent, update the value to the
+ // default stored in newSelection.
+ //
+ if ((this.value == -1 || dontRestore) && newSelection)
+ this.value = newSelection.getAttribute("value");
+ menulist.selectedItem = this.validMenuitem;
+ ]]>
+ </body>
+ </method>
+ <method name="onSelect">
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ var menulist = document.getAnonymousNodes(this)[0];
+ if (menulist.value == Ci.nsMsgSearchAttrib.OtherHeader) {
+ // Customize menuitem selected.
+ let args = {};
+ window.openDialog("chrome://messenger/content/CustomHeaders.xul",
+ "",
+ "modal,centerscreen,resizable,titlebar,chrome",
+ args);
+ // User may have removed the custom header currently selected in
+ // the menulist so temporarily set the selection to a safe value.
+ this.value = Ci.nsMsgSearchAttrib.OtherHeader;
+ // rebuild the menulist
+ UpdateAfterCustomHeaderChange();
+ // Find the created or chosen custom header and select it.
+ if (args.selectedVal) {
+ let menuitem = menulist.querySelector('[label="' +
+ args.selectedVal + '"]');
+ this.value = menuitem.value;
+ } else {
+ // Nothing was picked in the custom headers editor so just pick
+ // something instead of the current "Customize" menuitem.
+ this.value = menulist.getItemAtIndex(0).value;
+ }
+ } else {
+ this.value = menulist.value;
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <!-- searchattribute - Subject, Sender, To, CC, etc. -->
+ <binding id="searchattribute" name="searchAttribute"
+ extends="chrome://messenger/content/mailWidgets.xml#search-menulist-abstract">
+ <implementation>
+ <field name="stringBundle">
+ <![CDATA[
+ this.Services.strings.createBundle(
+ "chrome://messenger/locale/search-attributes.properties");
+ ]]>
+ </field>
+ <property name="valueLabel" readonly="true">
+ <getter>
+ <![CDATA[
+ if (isNaN(this.value)) // is this a custom term?
+ {
+ let customTerm = MailServices.filters.getCustomTerm(this.value);
+ if (customTerm)
+ return customTerm.name;
+ // The custom term may be missing after the extension that added
+ // it was disabled or removed. We need to notify the user.
+ let scriptError = Cc["@mozilla.org/scripterror;1"]
+ .createInstance(Ci.nsIScriptError);
+ scriptError.init("Missing custom search term " + this.value,
+ null, null, 0, 0, Ci.nsIScriptError.errorFlag,
+ "component javascript");
+ this.Services.console.logMessage(scriptError);
+ return this.stringBundle.GetStringFromName("MissingCustomTerm");
+ }
+ return this.stringBundle.GetStringFromName(
+ this.validityManager.getAttributeProperty(parseInt(this.value)));
+ ]]>
+ </getter>
+ </property>
+ <property name="valueIds" readonly="true">
+ <getter>
+ <![CDATA[
+ let result = this.validityTable.getAvailableAttributes();
+ // add any available custom search terms
+ for (let customTerm of MailServices.filters.getCustomTerms()) {
+ // for custom terms, the array element is a string with the custom id
+ // instead of the integer attribute
+ if (customTerm.getAvailable(this.searchScope, null))
+ result.push(customTerm.id);
+ }
+ return result;
+ ]]>
+ </getter>
+ </property>
+ <property name="valueStrings" readonly="true">
+ <getter>
+ <![CDATA[
+ let strings = new Array;
+ let ids = this.valueIds;
+ let hdrsArray = null;
+ try
+ {
+ let hdrs =
+ this.Services.prefs.getCharPref("mailnews.customHeaders");
+ hdrs = hdrs.replace(/\s+/g, ""); //remove white spaces before splitting
+ hdrsArray = hdrs.match(/[^:]+/g);
+ }
+ catch(ex)
+ {
+ }
+
+ let j = 0;
+ for (let i = 0; i < ids.length; i++)
+ {
+ if (isNaN(ids[i])) // Is this a custom search term?
+ {
+ let customTerm = MailServices.filters.getCustomTerm(ids[i]);
+ if (customTerm)
+ strings[i] = customTerm.name;
+ else
+ strings[i] = "";
+ }
+ else if(ids[i] > Ci.nsMsgSearchAttrib.OtherHeader && hdrsArray)
+ strings[i] = hdrsArray[j++];
+ else
+ strings[i] = this.stringBundle.GetStringFromName(
+ this.validityManager.getAttributeProperty(ids[i]));
+ }
+ return strings;
+ ]]>
+ </getter>
+ </property>
+ <constructor>
+ <![CDATA[
+ var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ initializeTermFromId(this.id);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <!-- searchoperator - Contains, Is Less than, etc -->
+ <binding id="searchoperator" name="searchOperator"
+ extends="chrome://messenger/content/mailWidgets.xml#search-menulist-abstract">
+ <implementation>
+ <field name="searchAttribute">Ci.nsMsgSearchAttrib.Default</field>
+ <field name="stringBundle">
+ <![CDATA[
+ this.Services.strings.createBundle("chrome://messenger/locale/search-operators.properties")
+ ]]>
+ </field>
+ <property name="valueLabel" readonly="true">
+ <getter>
+ <![CDATA[
+ return this.stringBundle.GetStringFromName(this.value);
+ ]]>
+ </getter>
+ </property>
+ <property name="valueIds" readonly="true">
+ <getter>
+ <![CDATA[
+ let isCustom = isNaN(this.searchAttribute);
+ if (isCustom)
+ {
+ let customTerm = MailServices.filters.getCustomTerm(this.searchAttribute);
+ if (customTerm)
+ return customTerm.getAvailableOperators(this.searchScope);
+ return [Ci.nsMsgSearchOp.Contains];
+ }
+ return this.validityTable.getAvailableOperators(this.searchAttribute);
+ ]]>
+ </getter>
+ </property>
+ <property name="valueStrings" readonly="true">
+ <getter>
+ <![CDATA[
+ let strings = new Array;
+ let ids = this.valueIds;
+ for (let i = 0; i < ids.length; i++)
+ strings[i] = this.stringBundle.GetStringFromID(ids[i]);
+ return strings;
+ ]]>
+ </getter>
+ </property>
+ <property name="parentValue">
+ <setter>
+ <![CDATA[
+ if (this.searchAttribute == val && val != Ci.nsMsgSearchAttrib.OtherHeader) return val;
+ this.searchAttribute = val;
+ this.refreshList(true); // don't restore the selection, since searchvalue nulls it
+ if (val == Ci.nsMsgSearchAttrib.AgeInDays) {
+ // Bug 187741 We want "Age in Days" to default to "is less than".
+ this.value = Ci.nsMsgSearchOp.IsLessThan;
+ }
+ return val;
+ ]]>
+ </setter>
+ <getter>
+ <![CDATA[
+ return this.searchAttribute;
+ ]]>
+ </getter>
+ </property>
+ <constructor>
+ <![CDATA[
+ var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <!-- searchvalue - a widget which dynamically changes its user interface
+ depending on what type of data it's supposed to be showing
+ currently handles arbitrary text entry, and menulists for
+ priority, status, junk status, tags, hasAttachment status,
+ and addressbook
+ -->
+ <binding id="searchvalue" name="searchValue">
+ <content>
+ <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup">
+ <xul:menuitem value="6" stringTag="priorityHighest" class="search-value-menuitem"/>
+ <xul:menuitem value="5" stringTag="priorityHigh" class="search-value-menuitem"/>
+ <xul:menuitem value="4" stringTag="priorityNormal" class="search-value-menuitem"/>
+ <xul:menuitem value="3" stringTag="priorityLow" class="search-value-menuitem"/>
+ <xul:menuitem value="2" stringTag="priorityLowest" class="search-value-menuitem"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup">
+ <xul:menuitem value="2" stringTag="replied" class="search-value-menuitem"/>
+ <xul:menuitem value="1" stringTag="read" class="search-value-menuitem"/>
+ <xul:menuitem value="65536" stringTag="new" class="search-value-menuitem"/>
+ <xul:menuitem value="4096" stringTag="forwarded" class="search-value-menuitem"/>
+ <xul:menuitem value="4" stringTag="flagged" class="search-value-menuitem"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup addrbooksPopup" localonly="true"/>
+ </xul:menulist>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup">
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup">
+ <xul:menuitem value="2" stringTag="junk" class="search-value-menuitem"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup">
+ <xul:menuitem value="0" stringTag="hasAttachments" class="search-value-menuitem"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
+ <xul:menupopup class="search-value-popup">
+ <xul:menuitem value="plugin" stringTag="junkScoreOriginPlugin"
+ class="search-value-menuitem"/>
+ <xul:menuitem value="user" stringTag="junkScoreOriginUser"
+ class="search-value-menuitem"/>
+ <xul:menuitem value="filter" stringTag="junkScoreOriginFilter"
+ class="search-value-menuitem"/>
+ <xul:menuitem value="whitelist" stringTag="junkScoreOriginWhitelist"
+ class="search-value-menuitem"/>
+ <xul:menuitem value="imapflag" stringTag="junkScoreOriginImapFlag"
+ class="search-value-menuitem"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:hbox flex="1" class="search-value-custom" xbl:inherits="disabled"/>
+ </content>
+ <implementation>
+ <field name="internalOperator">null</field>
+ <field name="internalAttribute">null</field>
+ <field name="internalValue">null</field>
+ <property name="opParentValue" onget="return this.internalOperator;">
+ <setter>
+ <![CDATA[
+ // noop if we're not changing it
+ if (this.internalOperator == val) return val;
+
+ // Keywords has the null field IsEmpty
+ if (this.searchAttribute == Ci.nsMsgSearchAttrib.Keywords) {
+ if (val == Ci.nsMsgSearchOp.IsEmpty ||
+ val == Ci.nsMsgSearchOp.IsntEmpty)
+ this.setAttribute("selectedIndex", "-1");
+ else
+ this.setAttribute("selectedIndex", "5");
+ }
+
+ // JunkStatus has the null field IsEmpty
+ if (this.searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) {
+ if (val == Ci.nsMsgSearchOp.IsEmpty ||
+ val == Ci.nsMsgSearchOp.IsntEmpty)
+ this.setAttribute("selectedIndex", "-1");
+ else
+ this.setAttribute("selectedIndex", "6");
+ }
+
+ // if it's not sender, to, cc, alladdresses, or toorcc, we don't care
+ if (this.searchAttribute != Ci.nsMsgSearchAttrib.Sender &&
+ this.searchAttribute != Ci.nsMsgSearchAttrib.To &&
+ this.searchAttribute != Ci.nsMsgSearchAttrib.ToOrCC &&
+ this.searchAttribute != Ci.nsMsgSearchAttrib.AllAddresses &&
+ this.searchAttribute != Ci.nsMsgSearchAttrib.CC ) {
+ this.internalOperator = val;
+ return val;
+ }
+
+ var children = document.getAnonymousNodes(this);
+ if (val == Ci.nsMsgSearchOp.IsntInAB ||
+ val == Ci.nsMsgSearchOp.IsInAB) {
+ // if the old internalOperator was
+ // IsntInAB or IsInAB, and the new internalOperator is
+ // IsntInAB or IsInAB, noop because the search value
+ // was an ab type, and it still is.
+ // otherwise, switch to the ab picker and select the PAB
+ if (this.internalOperator != Ci.nsMsgSearchOp.IsntInAB &&
+ this.internalOperator != Ci.nsMsgSearchOp.IsInAB) {
+ var abs = children[4].getElementsByAttribute("value", "moz-abmdbdirectory://abook.mab");
+ if (abs.item(0))
+ children[4].selectedItem = abs[0];
+ this.setAttribute("selectedIndex", "4");
+ }
+ }
+ else {
+ // if the old internalOperator wasn't
+ // IsntInAB or IsInAB, and the new internalOperator isn't
+ // IsntInAB or IsInAB, noop because the search value
+ // wasn't an ab type, and it still isn't.
+ // otherwise, switch to the textbox and clear it
+ if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
+ this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
+ children[0].value = "";
+ this.setAttribute("selectedIndex", "0");
+ }
+ }
+
+ this.internalOperator = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <!-- parentValue forwards to the attribute -->
+ <property name="parentValue" onset="return this.searchAttribute=val;"
+ onget="return this.searchAttribute;"/>
+ <property name="searchAttribute" onget="return this.internalAttribute;">
+ <setter>
+ <![CDATA[
+ // noop if we're not changing it
+ if (this.internalAttribute == val) return val;
+ this.internalAttribute = val;
+
+ // if the searchAttribute changing, null out the internalOperator
+ this.internalOperator = null;
+
+ // we inherit from a deck, so just use it's index attribute
+ // to hide/show widgets
+ if (isNaN(val)) // Is this a custom attribute?
+ {
+ this.setAttribute("selectedIndex", "9");
+ let customHbox = document.getAnonymousNodes(this)[9];
+ if (this.internalValue)
+ customHbox.setAttribute("value", this.internalValue.str);
+ // the searchAttribute attribute is intended as a selector in
+ // CSS for custom search terms to bind a custom value
+ customHbox.setAttribute("searchAttribute", val);
+ }
+ else if (val == Ci.nsMsgSearchAttrib.Priority)
+ this.setAttribute("selectedIndex", "1");
+ else if (val == Ci.nsMsgSearchAttrib.MsgStatus)
+ this.setAttribute("selectedIndex", "2");
+ else if (val == Ci.nsMsgSearchAttrib.Date)
+ this.setAttribute("selectedIndex", "3");
+ else if (val == Ci.nsMsgSearchAttrib.Sender) {
+ // since the internalOperator is null
+ // this is the same as the initial state
+ // the initial state for Sender isn't an ab type search
+ // it's a text search, so show the textbox
+ this.setAttribute("selectedIndex", "0");
+ }
+ else if (val == Ci.nsMsgSearchAttrib.Keywords) {
+ this.setAttribute("selectedIndex", "5");
+ }
+ else if (val == Ci.nsMsgSearchAttrib.JunkStatus) {
+ this.setAttribute("selectedIndex", "6");
+ }
+ else if (val == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
+ this.setAttribute("selectedIndex", "7");
+ }
+ else if (val == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
+ this.setAttribute("selectedIndex", "8");
+ }
+ else {
+ // a normal text field
+ this.setAttribute("selectedIndex", "0");
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="value" onget="return this.internalValue;">
+ <setter>
+ <![CDATA[
+ // val is a nsIMsgSearchValue object
+ this.internalValue = val;
+ var attrib = this.internalAttribute;
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ var children = document.getAnonymousNodes(this);
+ this.searchAttribute = attrib;
+ if (isNaN(attrib)) // a custom term
+ {
+ let customHbox = document.getAnonymousNodes(this)[9];
+ customHbox.setAttribute("value", val.str);
+ return val;
+ }
+ if (attrib == nsMsgSearchAttrib.Priority) {
+ var matchingPriority =
+ children[1].getElementsByAttribute("value", val.priority);
+ if (matchingPriority.item(0))
+ children[1].selectedItem = matchingPriority[0];
+ }
+ else if (attrib == nsMsgSearchAttrib.MsgStatus) {
+ var matchingStatus =
+ children[2].getElementsByAttribute("value", val.status);
+ if (matchingStatus.item(0))
+ children[2].selectedItem = matchingStatus[0];
+ }
+ else if (attrib == nsMsgSearchAttrib.AgeInDays)
+ children[0].value = val.age;
+ else if (attrib == nsMsgSearchAttrib.Date)
+ children[3].value = convertPRTimeToString(val.date);
+ else if (attrib == nsMsgSearchAttrib.Sender ||
+ attrib == nsMsgSearchAttrib.To ||
+ attrib == nsMsgSearchAttrib.CC ||
+ attrib == nsMsgSearchAttrib.AllAddresses ||
+ attrib == nsMsgSearchAttrib.ToOrCC)
+ {
+ if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
+ this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
+ var abs = children[4].getElementsByAttribute("value", val.str);
+ if (abs.item(0))
+ children[4].selectedItem = abs[0];
+ }
+ else
+ children[0].value = val.str;
+ }
+ else if (attrib == nsMsgSearchAttrib.Keywords)
+ {
+ var keywordVal = children[5].getElementsByAttribute("value", val.str);
+ if (keywordVal.item(0))
+ {
+ children[5].value = val.str;
+ children[5].selectedItem = keywordVal[0];
+ }
+ }
+ else if (attrib == nsMsgSearchAttrib.JunkStatus) {
+ var junkStatus =
+ children[6].getElementsByAttribute("value", val.junkStatus);
+ if (junkStatus.item(0))
+ children[6].selectedItem = junkStatus[0];
+ }
+ else if (attrib == nsMsgSearchAttrib.HasAttachmentStatus) {
+ var hasAttachmentStatus =
+ children[7].getElementsByAttribute("value", val.hasAttachmentStatus);
+ if (hasAttachmentStatus.item(0))
+ children[7].selectedItem = hasAttachmentStatus[0];
+ }
+ else if (attrib == nsMsgSearchAttrib.JunkScoreOrigin) {
+ var junkScoreOrigin =
+ children[8].getElementsByAttribute("value", val.str);
+ if (junkScoreOrigin.item(0))
+ children[8].selectedItem = junkScoreOrigin[0];
+ }
+ else if (attrib == nsMsgSearchAttrib.JunkPercent) {
+ children[0].value = val.junkPercent;
+ }
+ else if (attrib == nsMsgSearchAttrib.Size) {
+ children[0].value = val.size;
+ }
+ else
+ children[0].value = val.str;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <method name="save">
+ <body>
+ <![CDATA[
+ var searchValue = this.value;
+ var searchAttribute = this.searchAttribute;
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ var children = document.getAnonymousNodes(this);
+
+ searchValue.attrib = searchAttribute;
+ if (searchAttribute == nsMsgSearchAttrib.Priority) {
+ searchValue.priority = children[1].selectedItem.value;
+ }
+ else if (searchAttribute == nsMsgSearchAttrib.MsgStatus)
+ searchValue.status = children[2].value;
+ else if (searchAttribute == nsMsgSearchAttrib.AgeInDays)
+ searchValue.age = children[0].value;
+ else if (searchAttribute == nsMsgSearchAttrib.Date)
+ searchValue.date = convertStringToPRTime(children[3].value);
+ else if (searchAttribute == nsMsgSearchAttrib.Sender ||
+ searchAttribute == nsMsgSearchAttrib.To ||
+ searchAttribute == nsMsgSearchAttrib.CC ||
+ searchAttribute == nsMsgSearchAttrib.AllAddresses ||
+ searchAttribute == nsMsgSearchAttrib.ToOrCC)
+ {
+ if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
+ this.internalOperator == Ci.nsMsgSearchOp.IsInAB)
+ searchValue.str = children[4].selectedItem.value;
+ else
+ searchValue.str = children[0].value;
+ }
+ else if (searchAttribute == nsMsgSearchAttrib.Keywords)
+ {
+ searchValue.str = children[5].value;
+ }
+ else if (searchAttribute == nsMsgSearchAttrib.JunkStatus)
+ searchValue.junkStatus = children[6].value;
+ else if (searchAttribute == nsMsgSearchAttrib.JunkPercent)
+ searchValue.junkPercent = children[0].value;
+ else if (searchAttribute == nsMsgSearchAttrib.Size)
+ searchValue.size = children[0].value;
+ else if (searchAttribute == nsMsgSearchAttrib.HasAttachmentStatus)
+ searchValue.status = 0x10000000; // 0x10000000 is MSG_FLAG_ATTACHMENT;
+ else if (searchAttribute == nsMsgSearchAttrib.JunkScoreOrigin)
+ searchValue.str = children[8].value;
+ else if (isNaN(searchAttribute)) // a custom term
+ {
+ searchValue.attrib = nsMsgSearchAttrib.Custom;
+ searchValue.str = children[9].getAttribute("value");
+ }
+ else
+ searchValue.str = children[0].value;
+ ]]>
+ </body>
+ </method>
+ <method name="saveTo">
+ <parameter name="searchValue"/>
+ <body>
+ <![CDATA[
+ this.internalValue = searchValue;
+ this.save();
+ ]]>
+ </body>
+ </method>
+ <method name="fillInTags">
+ <body>
+ <![CDATA[
+ var children = document.getAnonymousNodes(this);
+ var popupMenu = children[5].firstChild;
+ var tagArray = MailServices.tags.getAllTags();
+ for (var i = 0; i < tagArray.length; ++i)
+ {
+ var taginfo = tagArray[i];
+ var newMenuItem = document.createElement('menuitem');
+ newMenuItem.setAttribute('label', taginfo.tag);
+ newMenuItem.setAttribute('value', taginfo.key);
+ popupMenu.appendChild(newMenuItem);
+ if (!i)
+ children[5].selectedItem = newMenuItem;
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="fillStringsForChildren">
+ <parameter name="parentNode"/>
+ <parameter name="bundle"/>
+ <body>
+ <![CDATA[
+ var children = parentNode.childNodes;
+ var len=children.length;
+ for (var i=0; i<len; i++) {
+ var node = children[i];
+ var stringTag = node.getAttribute("stringTag");
+ if (stringTag) {
+ var attr = (node.tagName == "label") ? "value" : "label";
+ node.setAttribute(attr, bundle.GetStringFromName(stringTag));
+ }
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="initialize">
+ <parameter name="menulist"/>
+ <parameter name="bundle"/>
+ <body>
+ <![CDATA[
+ this.fillStringsForChildren(menulist.firstChild, bundle);
+ ]]>
+ </body>
+ </method>
+ <constructor>
+ <![CDATA[
+ var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+
+ // initialize strings
+ let bundle = Services.strings.createBundle("chrome://messenger/locale/messenger.properties");
+
+ // intialize the priority picker
+ this.initialize(document.getAnonymousNodes(this)[1], bundle);
+
+ // initialize the status picker
+ this.initialize(document.getAnonymousNodes(this)[2], bundle);
+
+ // initialize the date picker
+ var datePicker = document.getAnonymousNodes(this)[3];
+ var searchAttribute = this.searchAttribute;
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ var time;
+ if (searchAttribute == nsMsgSearchAttrib.Date)
+ time = datePicker.value;
+ else
+ time = new Date();
+ // do .value instead of .setAttribute("value", xxx);
+ // to work around for bug #179412
+ // (caused by bug #157210)
+ //
+ // the searchvalue widget has two textboxes
+ // one for text, one as a placeholder for a date / calendar widget
+ datePicker.value = convertDateToString(time);
+
+ // initialize the address book picker
+ this.initialize(document.getAnonymousNodes(this)[4], bundle);
+
+ // initialize the junk status picker
+ this.initialize(document.getAnonymousNodes(this)[6], bundle);
+
+ // initialize the has attachment status picker
+ this.initialize(document.getAnonymousNodes(this)[7], bundle);
+
+ // initialize the junk score origin picker
+ this.initialize(document.getAnonymousNodes(this)[8], bundle);
+
+ // initialize the tag list
+ fillInTags();
+ ]]>
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="keypress" keycode="VK_RETURN" modifiers="accel any"
+ action="onEnterInSearchTerm(event);" preventdefault="true"/>
+ </handlers>
+ </binding>
+
+ <binding id="folderSummary-popup" extends="chrome://global/content/bindings/popup.xml#tooltip">
+ <content>
+ <children>
+ <xul:folderSummary/>
+ </children>
+ </content>
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ let msgFolder = gFolderTreeView.getFolderAtCoords(event.clientX,
+ event.clientY);
+ if (!msgFolder)
+ return false;
+
+ let tooltipnode = document.getAnonymousNodes(this)[0];
+ let asyncResults = {};
+ if (tooltipnode.parseFolder(msgFolder, null, asyncResults))
+ return true;
+
+ let row = {}, col = {};
+ gFolderTreeView._tree.getCellAt(event.clientX, event.clientY, row,
+ col, {});
+ if (col.value.id == "folderNameCol") {
+ let cropped = gFolderTreeView._tree.isCellCropped(row.value,
+ col.value);
+ if (tooltipnode.addLocationInfo(msgFolder, cropped))
+ return true;
+ }
+
+ let counts = gFolderTreeView.getSummarizedCounts(row.value,
+ col.value.id);
+ if (counts) {
+ if (tooltipnode.addSummarizeExplain(counts))
+ return true;
+ }
+
+ return false;
+ ]]>
+ </handler>
+
+ <handler event="popuphiding">
+ document.getAnonymousNodes(this)[0].clear();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="folderSummary">
+ <content>
+ <xul:vbox/>
+ </content>
+
+ <implementation>
+ <field name="mMaxMsgHdrsInPopup">8</field>
+ <property name="hasMessages" readonly="true" onget="return document.getAnonymousNodes(this)[0].hasChildNodes();"/>
+ <method name="parseFolder">
+ <parameter name="aFolder"/>
+ <parameter name="aUrlListener"/>
+ <parameter name="aOutAsync"/>
+ <body>
+ <![CDATA[
+ // Skip servers, Trash and Junk folders, and newgroups.
+ if (!aFolder || aFolder.isServer || !aFolder.hasNewMessages ||
+ aFolder.getFlag(Ci.nsMsgFolderFlags.Junk) ||
+ aFolder.getFlag(Ci.nsMsgFolderFlags.Trash) ||
+ (aFolder.server instanceof Ci.nsINntpIncomingServer))
+ return false;
+ let showPreviewText = this.Services.prefs.getBoolPref("mail.biff.alert.show_preview");
+ let folderArray = [];
+ let msgDatabase;
+ try {
+ msgDatabase = aFolder.msgDatabase;
+ } catch(e) {
+ // The database for this folder may be missing
+ // (e.g. outdated/missing .msf), so just skip this folder.
+ return false;
+ }
+
+ if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual)
+ {
+ let dbFolderInfo = msgDatabase.dBFolderInfo;
+ var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
+ var srchFolderUriArray = srchFolderUri.split('|');
+ var foldersAdded = 0;
+ var RDF = Cc['@mozilla.org/rdf/rdf-service;1']
+ .getService(Ci.nsIRDFService);
+ for (var i in srchFolderUriArray)
+ {
+ var realFolder = RDF.GetResource(srchFolderUriArray[i])
+ .QueryInterface(Ci.nsIMsgFolder);
+ if (!realFolder.isServer)
+ folderArray[foldersAdded++] = realFolder;
+ }
+ }
+ else {
+ folderArray[0] = aFolder;
+ }
+
+ var foundNewMsg = false;
+ for (var folderIndex = 0; folderIndex < folderArray.length; folderIndex++)
+ {
+ aFolder = folderArray[folderIndex];
+ // now get the database
+ try {
+ msgDatabase = aFolder.msgDatabase;
+ } catch(e) {
+ // The database for this folder may be missing
+ // (e.g. outdated/missing .msf), then just skip this folder.
+ continue;
+ }
+
+ aFolder.msgDatabase = null;
+ let msgKeys = msgDatabase.getNewList();
+
+ if (!msgKeys.length)
+ continue;
+
+ if (showPreviewText)
+ {
+ // fetchMsgPreviewText forces the previewText property to get generated
+ // for each of the message keys.
+ try {
+ aOutAsync.value = aFolder.fetchMsgPreviewText(msgKeys, aUrlListener);
+ aFolder.msgDatabase = null;
+ }
+ catch (ex)
+ {
+ // fetchMsgPreviewText throws an error when we call it on a news folder, we should just not show
+ // the tooltip if this method returns an error.
+ aFolder.msgDatabase = null;
+ continue;
+ }
+ }
+ // if fetching the preview text is going to be an asynch operation and the caller
+ // is set up to handle that fact, then don't bother filling in any of the fields since
+ // we'll have to do this all over again when the fetch for the preview text completes.
+ // We don't expect to get called with a urlListener if we're doing a virtual folder.
+ if (aOutAsync.value && aUrlListener)
+ return false;
+ var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ foundNewMsg = true;
+
+ var index = 0;
+ while (document.getAnonymousNodes(this)[0].childNodes.length < this.mMaxMsgHdrsInPopup && index < msgKeys.length)
+ {
+ var msgPopup = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummaryMessage");
+ var msgHdr = msgDatabase.GetMsgHdrForKey(msgKeys[index++]);
+
+ var msgSubject = msgHdr.mime2DecodedSubject;
+ const kMsgFlagHasRe = 0x0010; // MSG_FLAG_HAS_RE
+ if(msgHdr.flags & kMsgFlagHasRe)
+ msgSubject = (msgSubject) ? "Re: " + msgSubject : "Re: ";
+
+ msgPopup.setAttribute('subject', msgSubject);
+
+ var previewText = msgHdr.getStringProperty('preview');
+ // convert the preview text from utf-8 to unicode
+ if (previewText)
+ {
+ try
+ {
+ var text = unicodeConverter.ConvertToUnicode(previewText);
+ if (text)
+ msgPopup.setAttribute('previewText', text);
+ }
+ catch (ex) { }
+ }
+
+ var names = {};
+ var emails = {};
+ var numAddresses = MailServices.headerParser.parseHeadersWithArray(msgHdr.mime2DecodedAuthor, emails, names, {});
+ msgPopup.setAttribute('sender', names.value[0] ? names.value[0] : emails.value[0]);
+ msgPopup.messageUri = aFolder.getUriForMsg(msgHdr);
+ msgPopup.folderUri = aFolder.URI;
+ msgPopup.msgKey = msgHdr.messageKey;
+ document.getAnonymousNodes(this)[0].appendChild(msgPopup);
+ }
+ if (document.getAnonymousNodes(this)[0].childNodes.length >= this.mMaxMsgHdrsInPopup)
+ return true;
+ }
+ return foundNewMsg;
+ ]]>
+ </body>
+ </method>
+
+ <method name="addLocationInfo">
+ <parameter name="aFolder"/>
+ <parameter name="aCropped"/>
+ <body>
+ <![CDATA[
+ let popupValue = null;
+ // Display also server name for items that are on level 0 and are
+ // not server names by themselves and do not have server name
+ // already appended in their label.
+ let folderIndex = gFolderTreeView.getIndexOfFolder(aFolder);
+ if (!aFolder.isServer &&
+ gFolderTreeView.getLevel(folderIndex) == 0 &&
+ !gFolderTreeView.getServerNameAdded(folderIndex)) {
+ let midPath = "";
+ let midFolder = aFolder.parent;
+ while (aFolder.server.rootFolder != midFolder) {
+ midPath = midFolder.name + " - " + midPath;
+ midFolder = midFolder.parent;
+ }
+ popupValue = aFolder.server.prettyName + " - " + midPath +
+ aFolder.name;
+ }
+ // If folder name is cropped or is a newsgroup and abbreviated per
+ // pref, use the full name as a tooltip.
+ else if (aCropped ||
+ ((aFolder.server instanceof Ci.nsINntpIncomingServer) &&
+ !(aFolder.flags & Ci.nsMsgFolderFlags.Virtual) &&
+ aFolder.server.abbreviate) && !aFolder.isServer) {
+ popupValue = aFolder.name;
+ }
+
+ if (popupValue) {
+ let loc = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummaryLocation");
+ loc.setAttribute("location", popupValue);
+ document.getAnonymousNodes(this)[0].appendChild(loc);
+ return true;
+ }
+
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="addSummarizeExplain">
+ <parameter name="aCounts"/>
+ <body>
+ <![CDATA[
+ if (!aCounts || !aCounts[1])
+ return false;
+ let expl = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummarySubfoldersSummary");
+ let sumString = document.getElementById("bundle_messenger")
+ .getFormattedString("subfoldersExplanation", [aCounts[0], aCounts[1]], 2);
+ expl.setAttribute("subfolders", sumString);
+ document.getAnonymousNodes(this)[0].appendChild(expl);
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="clear">
+ <body>
+ <![CDATA[
+ var containingBox = document.getAnonymousNodes(this)[0];
+ while (containingBox.hasChildNodes())
+ containingBox.lastChild.remove();
+ ]]>
+ </body>
+ </method>
+ <constructor>
+ <![CDATA[
+ var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="folderSummary-location">
+ <content>
+ <xul:hbox>
+ <xul:label anonid="location" xbl:inherits="value=location"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="folderSummary-subfoldersSummary">
+ <content>
+ <xul:hbox>
+ <xul:label anonid="subfolders" xbl:inherits="value=subfolders"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="folderSummary-message">
+ <content>
+ <xul:vbox class="folderSummaryMessage">
+ <xul:hbox class="folderSummary-message-row">
+ <xul:label anonid="subject" flex="1" class="folderSummary-subject" xbl:inherits="value=subject" crop="right"/>
+ <xul:label anonid="sender" class="folderSummary-sender" xbl:inherits="value=sender" crop="right"/>
+ <xul:spring anonid="spring" flex="100%"/>
+ </xul:hbox>
+ <xul:description anonid="preview" class="folderSummary-message-row folderSummary-previewText" xbl:inherits="value=previewText" crop="right"></xul:description>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <constructor>
+ <![CDATA[
+ var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+
+ if (!this.Services.prefs.getBoolPref("mail.biff.alert.show_preview"))
+ document.getAnonymousElementByAttribute(this, "anonid", "preview").hidden = true;
+ var hideSubject = !this.Services.prefs.getBoolPref("mail.biff.alert.show_subject");
+ var hideSender = !this.Services.prefs.getBoolPref("mail.biff.alert.show_sender");
+ if (hideSubject)
+ document.getAnonymousElementByAttribute(this, "anonid", "subject").hidden = true;
+ if (hideSender)
+ document.getAnonymousElementByAttribute(this, "anonid", "sender").hidden = true;
+ if (hideSubject && hideSender)
+ document.getAnonymousElementByAttribute(this, "anonid", "spring").hidden = true;
+ ]]>
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="click" button="0">
+ <![CDATA[
+ var topmostMsgWindow;
+ try {
+ topmostMsgWindow = MailServices.mailSession.topmostMsgWindow;
+ } catch (ex) {}
+
+ if (topmostMsgWindow)
+ {
+ // Bring window to the front
+ topmostMsgWindow.domWindow.focus();
+
+ try {
+ // SelectFolder throws an exception if the folder is not in the current folder view
+ MailServices.mailSession.topmostMsgWindow.windowCommands.selectFolder(this.folderUri);
+ MailServices.mailSession.topmostMsgWindow.windowCommands.selectMessage(this.messageUri);
+ } catch (ex) {}
+ }
+ else
+ {
+ // open a new window
+ var mailWindowService = Cc["@mozilla.org/messenger/windowservice;1"].
+ getService(Ci.nsIMessengerWindowService);
+ mailWindowService.openMessengerWindowWithUri("mail:3pane", this.folderUri, this.msgKey);
+ }
+
+ if (gAlertListener)
+ gAlertListener.observe(null, "alertclicksimplecallback", "");
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/comm/suite/mailnews/content/mailWindow.js b/comm/suite/mailnews/content/mailWindow.js
new file mode 100644
index 0000000000..388ebe2cb1
--- /dev/null
+++ b/comm/suite/mailnews/content/mailWindow.js
@@ -0,0 +1,593 @@
+/* -*- 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/. */
+
+ //This file stores variables common to mail windows
+
+var messenger;
+var statusFeedback;
+var msgWindow;
+
+var msgComposeService;
+var accountManager;
+var RDF;
+var msgComposeType;
+var msgComposeFormat;
+
+var gMessengerBundle;
+var gBrandBundle;
+
+var accountCentralBox = null;
+var gDisableViewsSearch = null;
+var gAccountCentralLoaded = true;
+//End progress and Status variables
+
+var gOfflineManager;
+
+function OnMailWindowUnload()
+{
+ RemoveMailOfflineObserver();
+ ClearPendingReadTimer();
+
+ var searchSession = GetSearchSession();
+ if (searchSession)
+ {
+ removeGlobalListeners();
+ if (gPreQuickSearchView) //close the cached pre quick search view
+ gPreQuickSearchView.close();
+ }
+
+ var dbview = GetDBView();
+ if (dbview) {
+ dbview.close();
+ }
+
+ MailServices.mailSession.RemoveFolderListener(folderListener);
+
+ MailServices.mailSession.RemoveMsgWindow(msgWindow);
+ messenger.setWindow(null, null);
+
+ msgWindow.closeWindow();
+
+ msgWindow.msgHeaderSink = null;
+ msgWindow.notificationCallbacks = null;
+ gDBView = null;
+}
+
+/**
+ * When copying/dragging, convert imap/mailbox URLs of images into data URLs so
+ * that the images can be accessed in a paste elsewhere.
+ */
+function onCopyOrDragStart(e) {
+ let browser = getBrowser();
+ if (!browser) {
+ return;
+ }
+ let sourceDoc = browser.contentDocument;
+ if (e.target.ownerDocument != sourceDoc) {
+ // We're only interested if this is in the message content.
+ return;
+ }
+
+ let imgMap = new Map(); // Mapping img.src -> dataURL.
+
+ // For copy, the data of what is to be copied is not accessible at this point.
+ // Figure out what images are a) part of the selection and b) visible in
+ // the current document. If their source isn't http or data already, convert
+ // them to data URLs.
+ let selection = sourceDoc.getSelection();
+ let draggedImg = selection.isCollapsed ? e.target : null;
+ for (let img of sourceDoc.images) {
+ if (/^(https?|data):/.test(img.src)) {
+ continue;
+ }
+
+ if (img.naturalWidth == 0) {
+ // Broken/inaccessible image then...
+ continue;
+ }
+
+ if (!draggedImg && !selection.containsNode(img, true)) {
+ continue;
+ }
+
+ let style = window.getComputedStyle(img);
+ if (style.display == "none" || style.visibility == "hidden") {
+ continue;
+ }
+
+ // Do not convert if the image is specifically flagged to not snarf.
+ if (img.getAttribute("moz-do-not-send") == "true") {
+ continue;
+ }
+
+ // We don't need to wait for the image to load. If it isn't already loaded
+ // in the source document, we wouldn't want it anyway.
+ let canvas = sourceDoc.createElement("canvas");
+ canvas.width = img.width;
+ canvas.height = img.height;
+ canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);
+
+ let type = /\.jpe?g$/i.test(img.src) ? "image/jpg" : "image/png";
+ imgMap.set(img.src, canvas.toDataURL(type));
+ }
+
+ if (imgMap.size == 0) {
+ // Nothing that needs converting!
+ return;
+ }
+
+ let clonedSelection = draggedImg ? draggedImg.cloneNode(false) :
+ selection.getRangeAt(0).cloneContents();
+ let div = sourceDoc.createElement("div");
+ div.appendChild(clonedSelection);
+
+ let images = div.querySelectorAll("img");
+ for (let img of images) {
+ if (!imgMap.has(img.src)) {
+ continue;
+ }
+ img.src = imgMap.get(img.src);
+ }
+
+ let html = div.innerHTML;
+ let parserUtils = Cc["@mozilla.org/parserutils;1"]
+ .getService(Ci.nsIParserUtils);
+ let plain =
+ parserUtils.convertToPlainText(html,
+ Ci.nsIDocumentEncoder.OutputForPlainTextClipboardCopy,
+ 0);
+
+ // Copy operation.
+ if ("clipboardData" in e) {
+ e.clipboardData.setData("text/html", html);
+ e.clipboardData.setData("text/plain", plain);
+ e.preventDefault();
+ }
+ // Drag operation.
+ else if ("dataTransfer" in e) {
+ e.dataTransfer.setData("text/html", html);
+ e.dataTransfer.setData("text/plain", plain);
+ }
+}
+
+function CreateMailWindowGlobals()
+{
+ // Get the messenger instance.
+ messenger = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger);
+
+ // Create windows status feedback
+ // set the JS implementation of status feedback before creating the c++ one..
+ window.MsgStatusFeedback = new nsMsgStatusFeedback();
+ // Double register the status feedback object as the xul browser window
+ // implementation.
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = window.MsgStatusFeedback;
+
+ statusFeedback = Cc["@mozilla.org/messenger/statusfeedback;1"]
+ .createInstance(Ci.nsIMsgStatusFeedback);
+ statusFeedback.setWrappedStatusFeedback(window.MsgStatusFeedback);
+
+ window.MsgWindowCommands = new nsMsgWindowCommands();
+
+ //Create message window object
+ msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(Ci.nsIMsgWindow);
+
+ msgComposeService = Cc['@mozilla.org/messengercompose;1']
+ .getService(Ci.nsIMsgComposeService);
+
+ accountManager = MailServices.accounts;
+
+ RDF = Cc['@mozilla.org/rdf/rdf-service;1']
+ .getService(Ci.nsIRDFService);
+
+ msgComposeType = Ci.nsIMsgCompType;
+ msgComposeFormat = Ci.nsIMsgCompFormat;
+
+ gMessengerBundle = document.getElementById("bundle_messenger");
+ gBrandBundle = document.getElementById("bundle_brand");
+
+ msgWindow.notificationCallbacks = new nsMsgBadCertHandler();
+}
+
+function InitMsgWindow()
+{
+ msgWindow.windowCommands = new nsMsgWindowCommands();
+ // set the domWindow before setting the status feedback and header sink objects
+ msgWindow.domWindow = window;
+ msgWindow.statusFeedback = statusFeedback;
+ msgWindow.msgHeaderSink = messageHeaderSink;
+ MailServices.mailSession.AddMsgWindow(msgWindow);
+
+ var messagepane = getMessageBrowser();
+ messagepane.docShell.allowAuth = false;
+ messagepane.docShell.allowDNSPrefetch = false;
+ msgWindow.rootDocShell.allowAuth = true;
+ msgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+ // Ensure we don't load xul error pages into the main window
+ msgWindow.rootDocShell.useErrorPages = false;
+
+ document.addEventListener("copy", onCopyOrDragStart, true);
+ document.addEventListener("dragstart", onCopyOrDragStart, true);
+}
+
+function messagePaneOnResize(event)
+{
+ // scale any overflowing images
+ var messagepane = getMessageBrowser();
+ var doc = messagepane.contentDocument;
+ var imgs = doc.images;
+ for (var img of imgs)
+ {
+ if (img.className == "moz-attached-image")
+ {
+ if (img.naturalWidth <= doc.body.clientWidth)
+ {
+ img.removeAttribute("isshrunk");
+ img.removeAttribute("overflowing");
+ }
+ else if (img.hasAttribute("shrinktofit"))
+ {
+ img.setAttribute("isshrunk", "true");
+ img.removeAttribute("overflowing");
+ }
+ else
+ {
+ img.setAttribute("overflowing", "true");
+ img.removeAttribute("isshrunk");
+ }
+ }
+ }
+
+}
+
+function messagePaneOnClick(event)
+{
+ // if this is stand alone mail (no browser)
+ // or this isn't a simple left click, do nothing, and let the normal code execute
+ if (event.button != 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
+ return contentAreaClick(event);
+
+ // try to determine the href for what you are clicking on.
+ // for example, it might be "" if you aren't left clicking on a link
+ var ceParams = hrefAndLinkNodeForClickEvent(event);
+ if (!ceParams && !event.button)
+ {
+ var target = event.target;
+ // is this an image that we might want to scale?
+ if (target instanceof Ci.nsIImageLoadingContent)
+ {
+ // make sure it loaded successfully
+ var req = target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (!req || req.imageStatus & Ci.imgIRequest.STATUS_ERROR)
+ return true;
+ // is it an inline attachment?
+ if (/^moz-attached-image/.test(target.className))
+ {
+ if (target.hasAttribute("isshrunk"))
+ {
+ // currently shrunk to fit, so unshrink it
+ target.removeAttribute("isshrunk");
+ target.removeAttribute("shrinktofit");
+ target.setAttribute("overflowing", "true");
+ }
+ else if (target.hasAttribute("overflowing"))
+ {
+ // user wants to shrink now
+ target.setAttribute("isshrunk", "true");
+ target.setAttribute("shrinktofit", "true");
+ target.removeAttribute("overflowing");
+ }
+ }
+ }
+ return true;
+ }
+ var href = ceParams.href;
+
+ // we know that http://, https://, ftp://, file://, chrome://,
+ // resource://, and about, should load in a browser. but if
+ // we don't have one of those (examples are mailto, imap, news, mailbox, snews,
+ // nntp, ldap, and externally handled schemes like aim) we may or may not
+ // want a browser window, in which case we return here and let the normal code
+ // handle it
+ var needABrowser = /(^http(s)?:|^ftp:|^file:|^chrome:|^resource:|^about:)/i;
+ if (href.search(needABrowser) == -1)
+ return true;
+
+ // however, if the protocol should not be loaded internally, then we should
+ // not put up a new browser window. we should just let the usual processing
+ // take place.
+ try {
+ var extProtService = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ var scheme = href.substring(0, href.indexOf(":"));
+ if (!extProtService.isExposedProtocol(scheme))
+ return true;
+ }
+ catch (ex) {} // ignore errors, and just assume that we can proceed.
+
+ // if you get here, the user did a simple left click on a link
+ // that we know should be in a browser window.
+ // since we are in the message pane, send it to the top most browser window
+ // (or open one) right away, instead of waiting for us to get some data and
+ // determine the content type, and then open a browser window
+ // we want to preventDefault, so that in
+ // nsGenericHTMLElement::HandleDOMEventForAnchors(), we don't try to handle the click again
+ event.preventDefault();
+ if (isPhishingURL(ceParams.linkNode, false, href))
+ return false;
+
+ openAsExternal(href);
+ return true;
+}
+
+// We're going to implement our status feedback for the mail window in JS now.
+// the following contains the implementation of our status feedback object
+
+function nsMsgStatusFeedback()
+{
+}
+
+nsMsgStatusFeedback.prototype =
+{
+ // global variables for status / feedback information....
+ statusTextFld : null,
+ statusBar : null,
+ statusPanel : null,
+ throbber : null,
+ stopCmd : null,
+ startTimeoutID : null,
+ stopTimeoutID : null,
+ pendingStartRequests : 0,
+ meteorsSpinning : false,
+ myDefaultStatus : "",
+
+ ensureStatusFields : function()
+ {
+ if (!this.statusTextFld ) this.statusTextFld = document.getElementById("statusText");
+ if (!this.statusBar) this.statusBar = document.getElementById("statusbar-icon");
+ if (!this.statusPanel) this.statusPanel = document.getElementById("statusbar-progresspanel");
+ if (!this.throbber) this.throbber = document.getElementById("navigator-throbber");
+ if (!this.stopCmd) this.stopCmd = document.getElementById("cmd_stop");
+ },
+
+ // nsIXULBrowserWindow implementation
+ setJSStatus : function(status)
+ {
+ if (status.length > 0)
+ this.showStatusString(status);
+ },
+ setOverLink : function(link, context)
+ {
+ this.ensureStatusFields();
+ this.statusTextFld.label = link;
+ },
+
+ // Called before links are navigated to to allow us to retarget them if needed.
+ onBeforeLinkTraversal: function(aOriginalTarget, aLinkURI, aLinkNode, aIsAppTab)
+ {
+ return aOriginalTarget;
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIMsgStatusFeedback) ||
+ iid.equals(Ci.nsIXULBrowserWindow) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ // nsIMsgStatusFeedback implementation.
+ showStatusString : function(statusText)
+ {
+ this.ensureStatusFields();
+ if ( !statusText.length )
+ statusText = this.myDefaultStatus;
+ else
+ this.myDefaultStatus = "";
+ this.statusTextFld.label = statusText;
+ },
+ setStatusString : function(status)
+ {
+ if (status.length > 0)
+ {
+ this.myDefaultStatus = status;
+ this.statusTextFld.label = status;
+ }
+ },
+ _startMeteors : function()
+ {
+ this.ensureStatusFields();
+
+ this.meteorsSpinning = true;
+ this.startTimeoutID = null;
+
+ // Show progress meter
+ this.statusPanel.collapsed = false;
+
+ // Turn progress meter on.
+ this.statusBar.setAttribute("mode","undetermined");
+
+ // start the throbber
+ if (this.throbber)
+ this.throbber.setAttribute("busy", true);
+
+ //turn on stop button and menu
+ if (this.stopCmd)
+ this.stopCmd.removeAttribute("disabled");
+ },
+ startMeteors : function()
+ {
+ this.pendingStartRequests++;
+ // if we don't already have a start meteor timeout pending
+ // and the meteors aren't spinning, then kick off a start
+ if (!this.startTimeoutID && !this.meteorsSpinning && window.MsgStatusFeedback)
+ this.startTimeoutID = setTimeout('window.MsgStatusFeedback._startMeteors();', 500);
+
+ // since we are going to start up the throbber no sense in processing
+ // a stop timeout...
+ if (this.stopTimeoutID)
+ {
+ clearTimeout(this.stopTimeoutID);
+ this.stopTimeoutID = null;
+ }
+ },
+ _stopMeteors : function()
+ {
+ this.ensureStatusFields();
+ this.showStatusString(this.myDefaultStatus);
+
+ // stop the throbber
+ if (this.throbber)
+ this.throbber.setAttribute("busy", false);
+
+ // Turn progress meter off.
+ this.statusPanel.collapsed = true;
+ this.statusBar.setAttribute("mode","normal");
+ this.statusBar.value = 0; // be sure to clear the progress bar
+ this.statusBar.label = "";
+ if (this.stopCmd)
+ this.stopCmd.setAttribute("disabled", "true");
+
+ this.meteorsSpinning = false;
+ this.stopTimeoutID = null;
+ },
+ stopMeteors : function()
+ {
+ if (this.pendingStartRequests > 0)
+ this.pendingStartRequests--;
+
+ // if we are going to be starting the meteors, cancel the start
+ if (this.pendingStartRequests == 0 && this.startTimeoutID)
+ {
+ clearTimeout(this.startTimeoutID);
+ this.startTimeoutID = null;
+ }
+
+ // if we have no more pending starts and we don't have a stop timeout already in progress
+ // AND the meteors are currently running then fire a stop timeout to shut them down.
+ if (this.pendingStartRequests == 0 && !this.stopTimeoutID)
+ {
+ if (this.meteorsSpinning && window.MsgStatusFeedback)
+ this.stopTimeoutID = setTimeout('window.MsgStatusFeedback._stopMeteors();', 500);
+ }
+ },
+ showProgress : function(percentage)
+ {
+ this.ensureStatusFields();
+ if (percentage >= 0)
+ {
+ this.statusBar.setAttribute("mode", "normal");
+ this.statusBar.value = percentage;
+ this.statusBar.label = Math.round(percentage) + "%";
+ }
+ }
+}
+
+
+function nsMsgWindowCommands()
+{
+}
+
+nsMsgWindowCommands.prototype =
+{
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIMsgWindowCommands) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ selectFolder: function(folderUri)
+ {
+ gFolderTreeView.selectFolder(MailUtils.getFolderForURI(folderUri));
+ },
+
+ selectMessage: function(messageUri)
+ {
+ SelectMessage(messageUri);
+ },
+
+ clearMsgPane: function()
+ {
+ if (gDBView)
+ setTitleFromFolder(gDBView.msgFolder,null);
+ else
+ setTitleFromFolder(null,null);
+ ClearMessagePane();
+ }
+}
+
+function StopUrls()
+{
+ msgWindow.StopUrls();
+}
+
+function loadStartPage()
+{
+ try
+ {
+ gMessageNotificationBar.clearMsgNotifications();
+
+ var startpageenabled = Services.prefs.getBoolPref("mailnews.start_page.enabled");
+ if (startpageenabled)
+ {
+ var startpage = GetLocalizedStringPref("mailnews.start_page.url");
+ if (startpage)
+ {
+ GetMessagePaneFrame().location.href = startpage;
+ //dump("start message pane with: " + startpage + "\n");
+ ClearMessageSelection();
+ }
+ }
+ }
+ catch (ex)
+ {
+ dump("Error loading start page.\n");
+ return;
+ }
+}
+
+// Given the server, open the twisty and the set the selection
+// on inbox of that server.
+// prompt if offline.
+function OpenInboxForServer(server)
+{
+ ShowThreadPane();
+ gFolderTreeView.selectFolder(GetInboxFolder(server));
+
+ if (!Services.io.offline) {
+ if (server.type != "imap")
+ GetMessagesForInboxOnServer(server);
+ }
+ else if (DoGetNewMailWhenOffline()) {
+ GetMessagesForInboxOnServer(server);
+ }
+}
+
+function GetSearchSession()
+{
+ if (("gSearchSession" in top) && gSearchSession)
+ return gSearchSession;
+ else
+ return null;
+}
+
+function MailSetCharacterSet(aEvent)
+{
+ if (aEvent.target.hasAttribute("charset")) {
+ msgWindow.mailCharacterSet = aEvent.target.getAttribute("charset");
+ msgWindow.charsetOverride = true;
+ }
+ messenger.setDocumentCharset(msgWindow.mailCharacterSet);
+}
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);
+}
diff --git a/comm/suite/mailnews/content/mailWindowOverlay.xul b/comm/suite/mailnews/content/mailWindowOverlay.xul
new file mode 100644
index 0000000000..61a7b4ff65
--- /dev/null
+++ b/comm/suite/mailnews/content/mailWindowOverlay.xul
@@ -0,0 +1,1929 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSMIMEOverlay.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/bindings.css" type="text/css"?>
+
+
+<?xul-overlay href="chrome://communicator/content/charsetOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/viewZoomOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/viewApplyThemeOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/msgHdrViewOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/mailOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/mailKeysOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd">
+ %messengerDTD;
+ <!ENTITY % mailKeysDTD SYSTEM "chrome://messenger/locale/mailKeysOverlay.dtd">
+ %mailKeysDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % msgViewPickerDTD SYSTEM "chrome://messenger/locale/msgViewPickerOverlay.dtd">
+ %msgViewPickerDTD;
+ <!ENTITY % msgHdrViewPopupDTD SYSTEM "chrome://messenger/locale/msgHdrViewPopup.dtd">
+ %msgHdrViewPopupDTD;
+ <!ENTITY % contentAreaCommandsDTD SYSTEM "chrome://communicator/locale/contentAreaCommands.dtd">
+ %contentAreaCommandsDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % msgReadSMIMEDTD SYSTEM "chrome://messenger-smime/locale/msgReadSMIMEOverlay.dtd">
+ %msgReadSMIMEDTD;
+]>
+
+<overlay
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://messenger/content/mailCommands.js"/>
+<script src="chrome://messenger/content/junkCommands.js"/>
+<script src="chrome://messenger/content/mailWindowOverlay.js"/>
+<script src="chrome://messenger/content/msgViewPickerOverlay.js"/>
+<script src="chrome://messenger-newsblog/content/newsblogOverlay.js"/>
+<script src="chrome://messenger/content/mail-offline.js"/>
+<script src="chrome://communicator/content/findUtils.js"/>
+<script src="chrome://global/content/printUtils.js"/>
+<script src="chrome://messenger/content/folderDisplay.js"/>
+<script src="chrome://messenger-smime/content/msgReadSMIMEOverlay.js"/>
+<script>
+<![CDATA[
+ ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
+ ChromeUtils.import("resource:///modules/PlacesUIUtils.jsm");
+]]></script>
+
+<stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_offlinePrompts" src="chrome://messenger/locale/offline.properties"/>
+ <stringbundle id="bundle_read_smime"
+ src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/>
+ <stringbundle id="bundle_viewZoom"/>
+ <stringbundle id="bundle_viewApplyTheme"/>
+ <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
+</stringbundleset>
+
+<!-- Performance optimization...we include utilityOverlay.xul which defines some command sets
+ which are updated based on events like focus and select. We have our own custom events
+ which we use to optmize when we do command updating. To avoid unnecessary command updating,
+ we are going to override the events the global edit menu items and select edit menu items
+ are updated on with events of our own controlling.
+ -->
+
+<commandset id="globalEditMenuItems"
+ commandupdater="true"
+ events="create-menu-edit"
+ oncommandupdate="goUpdateGlobalEditMenuItems()"/>
+<commandset id="selectEditMenuItems"
+ commandupdater="true"
+ events="create-menu-edit"
+ oncommandupdate="goUpdateSelectEditMenuItems()"/>
+
+<!-- End command set merging -->
+
+<commandset id="mailDownloadCommands">
+ <command id="cmd_downloadFlagged" oncommand="goDoCommand('cmd_downloadFlagged')"/>
+ <command id="cmd_downloadSelected" oncommand="goDoCommand('cmd_downloadSelected')"/>
+</commandset>
+
+<commandset id="mailFileMenuItems"
+ commandupdater="true"
+ events="create-menu-file, message-header-pane"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+
+ <command id="cmd_getNewMessages" oncommand="goDoCommand('cmd_getNewMessages')" disabled="true"/>
+ <command id="cmd_open" oncommand="goDoCommand('cmd_open')"/>
+
+ <command id="cmd_emptyTrash" oncommand="goDoCommand('cmd_emptyTrash')" disabled="true"/>
+ <command id="cmd_compactFolder" oncommand="goDoCommand('cmd_compactFolder')" disabled="true"/>
+
+ <command id="cmd_printSetup" oncommand="goDoCommand('cmd_printSetup')" disabled="true"/>
+ <command id="cmd_print" oncommand="goDoCommand('cmd_print')" disabled="true"/>
+ <command id="cmd_printpreview" oncommand="goDoCommand('cmd_printpreview')" disabled="true"/>
+ <command id="cmd_saveAsFile" oncommand="goDoCommand('cmd_saveAsFile')" disabled="true"/>
+ <command id="cmd_saveAsTemplate" oncommand="goDoCommand('cmd_saveAsTemplate')" disabled="true"/>
+ <command id="cmd_getNextNMessages" oncommand="goDoCommand('cmd_getNextNMessages')" disabled="true"/>
+ <command id="cmd_renameFolder" oncommand="goDoCommand('cmd_renameFolder')" />
+ <command id="cmd_sendUnsentMsgs" oncommand="goDoCommand('cmd_sendUnsentMsgs')" />
+ <command id="cmd_subscribe" oncommand="goDoCommand('cmd_subscribe')" disabled="true"/>
+ <command id="cmd_synchronizeOffline" oncommand="goDoCommand('cmd_synchronizeOffline');" disabled="true"/>
+ <command id="cmd_settingsOffline" oncommand="goDoCommand('cmd_settingsOffline');" disabled="true"/>
+</commandset>
+
+<commandset id="mailCommands">
+ <command id="cmd_newNavigator"/>
+ <command id="cmd_newPrivateWindow"/>
+ <command id="cmd_newEditor"/>
+ <command id="cmd_createFilterFromPopup" oncommand="goDoCommand('cmd_createFilterFromPopup')"/>
+ <command id="cmd_pageSetup"/>
+</commandset>
+
+<commandset id="mailViewMenuItems"
+ commandupdater="true"
+ events="create-menu-view"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+
+ <command id="cmd_viewPageSource" oncommand="goDoCommand('cmd_viewPageSource')" disabled="true"/>
+ <command id="cmd_setFolderCharset" oncommand="goDoCommand('cmd_setFolderCharset')" />
+ <command id="cmd_reload" oncommand="goDoCommand('cmd_reload')" disabled="true"/>
+
+ <command id="cmd_expandAllThreads" oncommand="goDoCommand('cmd_expandAllThreads')" disabled="true"/>
+ <command id="cmd_collapseAllThreads" oncommand="goDoCommand('cmd_collapseAllThreads')" disabled="true"/>
+ <command id="cmd_viewAllMsgs" oncommand="goDoCommand('cmd_viewAllMsgs')" disabled="true"/>
+ <command id="cmd_viewUnreadMsgs" oncommand="goDoCommand('cmd_viewUnreadMsgs')" disabled="true"/>
+ <command id="cmd_viewThreadsWithUnread" oncommand="goDoCommand('cmd_viewThreadsWithUnread')" disabled="true"/>
+ <command id="cmd_viewWatchedThreadsWithUnread" oncommand="goDoCommand('cmd_viewWatchedThreadsWithUnread')" disabled="true"/>
+ <command id="cmd_viewIgnoredThreads" oncommand="goDoCommand('cmd_viewIgnoredThreads')" disabled="true"/>
+ <!-- Needed to support the Lightning Task filter See Bug 316916 -->
+ <command id="cmd_showQuickFilterBar" oncommand="goDoCommand('cmd_showQuickFilterBar');"/>
+ <commandset id="viewZoomCommands"/>
+ <command id="cmd_viewSecurityStatus" oncommand="showMessageReadSecurityInfo();" disabled="true"/>
+</commandset>
+
+<commandset id="mailEditMenuItems"
+ commandupdater="true"
+ events="create-menu-edit, message-header-pane"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+
+ <command id="cmd_undo"
+ valueDeleteMsg="&undoDeleteMsgCmd.label;"
+ valueMoveMsg="&undoMoveMsgCmd.label;"
+ valueCopyMsg="&undoCopyMsgCmd.label;"
+ valueUnmarkAllMsgs="&undoMarkAllCmd.label;"
+ valueDefault="&undoDefaultCmd.label;"/>
+ <command id="cmd_redo"
+ valueDeleteMsg="&redoDeleteMsgCmd.label;"
+ valueMoveMsg="&redoMoveMsgCmd.label;"
+ valueCopyMsg="&redoCopyMsgCmd.label;"
+ valueUnmarkAllMsgs="&redoMarkAllCmd.label;"
+ valueDefault="&redoDefaultCmd.label;"/>
+ <command id="cmd_cut"/>
+ <command id="cmd_copy"/>
+ <command id="cmd_paste"/>
+ <command id="cmd_delete"
+ valueFolder="&deleteFolderCmd.label;"
+ valueFolderAccessKey="&deleteFolderCmd.accesskey;"
+ valueNewsgroup="&unsubscribeNewsgroupCmd.label;"
+ valueNewsgroupAccessKey="&unsubscribeNewsgroupCmd.accesskey;"
+ valueMessage="&deleteMsgCmd.label;"
+ valueMessageAccessKey="&deleteMsgCmd.accesskey;"
+ valueIMAPDeletedMessage="&undeleteMsgCmd.label;"
+ valueIMAPDeletedMessageAccessKey="&undeleteMsgCmd.accesskey;"
+ valueMessages="&deleteMsgsCmd.label;"
+ valueMessagesAccessKey="&deleteMsgsCmd.accesskey;"
+ valueIMAPDeletedMessages="&undeleteMsgsCmd.label;"
+ valueIMAPDeletedMessagesAccessKey="&undeleteMsgsCmd.accesskey;"/>
+ <command id="cmd_selectAll"/>
+ <command id="cmd_selectThread" oncommand="goDoCommand('cmd_selectThread')"/>
+ <command id="cmd_selectFlagged" oncommand="goDoCommand('cmd_selectFlagged')"/>
+ <command id="cmd_properties" oncommand="goDoCommand('cmd_properties')"
+ valueNewsgroup="&folderPropsNewsgroupCmd.label;"
+ valueFolder="&folderPropsFolderCmd.label;"
+ valueGeneric="&folderPropsCmd.label;"/>
+ <command id="cmd_find" oncommand="goDoCommand('cmd_find')" disabled="true"/>
+ <command id="cmd_findNext"
+ oncommand="goDoCommand('cmd_findNext');"
+ disabled="true"/>
+ <command id="cmd_findPrev"
+ oncommand="goDoCommand('cmd_findPrev');"
+ disabled="true"/>
+ <command id="cmd_findTypeText"/>
+ <command id="cmd_findTypeLinks"/>
+ <command id="cmd_search" oncommand="goDoCommand('cmd_search');"/>
+ <command id="cmd_stop" oncommand="MsgStop();"/>
+</commandset>
+
+<commandset id="mailEditContextMenuItems">
+ <command id="cmd_copyLink"/>
+ <command id="cmd_copyImage"/>
+</commandset>
+
+<commandset id="mailGoMenuItems"
+ commandupdater="true"
+ events="create-menu-go"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+
+ <command id="cmd_nextMsg" oncommand="goDoCommand('cmd_nextMsg')" disabled="true"/>
+ <command id="cmd_nextUnreadMsg" oncommand="goDoCommand('cmd_nextUnreadMsg')" disabled="true"/>
+ <command id="cmd_nextFlaggedMsg" oncommand="goDoCommand('cmd_nextFlaggedMsg')" disabled="true"/>
+ <command id="cmd_nextUnreadThread" oncommand="goDoCommand('cmd_nextUnreadThread')" disabled="true"/>
+ <command id="cmd_previousMsg" oncommand="goDoCommand('cmd_previousMsg')" disabled="true"/>
+ <command id="cmd_previousUnreadMsg" oncommand="goDoCommand('cmd_previousUnreadMsg')" disabled="true"/>
+ <command id="cmd_previousFlaggedMsg" oncommand="goDoCommand('cmd_previousFlaggedMsg')" disabled="true"/>
+ <command id="cmd_goStartPage" oncommand="goDoCommand('cmd_goStartPage');"/>
+ <command id="cmd_goBack" oncommand="goDoCommand('cmd_goBack')" disabled="true"/>
+ <command id="cmd_goForward" oncommand="goDoCommand('cmd_goForward');" disabled="true"/>
+</commandset>
+
+<commandset id="mailMessageMenuItems"
+ commandupdater="true"
+ events="create-menu-message"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+ <command id="cmd_archive" oncommand="goDoCommand('cmd_archive')"/>
+ <command id="cmd_reply" oncommand="goDoCommand('cmd_reply')"/>
+ <command id="cmd_replyList" oncommand="goDoCommand('cmd_replyList')"/>
+ <command id="cmd_replyGroup" oncommand="goDoCommand('cmd_replyGroup')"/>
+ <command id="cmd_replySender" oncommand="goDoCommand('cmd_replySender')"/>
+ <command id="cmd_replyall" oncommand="goDoCommand('cmd_replyall')"/>
+ <command id="cmd_replySenderAndGroup" oncommand="goDoCommand('cmd_replySenderAndGroup')"/>
+ <command id="cmd_replyAllRecipients" oncommand="goDoCommand('cmd_replyAllRecipients')"/>
+ <command id="cmd_forward" oncommand="goDoCommand('cmd_forward')"/>
+ <command id="cmd_forwardInline" oncommand="goDoCommand('cmd_forwardInline')"/>
+ <command id="cmd_forwardAttachment" oncommand="goDoCommand('cmd_forwardAttachment')"/>
+ <command id="cmd_editAsNew" oncommand="MsgEditMessageAsNew(event);"/>
+ <command id="cmd_editDraftMsg" oncommand="MsgEditDraftMessage(event);"/>
+ <command id="cmd_newMsgFromTemplate"
+ oncommand="MsgNewMessageFromTemplate(event);"/>
+ <command id="cmd_editTemplateMsg" oncommand="MsgEditTemplateMessage(event);"/>
+ <command id="cmd_openMessage" oncommand="goDoCommand('cmd_openMessage')"/>
+ <command id="cmd_createFilterFromMenu" oncommand="goDoCommand('cmd_createFilterFromMenu')"/>
+ <command id="cmd_cancel" oncommand="goDoCommand('cmd_cancel')"/>
+ <command id="cmd_killThread" oncommand="goDoCommand('cmd_killThread')"/>
+ <command id="cmd_killSubthread" oncommand="goDoCommand('cmd_killSubthread')"/>
+ <command id="cmd_watchThread" oncommand="goDoCommand('cmd_watchThread')"/>
+</commandset>
+
+<commandset id="mailToolbarItems"
+ commandupdater="true"
+ events="mail-toolbar"
+ oncommandupdate="goUpdateMailMenuItems(this);
+ /* update cmd_delete manually to avoid a doubled id */
+ goUpdateCommand('cmd_delete');">
+ <command id="button_reply"/>
+ <command id="button_replyall"/>
+ <command id="button_forward"/>
+ <command id="button_delete"/>
+ <command id="button_mark"/>
+ <command id="button_getNewMessages"/>
+ <command id="button_print"/>
+ <command id="button_next"/>
+ <command id="button_goBack"/>
+ <command id="button_goForward"/>
+ <command id="button_file"/>
+ <command id="cmd_shiftDelete" oncommand="goDoCommand('cmd_shiftDelete');"/>
+ <command id="button_junk"/>
+ <command id="button_search"/>
+</commandset>
+
+
+<commandset id="mailGetMsgMenuItems"
+ commandupdater="true"
+ events="create-menu-getMsgToolbar,create-menu-file"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+
+ <command id="cmd_getMsgsForAuthAccounts"
+ oncommand="goDoCommand('cmd_getMsgsForAuthAccounts'); event.stopPropagation()"
+ disabled="true"/>
+</commandset>
+
+<commandset id="mailMarkMenuItems"
+ commandupdater="true"
+ events="create-menu-mark"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+
+ <command id="cmd_markAsRead" oncommand="goDoCommand('cmd_markAsRead'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_markAsUnread" oncommand="goDoCommand('cmd_markAsUnread'); event.stopPropagation();" disabled="true"/>
+ <command id="cmd_markAllRead" oncommand="goDoCommand('cmd_markAllRead'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_markThreadAsRead" oncommand="goDoCommand('cmd_markThreadAsRead'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_markReadByDate" oncommand="goDoCommand('cmd_markReadByDate');" disabled="true"/>
+ <command id="cmd_markAsFlagged" oncommand="goDoCommand('cmd_markAsFlagged'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_markAsJunk" oncommand="goDoCommand('cmd_markAsJunk'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_markAsNotJunk" oncommand="goDoCommand('cmd_markAsNotJunk'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_recalculateJunkScore" oncommand="goDoCommand('cmd_recalculateJunkScore');" disabled="true"/>
+ <command id="cmd_markAsShowRemote" oncommand="goDoCommand('cmd_markAsShowRemote'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_markAsNotPhish" oncommand="goDoCommand('cmd_markAsNotPhish'); event.stopPropagation()" disabled="true"/>
+ <command id="cmd_viewAllHeader"
+ oncommand="goDoCommand('cmd_viewAllHeader');"
+ disabled="true"/>
+ <command id="cmd_viewNormalHeader"
+ oncommand="goDoCommand('cmd_viewNormalHeader');"
+ disabled="true"/>
+</commandset>
+
+<commandset id="mailToolsMenuItems"
+ commandupdater="true"
+ events="create-menu-tasks"
+ oncommandupdate="goUpdateMailMenuItems(this)">
+ <command id="cmd_displayMsgFilters"
+ disabled="true"
+ oncommand="goDoCommand('cmd_displayMsgFilters');"/>
+ <command id="cmd_applyFilters" oncommand="goDoCommand('cmd_applyFilters');" disabled="true"/>
+ <command id="cmd_applyFiltersToSelection"
+ oncommand="goDoCommand('cmd_applyFiltersToSelection');"
+ disabled="true"
+ valueSelection="&filtersApplyToSelection.label;"
+ valueSelectionAccessKey="&filtersApplyToSelection.accesskey;"
+ valueMessage="&filtersApplyToMessage.label;"
+ valueMessageAccessKey="&filtersApplyToMessage.accesskey;"/>
+ <command id="cmd_runJunkControls" oncommand="goDoCommand('cmd_runJunkControls');" disabled="true"/>
+ <command id="cmd_deleteJunk" oncommand="goDoCommand('cmd_deleteJunk');" disabled="true"/>
+</commandset>
+
+<keyset id="mailKeys">
+ <key id="space" key=" " modifiers="shift any" oncommand="SpaceHit(event);"/>
+
+ <!-- File Menu -->
+ <key id="key_newTab"
+ key="&newTabCmd.key;"
+ modifiers="accel"
+ oncommand="MsgOpenNewTab();"/>
+ <key id="key_newNavigator"/>
+ <key id="key_newPrivateWindow"/>
+ <key id="key_newBlankPage"/>
+ <key id="key_close"/>
+ <!-- Edit Menu -->
+ <key id="key_undo"/>
+ <key id="key_redo"/>
+ <key id="key_cut"/>
+ <key id="key_copy"/>
+ <key id="key_paste"/>
+ <key id="key_selectThread" key="&selectThreadCmd.key;" oncommand="goDoCommand('cmd_selectThread');" modifiers="alt, shift"/>
+
+ <key id="key_markJunk" key="&markAsJunkCmd.key;" oncommand="goDoCommand('cmd_markAsJunk');"/>
+ <key id="key_markNotJunk" key="&markAsNotJunkCmd.key;" oncommand="goDoCommand('cmd_markAsNotJunk');"
+ modifiers="shift"/>
+ <key id="key_markShowRemote" key="&markAsShowRemoteCmd.key;" oncommand="goDoCommand('cmd_markAsShowRemote');"
+ modifiers="shift"/>
+ <key id="key_markNotPhish" key="&markAsNotPhishCmd.key;" oncommand="goDoCommand('cmd_markAsNotPhish');"
+ modifiers="shift"/>
+ <key id="key_markAllRead" key="&markAllReadCmd.key;" oncommand="goDoCommand('cmd_markAllRead');" modifiers="accel, shift"/>
+ <key id="key_markThreadAsRead" key="&markThreadAsReadCmd.key;" oncommand="goDoCommand('cmd_markThreadAsRead')"/>
+ <key id="key_markReadByDate" key="&markReadByDateCmd.key;" oncommand="goDoCommand('cmd_markReadByDate')"/>
+ <key id="key_nextMsg" key="&nextMsgCmd.key;" oncommand="goDoCommand('cmd_nextMsg')"/>
+ <key id="key_nextUnreadMsg" key="&nextUnreadMsgCmd.key;" oncommand="goDoCommand('cmd_nextUnreadMsg')"/>
+ <key id="key_expandAllThreads" key="&expandAllThreadsCmd.key;" oncommand="goDoCommand('cmd_expandAllThreads')"/>
+ <key key="&expandAllThreadsCmd.key;" modifiers="shift" oncommand="goDoCommand('cmd_expandAllThreads')"/>
+ <key id="key_collapseAllThreads" key="&collapseAllThreadsCmd.key;" oncommand="goDoCommand('cmd_collapseAllThreads')"/>
+ <key key="&collapseAllThreadsCmd.key;" modifiers="shift" oncommand="goDoCommand('cmd_collapseAllThreads')"/>
+ <key id="key_nextUnreadThread" key="&nextUnreadThread.key;" oncommand="goDoCommand('cmd_nextUnreadThread')"/>
+ <key id="key_previousMsg" key="&prevMsgCmd.key;" oncommand="goDoCommand('cmd_previousMsg')"/>
+ <key id="key_previousUnreadMsg" key="&prevUnreadMsgCmd.key;" oncommand="goDoCommand('cmd_previousUnreadMsg')"/>
+ <key id="key_archive" key="&archiveMsgCmd.key;" oncommand="goDoCommand('cmd_archive')" modifiers="shift"/>
+ <key id="key_goBack" key="&goBackCmd.commandKey;" oncommand="goDoCommand('cmd_goBack')"/>
+ <key id="key_goForward" key="&goForwardCmd.commandKey;" oncommand="goDoCommand('cmd_goForward');"/>
+ <key id="key_reply" key="&replyMsgCmd.key;" oncommand="goDoCommand('cmd_reply')" modifiers="accel"/>
+ <key id="key_replyall" key="&replyToAllMsgCmd.key;" oncommand="goDoCommand('cmd_replyall')" modifiers="accel, shift"/>
+ <key id="key_forward" key="&forwardMsgCmd.key;" oncommand="goDoCommand('cmd_forward')" modifiers="accel"/>
+ <key id="key_editAsNew"
+ key="&editAsNewMsgCmd.key;"
+ modifiers="accel"
+ oncommand="goDoCommand('cmd_editAsNew');"/>
+ <!-- for display on menus only -->
+ <key id="key_newMsgFromTemplate"
+ keycode="&newMsgFromTemplateCmd.keycode;"/>
+ <key id="key_watchThread" key="&watchThreadMenu.key;" oncommand="goDoCommand('cmd_watchThread')" />
+ <key id="key_killThread" key="&killThreadMenu.key;" oncommand="goDoCommand('cmd_killThread')" />
+ <key id="key_killSubthread" key="&killSubthreadMenu.key;" oncommand="goDoCommand('cmd_killSubthread')" modifiers="shift" />
+ <key id="key_print"/>
+ <key id="key_saveAsFile" key="&saveAsFileCmd.key;" oncommand="goDoCommand('cmd_saveAsFile')" modifiers="accel"/>
+ <key id="key_viewPageSource" key="&pageSourceCmd.key;" oncommand="goDoCommand('cmd_viewPageSource')" modifiers="accel"/>
+ <key id="key_getNewMessages" key="&getNewMsgCmd2.key;" oncommand="goDoCommand('cmd_getNewMessages')" modifiers="accel"/>
+ <key id="key_getAllNewMessages"
+ key="&getAllNewMsgCmd2.key;"
+ oncommand="goDoCommand('cmd_getMsgsForAuthAccounts');"
+ modifiers="accel, shift"/>
+ <keyset id="findKeys"/>
+ <key id="key_stop" keycode="VK_ESCAPE" command="cmd_stop"/>
+ <keyset id="viewZoomKeys"/>
+#ifndef XP_MACOSX
+ <key id="key_reload" keycode="VK_F5" oncommand="ReloadMessage();"/>
+#endif
+
+ <!-- View Toggle Keys -->
+#ifndef XP_MACOSX
+ <key id="key_toggleFolderPane"
+ keycode="VK_F9"
+ oncommand="MsgToggleFolderPane(true);"
+ observes="mailDisableKeys"/>
+#else
+ <key id="key_toggleFolderPane"
+ key="&toggleFolderPaneCmd.key;"
+ modifiers="accel,alt"
+ oncommand="MsgToggleFolderPane(true);"
+ observes="mailDisableKeys"/>
+#endif
+ <key id="key_toggleThreadPane"
+ keycode="VK_F8"
+ modifiers="shift"
+ oncommand="MsgToggleThreadPane();"
+ disabled="true"/>
+ <key id="key_toggleMessagePane"
+ keycode="VK_F8"
+ oncommand="MsgToggleMessagePane(true);"
+ disabled="true"/>
+
+ <key id="key_searchMail" key="&searchMailCmd.key;" oncommand="goDoCommand('cmd_search')" modifiers="accel, shift"/>
+
+ <key key="&focusSearchInput.key;"
+ modifiers="accel"
+ oncommand="focusElement(document.getElementById('searchInput'));"/>
+
+ <!-- Needed to support the Lightning Task filter See Bug 316916 -->
+ <key id="key_qfb_show"
+ key="&quickFilterBar.show.key2;"
+ modifiers="accel,shift"
+ command="cmd_showQuickFilterBar"/>
+</keyset>
+
+ <menupopup id="folderPaneContext"
+ onpopupshowing="return FillFolderPaneContextMenu();"
+ onpopuphiding="if (event.target == this) FolderPaneOnPopupHiding();">
+ <menuitem id="folderPaneContext-getMessages"
+ label="&folderContextGetMessages.label;"
+ accesskey="&folderContextGetMessages.accesskey;"
+ oncommand="MsgGetMessage();"/>
+ <menuitem id="folderPaneContext-openNewTab"
+ label="&folderContextOpenNewTab.label;"
+ accesskey="&folderContextOpenNewTab.accesskey;"
+ oncommand="FolderPaneContextMenuNewTab(event);"/>
+ <menuitem id="folderPaneContext-openNewWindow"
+ label="&folderContextOpenNewWindow.label;"
+ accesskey="&folderContextOpenNewWindow.accesskey;"
+ oncommand="MsgOpenNewWindowForFolder(null,-1);"/>
+ <menuitem id="folderPaneContext-searchMessages"
+ label="&folderContextSearchMessages.label;"
+ accesskey="&folderContextSearchMessages.accesskey;"
+ oncommand="gFolderTreeController.searchMessages();"/>
+ <menuitem id="folderPaneContext-subscribe"
+ label="&folderContextSubscribe.label;"
+ accesskey="&folderContextSubscribe.accesskey;"
+ oncommand="MsgSubscribe();"/>
+ <menuitem id="folderPaneContext-newsUnsubscribe"
+ label="&folderContextUnsubscribe.label;"
+ accesskey="&folderContextUnsubscribe.accesskey;"
+ oncommand="MsgUnsubscribe();"/>
+
+ <menuseparator id="folderPaneContext-sep1"/>
+
+ <menuitem id="folderPaneContext-new"
+ label="&folderContextNew.label;"
+ accesskey="&folderContextNew.accesskey;"
+ oncommand="gFolderTreeController.newFolder();"/>
+ <menuitem id="folderPaneContext-remove"
+ label="&folderContextRemove.label;"
+ accesskey="&folderContextRemove.accesskey;"
+ oncommand="gFolderTreeController.deleteFolder();"/>
+ <menuitem id="folderPaneContext-rename"
+ label="&folderContextRename.label;"
+ accesskey="&folderContextRename.accesskey;"
+ oncommand="gFolderTreeController.renameFolder();"/>
+
+ <menuitem id="folderPaneContext-compact"
+ label="&folderContextCompact.label;"
+ accesskey="&folderContextCompact.accesskey;"
+ oncommand="gFolderTreeController.compactFolders();"/>
+ <menuitem id="folderPaneContext-markMailFolderAllRead"
+ label="&folderContextMarkMailFolderRead.label;"
+ accesskey="&folderContextMarkMailFolderRead.accesskey;"
+ oncommand="MsgMarkAllRead();"/>
+ <menuitem id="folderPaneContext-markNewsgroupAllRead"
+ label="&folderContextMarkNewsgroupRead.label;"
+ accesskey="&folderContextMarkNewsgroupRead.accesskey;"
+ oncommand="MsgMarkAllRead();"/>
+ <menuitem id="folderPaneContext-emptyTrash"
+ label="&folderContextEmptyTrash.label;"
+ accesskey="&folderContextEmptyTrash.accesskey;"
+ oncommand="gFolderTreeController.emptyTrash();"/>
+ <menuitem id="folderPaneContext-emptyJunk"
+ label="&folderContextEmptyJunk.label;"
+ accesskey="&folderContextEmptyJunk.accesskey;"
+ oncommand="gFolderTreeController.emptyJunk();"/>
+ <menuitem id="folderPaneContext-sendUnsentMessages"
+ label="&folderContextSendUnsentMessages.label;"
+ accesskey="&folderContextSendUnsentMessages.accesskey;"
+ oncommand="goDoCommand('cmd_sendUnsentMsgs')"/>
+
+ <menuseparator id="folderPaneContext-sep-edit"/>
+
+ <menuitem id="folderPaneContext-favoriteFolder"
+ type="checkbox"
+ label="&folderContextFavoriteFolder.label;"
+ accesskey="&folderContextFavoriteFolder.accesskey;"
+ checked="false"
+ oncommand="ToggleFavoriteFolderFlag();"/>
+ <menuitem id="folderPaneContext-properties"
+ label="&folderContextProperties.label;"
+ accesskey="&folderContextProperties.accesskey;"
+ oncommand="gFolderTreeController.editFolder();"/>
+ <menuitem id="folderPaneContext-markAllFoldersRead"
+ label="&folderContextMarkAllFoldersRead.label;"
+ accesskey="&folderContextMarkAllFoldersRead.accesskey;"
+ oncommand="MsgMarkAllFoldersRead();"/>
+ <menuseparator id="folderPaneContext-sep4"/>
+ <menuitem id="folderPaneContext-settings"
+ label="&folderContextSettings.label;"
+ accesskey="&folderContextSettings.accesskey;"
+ oncommand="gFolderTreeController.editFolder();"/>
+ </menupopup>
+
+ <menupopup id="mailContext"
+ onpopupshowing="return FillMailContextMenu(this, event);"
+ onpopuphiding="MailContextOnPopupHiding(this, event);">
+ <menuitem id="context-openlinkintab"
+ label="&openLinkCmdInTab.label;"
+ accesskey="&openLinkCmdInTab.accesskey;"
+ usercontextid="0"
+ oncommand="gContextMenu.openLinkInTab(event);"/>
+ <menuitem id="context-openlink"
+ label="&openLinkCmd.label;"
+ accesskey="&openLinkCmd.accesskey;"
+ oncommand="gContextMenu.openLinkInWindow();"/>
+ <menuitem id="context-openlinkinprivatewindow"
+ label="&openLinkCmdInPrivateWindow.label;"
+ accesskey="&openLinkCmdInPrivateWindow.accesskey;"
+ oncommand="gContextMenu.openLinkInPrivateWindow();"/>
+ <menuseparator id="mailContext-sep-link"/>
+ <menuitem id="context-selectall"/>
+ <menuitem id="context-copy"/>
+ <menuitem id="context-searchselect"
+ oncommand="MsgOpenSearch(gContextMenu.searchSelected(), event);"/>
+ <menuitem id="mailContext-openNewTab"
+ label="&contextOpenNewTab.label;"
+ accesskey="&contextOpenNewTab.accesskey;"
+ oncommand="OpenMessageInNewTab(event);"/>
+ <menuitem id="mailContext-openNewWindow"
+ label="&contextOpenNewWindow.label;"
+ accesskey="&contextOpenNewWindow.accesskey;"
+ oncommand="MsgOpenNewWindowForMessage();"/>
+ <menuseparator id="mailContext-sep-open"/>
+ <menuitem id="mailContext-replySender"
+ label="&contextReplySender.label;"
+ accesskey="&contextReplySender.accesskey;"
+ oncommand="MsgReplySender(event);"/>
+ <menuitem id="mailContext-replyList"
+ label="&contextReplyList.label;"
+ accesskey="&contextReplyList.accesskey;"
+ oncommand="MsgReplyList(event);"/>
+ <menuitem id="mailContext-replyNewsgroup"
+ label="&contextReplyNewsgroup.label;"
+ accesskey="&contextReplyNewsgroup.accesskey;"
+ oncommand="MsgReplyGroup(event);"/>
+ <menuitem id="mailContext-replySenderAndNewsgroup"
+ label="&contextReplySenderAndNewsgroup.label;"
+ accesskey="&contextReplySenderAndNewsgroup.accesskey;"
+ oncommand="MsgReplyToSenderAndGroup(event);"/>
+ <menuitem id="mailContext-replyAll"
+ label="&contextReplyAll.label;"
+ accesskey="&contextReplyAll.accesskey;"
+ oncommand="MsgReplyToAllRecipients(event);"/>
+ <menuitem id="mailContext-forward"
+ label="&contextForward.label;"
+ accesskey="&contextForward.accesskey;"
+ oncommand="MsgForwardMessage(event);"/>
+ <menuitem id="mailContext-forwardAsAttachment"
+ label="&contextForwardAsAttachment.label;"
+ accesskey="&contextForwardAsAttachment.accesskey;"
+ oncommand="MsgForwardAsAttachment(event);"/>
+ <menuitem id="mailContext-editAsNew"
+ label="&contextEditMsgAsNew.label;"
+ accesskey="&contextEditMsgAsNew.accesskey;"
+ oncommand="MsgEditMessageAsNew(event);"/>
+ <menuitem id="mailContext-editDraftMsg"
+ label="&contextEditDraftMsg.label;"
+ default="true"
+ oncommand="MsgEditDraftMessage(event);"/>
+ <menuitem id="mailContext-newMsgFromTemplate"
+ label="&contextNewMsgFromTemplate.label;"
+ default="true"
+ oncommand="MsgNewMessageFromTemplate(event);"/>
+ <menuitem id="mailContext-editTemplateMsg"
+ label="&contextEditTemplate.label;"
+ accesskey="&contextEditTemplate.accesskey;"
+ oncommand="MsgEditTemplateMessage(event);"/>
+ <menuseparator id="mailContext-sep-tags"/>
+ <menu id="mailContext-tags"
+ label="&tagMenu.label;"
+ accesskey="&tagMenu.accesskey;">
+ <menupopup id="mailContext-tagpopup"
+ onpopupshowing="InitMessageTags(this)">
+ <menuitem id="mailContext-tagRemoveAll"
+ oncommand="RemoveAllMessageTags();"/>
+ <menuseparator id="mailContext-sep-afterTagRemoveAll"/>
+ <menuseparator id="mailContext-sep-beforeAddNewTag"/>
+ <menuitem id="mailContext-tagCustomize"
+ label="&tagCustomize.label;"
+ accesskey="&tagCustomize.accesskey;"
+ oncommand="goPreferences('tags_pane');"/>
+ </menupopup>
+ </menu>
+ <menu id="mailContext-mark"
+ label="&markMenu.label;"
+ accesskey="&markMenu.accesskey;">
+ <menupopup id="mailContext-markPopup"
+ onpopupshowing="InitMessageMark()">
+ <menuitem id="mailContext-markRead"
+ label="&markAsReadCmd.label;"
+ accesskey="&markAsReadCmd.accesskey;"
+ command="cmd_markAsRead"/>
+ <menuitem id="mailContext-markUnread"
+ label="&markAsUnreadCmd.label;"
+ accesskey="&markAsUnreadCmd.accesskey;"
+ command="cmd_markAsUnread"/>
+ <menuitem id="mailContext-markThreadAsRead"
+ label="&markThreadAsReadCmd.label;"
+ accesskey="&markThreadAsReadCmd.accesskey;"
+ command="cmd_markThreadAsRead"/>
+ <menuitem id="mailContext-markReadByDate"
+ label="&markReadByDateCmd.label;"
+ accesskey="&markReadByDateCmd.accesskey;"
+ command="cmd_markReadByDate"/>
+ <menuitem id="mailContext-markAllRead"
+ label="&markAllReadCmd.label;"
+ accesskey="&markAllReadCmd.accesskey;"
+ command="cmd_markAllRead"/>
+ <menuseparator id="mailContext-sep-afterMarkAllRead"/>
+ <menuitem id="mailContext-markFlagged"
+ type="checkbox"
+ label="&markFlaggedCmd.label;"
+ accesskey="&markFlaggedCmd.accesskey;"
+ command="cmd_markAsFlagged"/>
+ <menuseparator id="mailContext-sep-afterMarkFlagged"/>
+ <menuitem id="mailContext-markAsJunk"
+ label="&markAsJunkCmd.label;"
+ accesskey="&markAsJunkCmd.accesskey;"
+ command="cmd_markAsJunk"/>
+ <menuitem id="mailContext-markAsNotJunk"
+ label="&markAsNotJunkCmd.label;"
+ accesskey="&markAsNotJunkCmd.accesskey;"
+ command="cmd_markAsNotJunk"/>
+ <menuitem id="mailContext-recalculateJunkScore"
+ label="&recalculateJunkScoreCmd.label;"
+ accesskey="&recalculateJunkScoreCmd.accesskey;"
+ command="cmd_recalculateJunkScore"/>
+ <menuitem id="mailContext-markAsShowRemote"
+ label="&markAsShowRemoteCmd.label;"
+ accesskey="&markAsShowRemoteCmd.accesskey;"
+ command="cmd_markAsShowRemote"/>
+ <menuitem id="mailContext-markAsNotPhish"
+ label="&markAsNotPhishCmd.label;"
+ accesskey="&markAsNotPhishCmd.accesskey;"
+ command="cmd_markAsNotPhish"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="mailContext-sep-mark"/>
+ <menuitem id="mailContext-downloadflagged"
+ label="&downloadFlaggedCmd.label;"
+ accesskey="&downloadFlaggedCmd.accesskey;"
+ command="cmd_downloadFlagged"/>
+ <menuitem id="mailContext-downloadselected"
+ label="&downloadSelectedCmd.label;"
+ accesskey="&downloadSelectedCmd.accesskey;"
+ command="cmd_downloadSelected"/>
+ <menuseparator id="mailContext-sep-move"/>
+ <menuitem id="mailContext-copyMessageUrl"
+ label="&copyMessageLocation.label;"
+ accesskey="&copyMessageLocation.accesskey;"
+ oncommand="CopyMessageUrl()"/>
+ <menuitem id="mailContext-archive"
+ label="&contextArchive.label;"
+ accesskey="&contextArchive.accesskey;"
+ oncommand="MsgArchiveSelectedMessages(event);"/>
+ <menu id="mailContext-moveMenu"
+ label="&contextMoveMsgMenu.label;"
+ accesskey="&contextMoveMsgMenu.accesskey;"
+ oncommand="MsgMoveMessage(event.target._folder);">
+ <menupopup id="mailContext-fileHereMenu"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ showRecent="true"
+ recentLabel="&contextMoveCopyMsgRecentMenu.label;"
+ recentAccessKey="&contextMoveCopyMsgRecentMenu.accesskey;"
+ showFavorites="true"
+ favoritesLabel="&contextMoveCopyMsgFavoritesMenu.label;"
+ favoritesAccessKey="&contextMoveCopyMsgFavoritesMenu.accesskey;"/>
+ </menu>
+ <menu id="mailContext-copyMenu"
+ label="&contextCopyMsgMenu.label;"
+ accesskey="&contextCopyMsgMenu.accesskey;"
+ oncommand="MsgCopyMessage(event.target._folder);">
+ <menupopup id="mailContext-copyHereMenu"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ showRecent="true"
+ recentLabel="&contextMoveCopyMsgRecentMenu.label;"
+ recentAccessKey="&contextMoveCopyMsgRecentMenu.accesskey;"
+ showFavorites="true"
+ favoritesLabel="&contextMoveCopyMsgFavoritesMenu.label;"
+ favoritesAccessKey="&contextMoveCopyMsgFavoritesMenu.accesskey;"/>
+ </menu>
+ <menuitem id="mailContext-saveAs"
+ label="&contextSaveAs.label;"
+ accesskey="&contextSaveAs.accesskey;"
+ oncommand="MsgSaveAsFile();"/>
+ <menuitem id="mailContext-delete"
+ command="cmd_delete"/>
+ <menuseparator id="mailContext-sep-print"/>
+#ifndef XP_MACOSX
+ <menuitem id="mailContext-printpreview"
+ label="&contextPrintPreview.label;"
+ accesskey="&contextPrintPreview.accesskey;"
+ oncommand="PrintEnginePrintPreview();"/>
+#endif
+ <menuitem id="mailContext-print"
+ label="&contextPrint.label;"
+ accesskey="&contextPrint.accesskey;"
+ oncommand="PrintEnginePrint();"/>
+ <menuseparator id="mailContext-sep-edit"/>
+ <menuitem id="context-copylink"
+ label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ command="cmd_copyLink"/>
+ <menuitem id="context-copyimage"
+ label="&copyImageCmd.label;"
+ accesskey="&copyImageCmd.accesskey;"
+ command="cmd_copyImage"/>
+ <menuitem id="context-viewimage"
+ label="&viewImageCmd.label;"
+ accesskey="&viewImageCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia();"/>
+ <menuitem id="context-addemail"
+ label="&AddToAddressBook.label;"
+ accesskey="&AddToAddressBook.accesskey;"
+ oncommand="AddEmailToAddressBook(gContextMenu.getEmail(), gContextMenu.linkText());"/>
+ <menuseparator id="mailContext-sep-image"/>
+ <menuitem id="context-blockimage"
+ oncommand="gContextMenu.toggleImageBlocking(true);"/>
+ <menuitem id="context-unblockimage"
+ oncommand="gContextMenu.toggleImageBlocking(false);"/>
+ <menuseparator id="mailContext-sep-blockimage"/>
+ <menuitem id="context-composeemailto"
+ label="&SendMailTo.label;"
+ accesskey="&SendMailTo.accesskey;"
+ oncommand="SendMailTo(gContextMenu.getEmail(), event);"/>
+ <menuitem id="context-createfilterfrom"
+ label="&CreateFilterFrom.label;"
+ accesskey="&CreateFilterFrom.accesskey;"
+ oncommand="CreateFilterFromMail(gContextMenu.getEmail());"/>
+ <menuitem id="context-copyemail"
+ label="&copyEmailCmd.label;"
+ accesskey="&copyEmailCmd.accesskey;"
+ oncommand="gContextMenu.copyEmail();"/>
+ <menuseparator id="mailContext-sep-copy"/>
+ <menuitem id="context-savelink"
+ label="&saveLinkCmd.label;"
+ accesskey="&saveLinkCmd.accesskey;"
+ oncommand="gContextMenu.saveLink();"/>
+ <menuitem id="context-saveimage"
+ label="&saveImageCmd.label;"
+ accesskey="&saveImageCmd.accesskey;"
+ oncommand="gContextMenu.saveImage();"/>
+ <menuitem id="context-bookmarklink"
+ label="&bookmarkLinkCmd.label;"
+ accesskey="&bookmarkLinkCmd.accesskey;"
+ oncommand="PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(gContextMenu.linkURL),
+ gContextMenu.linkText());"/>
+ </menupopup>
+
+ <menupopup id="remoteContentOptions"
+ onpopupshowing="onRemoteContentOptionsShowing(event);"
+ oncommand="allowRemoteContentForURI(event.target);">
+ <menuitem id="remoteContentOptionAllowForMsg"
+ label="&remoteContentOptionsAllowForMsg.label;"
+ accesskey="&remoteContentOptionsAllowForMsg.accesskey;"
+ oncommand="LoadMsgWithRemoteContent();"/>
+ <menuseparator id="remoteContentSettingsMenuSeparator"/>
+ <menuitem id="editRemoteContentSettings"
+ label="&editRemoteContentSettings.label;"
+ accesskey="&editRemoteContentSettings.accesskey;"
+ oncommand="editRemoteContentSettings();"/>
+ </menupopup>
+
+ <toolbar type="menubar"
+ id="mail-toolbar-menubar2"
+ class="chromeclass-menubar"
+ persist="collapsed"
+ grippytooltiptext="&menuBar.tooltip;"
+ customizable="true"
+ defaultset="menubar-items"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items"
+ class="menubar-items"
+ align="center">
+ </toolbaritem>
+ </toolbar>
+
+<menubar id="mail-menubar">
+ <menu id="menu_File" >
+ <menupopup id="menu_FilePopup" onpopupshowing="file_init();">
+ <menu id="menu_New">
+ <menupopup id="menu_NewPopup" onpopupshowing="menu_new_init();">
+ <menuitem id="newNewMsgCmd"
+ label="&newNewMsgCmd.label;"
+ accesskey="&newNewMsgCmd.accesskey;"
+ key="key_newMessage"
+ oncommand="MsgNewMessage(null);"/>
+ <menuitem id="menu_newFolder"
+ label="&newFolderCmd.label;"
+ accesskey="&newFolderCmd.accesskey;"
+ oncommand="gFolderTreeController.newFolder();"/>
+ <menuitem id="menu_newVirtualFolder" label="&newVirtualFolderCmd.label;"
+ oncommand="gFolderTreeController.newVirtualFolder();"
+ accesskey="&newVirtualFolderCmd.accesskey;"/>
+ <menuitem id="newAccountMenuItem"
+ label="&newAccountCmd.label;"
+ accesskey="&newAccountCmd.accesskey;"
+ oncommand="MsgAccountWizard();"/>
+ <menuseparator id="newPopupMenuSeparator"/>
+ <menuitem id="menu_newCard"/>
+ <menuitem id="menu_newTab"
+ label="&newTabCmd.label;"
+ accesskey="&newTabCmd.accesskey;"
+ key="key_newTab"
+ oncommand="MsgOpenNewTab();"/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ <menuitem id="menu_newEditor"/>
+ </menupopup>
+ </menu>
+ <menuitem id="openMessageFileMenuitem" label="&openMessageFileCmd.label;"
+ key="key_openFileMessage"
+ accesskey="&openMessageFileCmd.accesskey;"
+ oncommand="MsgOpenFromFile();"/>
+ <menuitem id="menu_close"/>
+ <menuseparator id="fileMenuAfterCloseSeparator"/>
+ <menu id="menu_saveAs" label="&saveAsMenu.label;" accesskey="&saveAsMenu.accesskey;">
+ <menupopup id="menu_SavePopup">
+ <menuitem id="menu_saveAsFile"
+ label="&saveAsFileCmd.label;"
+ accesskey="&saveAsFileCmd.accesskey;"
+ key="key_saveAsFile"
+ command="cmd_saveAsFile"/>
+ <menuitem id="menu_saveAsTemplate"
+ label="&saveAsTemplateCmd.label;"
+ accesskey="&saveAsTemplateCmd.accesskey;"
+ command="cmd_saveAsTemplate"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="fileMenuAfterSaveSeparator"/>
+ <menuitem id="menu_getNewMsg"
+ label="&getNewMsgCmd.label;"
+ accesskey="&getNewMsgCmd.accesskey;"
+ key="key_getNewMessages"
+ command="cmd_getNewMessages"/>
+ <menu id="menu_getAllNewMsg"
+ label="&getNewMsgForCmd.label;"
+ accesskey="&getNewMsgForCmd.accesskey;"
+ oncommand="MsgGetMessagesForAccount();">
+ <menupopup id="menu_getAllNewMsgPopup"
+ type="folder"
+ mode="getMail"
+ expandFolders="false"
+ oncommand="MsgGetMessagesForAccount(event.target._folder); event.stopPropagation();">
+ <menuitem id="menu_getAllNewMsgPopupMenu"
+ label="&getAllNewMsgCmdPopupMenu.label;"
+ accesskey="&getAllNewMsgCmdPopupMenu.accesskey;"
+ key="key_getAllNewMessages"
+ command="cmd_getMsgsForAuthAccounts"/>
+ <menuseparator id="fileMenuAfterGetNewMsgSeparator"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_getnextnmsg" label="&getNextNMsgCmd.label;"
+ accesskey="&getNextNMsgCmd.accesskey;"
+ command="cmd_getNextNMessages"/>
+ <menuitem id="menu_sendunsentmsgs" label="&sendUnsentCmd.label;"
+ accesskey="&sendUnsentCmd.accesskey;"
+ command="cmd_sendUnsentMsgs"/>
+ <menuitem label="&subscribeCmd.label;"
+ accesskey="&subscribeCmd.accesskey;"
+ command="cmd_subscribe"/>
+ <menuseparator id="fileMenuAfterSubscribeSeparator"/>
+ <menuitem id="menu_renameFolder" label="&renameFolder.label;"
+ accesskey="&renameFolder.accesskey;"
+ command="cmd_renameFolder"
+ observes="mailHideMenus"/>
+ <menuitem id="menu_compactFolder" label="&compactFolders.label;"
+ accesskey="&compactFolders.accesskey;"
+ command="cmd_compactFolder"
+ observes="mailHideMenus"/>
+ <menuitem id="menu_emptyTrash" label="&emptyTrashCmd.label;"
+ accesskey="&emptyTrashCmd.accesskey;"
+ command="cmd_emptyTrash"
+ observes="mailHideMenus"/>
+ <menuseparator id="trashMenuSeparator" observes="mailHideMenus"/>
+ <menu id="menu_Offline"
+ label="&offlineMenu.label;"
+ accesskey="&offlineMenu.accesskey;">
+ <menupopup id="menu_OfflinePopup">
+ <menuitem id="offlineGoOfflineCmd"/>
+ <menuseparator id="offlineMenuAfterGoSeparator"/>
+ <menuitem id="menu_synchronizeOffline"
+ label="&synchronizeOfflineCmd.label;"
+ accesskey="&synchronizeOfflineCmd.accesskey;"
+ command="cmd_synchronizeOffline"/>
+ <menuitem id="menu_settingsOffline"
+ label="&settingsOfflineCmd.label;"
+ accesskey="&settingsOfflineCmd.accesskey;"
+ command="cmd_settingsOffline"/>
+ <menuseparator id="offlineMenuAfterSettingsSeparator"/>
+ <menuitem id="menu_downloadFlagged"
+ label="&downloadFlaggedCmd.label;"
+ accesskey="&downloadFlaggedCmd.accesskey;"
+ command="cmd_downloadFlagged"/>
+ <menuitem id="menu_downloadSelected"
+ label="&downloadSelectedCmd.label;"
+ accesskey="&downloadSelectedCmd.accesskey;"
+ command="cmd_downloadSelected"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="fileMenuAfterOfflineSeparator"/>
+ <menuitem id="menu_printSetup"/>
+ <menuitem id="menu_printPreview"/>
+ <menuitem id="menu_print"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_Edit" oncommand="CommandUpdate_UndoRedo();">
+ <menupopup id="menu_EditPopup" onpopupshowing="InitEditMessagesMenu()">
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator id="editMenuAfterRedoSeparator"/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_delete" command="cmd_delete"/>
+ <menuseparator id="editMenuAfterDeleteSeparator"/>
+ <menu id="menu_select" label="&selectMenu.label;"
+ accesskey="&selectMenu.accesskey;">
+ <menupopup id="menu_SelectPopup">
+ <menuitem id="menu_mailSelectAll"
+ label="&all.label;"
+ accesskey="&all.accesskey;" key="key_selectAll"
+ command="cmd_selectAll"/>
+ <menuseparator id="selectMenuSeparator"/>
+ <menuitem id="menu_selectThread"
+ label="&selectThreadCmd.label;"
+ accesskey="&selectThreadCmd.accesskey;" key="key_selectThread"
+ command="cmd_selectThread"/>
+ <menuitem id="menu_selectFlagged"
+ label="&selectFlaggedCmd.label;"
+ accesskey="&selectFlaggedCmd.accesskey;"
+ command="cmd_selectFlagged"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="editMenuAfterSelectSeparator"/>
+ <menuitem id="menu_find" label="&findCmd.label;"/>
+ <menuitem id="menu_findNext"/>
+ <menuitem id="menu_findPrev"/>
+ <menuseparator id="editMenuAfterFindSeparator"/>
+ <menuitem id="menu_findTypeLinks"/>
+ <menuitem id="menu_findTypeText"/>
+ <menuseparator id="editPropertiesSeparator"/>
+ <menuitem id="menu_favoriteFolder"
+ type="checkbox"
+ label="&menuFavoriteFolder.label;"
+ accesskey="&menuFavoriteFolder.accesskey;"
+ checked="false"
+ oncommand="ToggleFavoriteFolderFlag();"
+ observes="mailHideMenus"/>
+ <menuitem id="menu_properties" label="&folderPropsCmd.label;"
+ accesskey="&folderPropsCmd.accesskey;"
+ command="cmd_properties"
+ observes="mailHideMenus"/>
+ <menuitem id="menu_accountmgr"
+ label="&accountManagerCmd.label;"
+ accesskey="&accountManagerCmd.accesskey;"
+ oncommand="MsgAccountManager(null);"/>
+ <menuitem id="menu_preferences" oncommand="goPreferences('mailnews_pane')"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_View">
+ <menupopup id="menu_View_Popup" onpopupshowing="view_init()">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="onViewToolbarsPopupShowing(event)"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="menu_showTaskbar"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_MessagePaneLayout" label="&messagePaneLayoutStyle.label;"
+ accesskey="&messagePaneLayoutStyle.accesskey;" observes="mailHideMenus">
+ <menupopup id="view_layout_popup" onpopupshowing="InitViewLayoutStyleMenu(event)">
+ <menuitem id="messagePaneClassic" type="radio" label="&messagePaneClassic.label;" name="viewlayoutgroup"
+ accesskey="&messagePaneClassic.accesskey;" oncommand="ChangeMailLayout(kClassicMailLayout);"/>
+ <menuitem id="messagePaneWide" type="radio" label="&messagePaneWide.label;" name="viewlayoutgroup"
+ accesskey="&messagePaneWide.accesskey;" oncommand="ChangeMailLayout(kWideMailLayout);"/>
+ <menuitem id="messagePaneVertical" type="radio" label="&messagePaneVertical.label;" name="viewlayoutgroup"
+ accesskey="&messagePaneVertical.accesskey;" oncommand="ChangeMailLayout(kVerticalMailLayout);"/>
+ <menuseparator id="viewMenuAfterPaneVerticalSeparator"/>
+ <menuitem id="menu_showMessagePane"
+ type="checkbox"
+ label="&showMessagePaneCmd.label;"
+ accesskey="&showMessagePaneCmd.accesskey;"
+ key="key_toggleMessagePane"
+ oncommand="MsgToggleMessagePane(true);"
+ observes="mailHideMenus"/>
+ <menuitem id="menu_showThreadPane"
+ type="checkbox"
+ label="&showThreadPaneCmd.label;"
+ accesskey="&showThreadPaneCmd.accesskey;"
+ key="key_toggleThreadPane"
+ oncommand="MsgToggleThreadPane();"
+ observes="mailHideMenus"/>
+ <menuitem id="menu_showFolderPane"
+ type="checkbox"
+ label="&showFolderPaneCmd.label;"
+ accesskey="&showFolderPaneCmd.accesskey;"
+ key="key_toggleFolderPane"
+ oncommand="MsgToggleFolderPane(true);"
+ observes="mailHideMenus"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewMessagesMenuSeparator" observes="mailHideMenus"/>
+ <menu id="viewSortMenu" label="&sortMenu.label;"
+ accesskey="&sortMenu.accesskey;" observes="mailHideMenus">
+ <menupopup id="menu_viewSortPopup" onpopupshowing="InitViewSortByMenu()">
+ <menuitem id="sortByDateMenuitem" type="radio" name="sortby" label="&sortByDateCmd.label;" accesskey="&sortByDateCmd.accesskey;" oncommand="MsgSortThreadPane('byDate')"/>
+ <menuitem id="sortByReceivedMenuitem" type="radio" name="sortby" label="&sortByReceivedCmd.label;" accesskey="&sortByReceivedCmd.accesskey;" oncommand="MsgSortThreadPane('byReceived')"/>
+ <menuitem id="sortByFlagMenuitem" type="radio" name="sortby" label="&sortByFlagCmd.label;" accesskey="&sortByFlagCmd.accesskey;" oncommand="MsgSortThreadPane('byFlagged')"/>
+ <menuitem id="sortByOrderReceivedMenuitem" type="radio" name="sortby" label="&sortByOrderReceivedCmd.label;" accesskey="&sortByOrderReceivedCmd.accesskey;" oncommand="MsgSortThreadPane('byId')"/>
+ <menuitem id="sortByPriorityMenuitem" type="radio" name="sortby" label="&sortByPriorityCmd.label;" accesskey="&sortByPriorityCmd.accesskey;" oncommand="MsgSortThreadPane('byPriority')"/>
+ <menuitem id="sortByFromMenuitem" type="radio" name="sortby" label="&sortByFromCmd.label;" accesskey="&sortByFromCmd.accesskey;" oncommand="MsgSortThreadPane('byAuthor')"/>
+ <menuitem id="sortByRecipientMenuitem" type="radio" name="sortby" label="&sortByRecipientCmd.label;" accesskey="&sortByRecipientCmd.accesskey;" oncommand="MsgSortThreadPane('byRecipient')"/>
+ <menuitem id="sortBySizeMenuitem" type="radio" name="sortby" label="&sortBySizeCmd.label;" accesskey="&sortBySizeCmd.accesskey;" oncommand="MsgSortThreadPane('bySize')"/>
+ <menuitem id="sortByStatusMenuitem" type="radio" name="sortby" label="&sortByStatusCmd.label;" accesskey="&sortByStatusCmd.accesskey;" oncommand="MsgSortThreadPane('byStatus')"/>
+ <menuitem id="sortBySubjectMenuitem" type="radio" name="sortby" label="&sortBySubjectCmd.label;" accesskey="&sortBySubjectCmd.accesskey;" oncommand="MsgSortThreadPane('bySubject')"/>
+ <menuitem id="sortByUnreadMenuitem" type="radio" name="sortby" label="&sortByUnreadCmd.label;" accesskey="&sortByUnreadCmd.accesskey;" oncommand="MsgSortThreadPane('byUnread')"/>
+ <menuitem id="sortByTagsMenuitem" type="radio" name="sortby" label="&sortByTagsCmd.label;" accesskey="&sortByTagsCmd.accesskey;" oncommand="MsgSortThreadPane('byTags')"/>
+ <menuitem id="sortByJunkStatusMenuitem" type="radio" name="sortby" label="&sortByJunkStatusCmd.label;" accesskey="&sortByJunkStatusCmd.accesskey;" oncommand="MsgSortThreadPane('byJunkStatus')"/>
+ <menuitem id="sortByAttachmentsMenuitem" type="radio" name="sortby" label="&sortByAttachmentsCmd.label;" accesskey="&sortByAttachmentsCmd.accesskey;" oncommand="MsgSortThreadPane('byAttachments')"/>
+ <menuseparator id="sortAfterAttachmentSeparator"/>
+ <menuitem id="sortAscending" type="radio" name="sortdirection" label="&sortAscending.label;" accesskey="&sortAscending.accesskey;" oncommand="MsgSortAscending()"/>
+ <menuitem id="sortDescending" type="radio" name="sortdirection" label="&sortDescending.label;" accesskey="&sortDescending.accesskey;" oncommand="MsgSortDescending()"/>
+ <menuseparator id="sortAfterDescendingSeparator"/>
+ <menuitem id="sortThreaded" type="radio" name="threaded" label="&sortThreaded.label;" accesskey="&sortThreaded.accesskey;" oncommand="MsgSortThreaded();"/>
+ <menuitem id="sortUnthreaded" type="radio" name="threaded" label="&sortUnthreaded.label;" accesskey="&sortUnthreaded.accesskey;" oncommand="MsgSortUnthreaded();"/>
+ <menuitem id="groupBySort" type="radio" name="group" label="&groupBySort.label;" accesskey="&groupBySort.accesskey;" oncommand="MsgGroupBySort();"/>
+ </menupopup>
+ </menu>
+ <menu id="viewMessageViewMenu" label="&msgsMenu.label;" accesskey="&msgsMenu.accesskey;"
+ observes="mailHideMenus" oncommand="ViewChangeByMenuitem(event.target);">
+ <menupopup id="viewMessagePopup"
+ onpopupshowing="RefreshViewPopup(this);">
+ <menuitem id="viewMessageAll"
+ label="&viewAll.label;"
+ accesskey="&viewAll.accesskey;"
+ type="radio"
+ name="viewmessages"
+ value="0"/>
+ <menuitem id="viewMessageUnread"
+ label="&viewUnread.label;"
+ accesskey="&viewUnread.accesskey;"
+ type="radio"
+ name="viewmessages"
+ value="1"/>
+ <menuitem id="viewMessageNotDeleted"
+ label="&viewNotDeleted.label;"
+ accesskey="&viewNotDeleted.accesskey;"
+ type="radio"
+ name="viewmessages"
+ value="3"/>
+ <menuseparator id="messageViewAfterUnreadSeparator"/>
+ <menu id="viewMessageTags" label="&viewTags.label;" accesskey="&viewTags.accesskey;">
+ <menupopup id="viewMessageTagsPopup"
+ onpopupshowing="RefreshTagsPopup(this);"/>
+ </menu>
+ <menu id="viewMessageCustomViews" label="&viewCustomViews.label;" accesskey="&viewCustomViews.accesskey;">
+ <menupopup id="viewMessageCustomViewsPopup"
+ onpopupshowing="RefreshCustomViewsPopup(this);"/>
+ </menu>
+ <menuseparator id="messageViewAfterCustomSeparator"/>
+ <menuitem id="viewMessageVirtualFolder" value="7" label="&viewVirtualFolder.label;" accesskey="&viewVirtualFolder.accesskey;"/>
+ <menuitem id="viewMessageCustomize" value="8" label="&viewCustomizeView.label;" accesskey="&viewCustomizeView.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menu id="viewMessagesMenu" label="&threads.label;"
+ accesskey="&threads.accesskey;" observes="mailHideMenus">
+ <menupopup id="menu_ThreadsPopup" onpopupshowing="InitViewMessagesMenu()">
+ <menuitem id="viewAllMessagesMenuItem" type="radio" name="viewmessages" label="&allMsgsCmd.label;" accesskey="&allMsgsCmd.accesskey;" disabled="true" command="cmd_viewAllMsgs"/>
+ <menuitem id="viewUnreadMessagesMenuItem" type="radio" name="viewmessages" label="&unreadMsgsCmd.label;" accesskey="&unreadMsgsCmd.accesskey;" disabled="true" command="cmd_viewUnreadMsgs"/>
+ <menuitem id="viewThreadsWithUnreadMenuItem" type="radio" name="viewmessages" label="&threadsWithUnreadCmd.label;" accesskey="&threadsWithUnreadCmd.accesskey;" disabled="true" command="cmd_viewThreadsWithUnread"/>
+ <menuitem id="viewWatchedThreadsWithUnreadMenuItem" type="radio" name="viewmessages" label="&watchedThreadsWithUnreadCmd.label;" accesskey="&watchedThreadsWithUnreadCmd.accesskey;" disabled="true" command="cmd_viewWatchedThreadsWithUnread"/>
+ <menuseparator id="threadsAfterWatchedSeparator"/>
+ <menuitem id="viewIgnoredThreadsMenuItem" type="checkbox" label="&ignoredThreadsCmd.label;" disabled="true" command="cmd_viewIgnoredThreads" accesskey="&ignoredThreadsCmd.accesskey;"/>
+ <menuseparator id="threadsAfterIgnoredSeparator"/>
+ <menuitem label="&expandAllThreadsCmd.label;" accesskey="&expandAllThreadsCmd.accesskey;" key="key_expandAllThreads" disabled="true" command="cmd_expandAllThreads"/>
+ <menuitem label="&collapseAllThreadsCmd.label;" accesskey="&collapseAllThreadsCmd.accesskey;" key="key_collapseAllThreads" disabled="true" command="cmd_collapseAllThreads"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewAfterThreadsSeparator"/>
+ <menu id="viewheadersmenu" label="&headersMenu.label;" accesskey="&headersMenu.accesskey;">
+ <menupopup id="menu_HeadersPopup" onpopupshowing="InitViewHeadersMenu();">
+ <menuitem id="viewallheaders"
+ type="radio"
+ name="viewheadergroup"
+ label="&headersAllCmd.label;"
+ accesskey="&headersAllCmd.accesskey;"
+ command="cmd_viewAllHeader"/>
+ <menuitem id="viewnormalheaders"
+ type="radio"
+ name="viewheadergroup"
+ label="&headersNormalCmd.label;"
+ accesskey="&headersNormalCmd.accesskey;"
+ command="cmd_viewNormalHeader"/>
+ </menupopup>
+ </menu>
+ <menu id="viewBodyMenu" accesskey="&bodyMenu.accesskey;" label="&bodyMenu.label;">
+ <menupopup id="viewBodyPopMenu" onpopupshowing="InitViewBodyMenu()">
+ <menuitem id="bodyAllowHTML"
+ type="radio"
+ name="bodyPlaintextVsHTMLPref"
+ label="&bodyAllowHTML.label;"
+ accesskey="&bodyAllowHTML.accesskey;"
+ oncommand="MsgBodyAllowHTML()"/>
+ <menuitem id="bodySanitized"
+ type="radio"
+ name="bodyPlaintextVsHTMLPref"
+ label="&bodySanitized.label;"
+ accesskey="&bodySanitized.accesskey;"
+ oncommand="MsgBodySanitized()"/>
+ <menuitem id="bodyAsPlaintext"
+ type="radio"
+ name="bodyPlaintextVsHTMLPref"
+ label="&bodyAsPlaintext.label;"
+ accesskey="&bodyAsPlaintext.accesskey;"
+ oncommand="MsgBodyAsPlaintext()"/>
+ <menuitem id="bodyAllParts"
+ type="radio"
+ name="bodyPlaintextVsHTMLPref"
+ label="&bodyAllParts.label;"
+ accesskey="&bodyAllParts.accesskey;"
+ oncommand="MsgBodyAllParts();"/>
+ </menupopup>
+ </menu>
+ <menu id="viewFeedSummary"
+ label="&bodyMenuFeed.label;"
+ accesskey="&bodyMenuFeed.accesskey;">
+ <menupopup id="viewFeedSummaryPopupMenu"
+ onpopupshowing="InitViewBodyMenu()">
+ <menuitem id="bodyFeedSummaryAllowHTML"
+ type="radio"
+ name="viewFeedBodyHTMLGroup"
+ label="&bodyAllowHTML.label;"
+ accesskey="&bodyAllowHTML.accesskey;"
+ oncommand="MsgFeedBodyRenderPrefs(false, 0, 0)"/>
+ <menuitem id="bodyFeedSummarySanitized"
+ type="radio"
+ name="viewFeedBodyHTMLGroup"
+ label="&bodySanitized.label;"
+ accesskey="&bodySanitized.accesskey;"
+ oncommand="MsgFeedBodyRenderPrefs(false, 3, gDisallow_classes_no_html)"/>
+ <menuitem id="bodyFeedSummaryAsPlaintext"
+ type="radio"
+ name="viewFeedBodyHTMLGroup"
+ label="&bodyAsPlaintext.label;"
+ accesskey="&bodyAsPlaintext.accesskey;"
+ oncommand="MsgFeedBodyRenderPrefs(true, 1, gDisallow_classes_no_html)"/>
+ <menuseparator id="viewFeedSummarySeparator"/>
+ <menuitem id="bodyFeedGlobalWebPage"
+ type="radio"
+ name="viewFeedSummaryGroup"
+ label="&viewFeedWebPage.label;"
+ accesskey="&viewFeedWebPage.accesskey;"
+ oncommand="FeedMessageHandler.onSelectPref = 0"/>
+ <menuitem id="bodyFeedGlobalSummary"
+ type="radio"
+ name="viewFeedSummaryGroup"
+ label="&viewFeedSummary.label;"
+ accesskey="&viewFeedSummary.accesskey;"
+ oncommand="FeedMessageHandler.onSelectPref = 1"/>
+ <menuitem id="bodyFeedPerFolderPref"
+ type="radio"
+ name="viewFeedSummaryGroup"
+ label="&viewFeedSummaryFeedPropsPref.label;"
+ accesskey="&viewFeedSummaryFeedPropsPref.accesskey;"
+ oncommand="FeedMessageHandler.onSelectPref = 2"/>
+ </menupopup>
+ </menu>
+ <menuitem id="viewAttachmentsInlineMenuitem"
+ type="checkbox"
+ checked="true"
+ label="&viewAttachmentsInlineCmd.label;"
+ accesskey="&viewAttachmentsInlineCmd.accesskey;"
+ oncommand="ToggleInlineAttachment(event.target)"/>
+ <menuseparator id="viewAfterAttachmentsSeparator"/>
+ <menuitem id="stopMenuitem"
+ label="&stopCmd.label;"
+ accesskey="&stopCmd.accesskey;"
+ key="key_stop"
+ disabled="true"
+ command="cmd_stop"/>
+ <menuitem id="menu_Stop"
+ label="&reloadCmd.label;"
+ key="key_reload"
+ accesskey="&reloadCmd.accesskey;"
+ command="cmd_reload"/>
+ <menuseparator id="viewAfterStopSeparator"/>
+ <!-- overlayed from viewZoomOverlay.xul -->
+ <menu id="menu_zoom"/>
+ <menu id="charsetMenu"
+ onpopupshowing="UpdateCharsetMenu(msgWindow.mailCharacterSet, this);"
+ oncommand="MailSetCharacterSet(event);"/>
+ <menuseparator id="viewAfterCharsetSeparator"/>
+ <menuitem id="pageSourceMenuItem" label="&pageSourceCmd.label;" key="key_viewPageSource" accesskey="&pageSourceCmd.accesskey;" command="cmd_viewPageSource"/>
+ <menuitem id="appmenu_securityStatus"
+ label="&menu_securityStatus.label;"
+ accesskey="&menu_securityStatus.accesskey;"
+ command="cmd_viewSecurityStatus"/>
+ <menuseparator observes="mailHideMenus"/>
+ <!-- overlayed from viewApplyThemeOverlay.xul -->
+ <menu id="menu_viewApplyTheme" observes="mailHideMenus"/>
+ </menupopup>
+ </menu>
+
+ <menu id="goMenu" label="&goMenu.label;" accesskey="&goMenu.accesskey;">
+ <menupopup id="menu_GoPopup" onpopupshowing="InitGoMessagesMenu();">
+ <menu id="goNextMenu" label="&nextMenu.label;" accesskey="&nextMenu.accesskey;">
+ <menupopup id="menu_GoNextPopup">
+ <menuitem id="nextMsgMenuItem"
+ label="&nextMsgCmd.label;"
+ accesskey="&nextMsgCmd.accesskey;"
+ key="key_nextMsg"
+ command="cmd_nextMsg"/>
+ <menuitem id="nextUnreadMsgMenuItem"
+ label="&nextUnreadMsgCmd.label;"
+ accesskey="&nextUnreadMsgCmd.accesskey;"
+ key="key_nextUnreadMsg"
+ command="cmd_nextUnreadMsg"/>
+ <menuitem id="nextFlaggedMenuItem"
+ label="&nextFlaggedMsgCmd.label;"
+ accesskey="&nextFlaggedMsgCmd.accesskey;"
+ command="cmd_nextFlaggedMsg"/>
+ <menuseparator id="goNextAfterFlaggedSeparator"/>
+ <menuitem id="nextUnreadThreadMenuItem"
+ label="&nextUnreadThread.label;"
+ accesskey="&nextUnreadThread.accesskey;"
+ key="key_nextUnreadThread"
+ command="cmd_nextUnreadThread"/>
+ </menupopup>
+ </menu>
+ <menu id="goPreviousMenu" label="&prevMenu.label;" accesskey="&prevMenu.accesskey;">
+ <menupopup id="menu_GoPreviousPopup">
+ <menuitem id="prevMsgMenuItem"
+ label="&prevMsgCmd.label;"
+ accesskey="&prevMsgCmd.accesskey;"
+ key="key_previousMsg"
+ command="cmd_previousMsg"/>
+ <menuitem id="prevUnreadMsgMenuItem"
+ label="&prevUnreadMsgCmd.label;"
+ accesskey="&prevUnreadMsgCmd.accesskey;"
+ key="key_previousUnreadMsg"
+ command="cmd_previousUnreadMsg"/>
+ <menuitem id="prevFlaggedMenuItem"
+ label="&prevFlaggedMsgCmd.label;"
+ accesskey="&prevFlaggedMsgCmd.accesskey;"
+ command="cmd_previousFlaggedMsg"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_goBack"
+ label="&goBackCmd.label;"
+ accesskey="&goBackCmd.accesskey;"
+ key="key_goBack"
+ command="cmd_goBack"/>
+ <menuitem id="menu_goForward"
+ label="&goForwardCmd.label;"
+ accesskey="&goForwardCmd.accesskey;"
+ key="key_goForward"
+ command="cmd_goForward"/>
+ <menuseparator id="goNextAfterForwardSeparator" observes="mailHideMenus"/>
+ <menu id="goFolderMenu"
+ label="&folderMenu.label;"
+ accesskey="&folderMenu.accesskey;"
+ oncommand="SelectMsgFolder(event.target._folder);"
+ observes="mailHideMenus">
+ <menupopup id="menu_GoFolderPopup"
+ type="folder"
+ showFileHereLabel="true"
+ showRecent="true"
+ recentLabel="&contextMoveCopyMsgRecentMenu.label;"
+ recentAccessKey="&contextMoveCopyMsgRecentMenu.accesskey;"
+ showFavorites="true"
+ favoritesLabel="&contextMoveCopyMsgFavoritesMenu.label;"
+ favoritesAccessKey="&contextMoveCopyMsgFavoritesMenu.accesskey;"/>
+ </menu>
+ <menuseparator id="goFolderSeparator"/>
+ <menuitem id="goStartPage" label="&startPageCmd.label;"
+ accesskey="&startPageCmd.accesskey;" command="cmd_goStartPage"
+ observes="mailHideMenus"/>
+ <menuseparator id="goNextAfterStartPageSeparator" observes="mailHideMenus"/>
+ </menupopup>
+ </menu>
+
+ <menu id="messageMenu" label="&msgMenu.label;" accesskey="&msgMenu.accesskey;">
+ <menupopup id="messageMenuPopup" onpopupshowing="InitMessageMenu();">
+ <menuitem id="newMsgCmd"
+ label="&newMsgCmd.label;"
+ accesskey="&newMsgCmd.accesskey;"
+ key="key_newMessage"
+ oncommand="MsgNewMessage(null);"/>
+ <menuitem id="replyMainMenu"
+ label="&replyMsgCmd.label;"
+ accesskey="&replyMsgCmd.accesskey;"
+ key="key_reply"
+ command="cmd_reply"/>
+ <menuitem id="replyListMainMenu"
+ label="&replyListCmd.label;"
+ accesskey="&replyListCmd.accesskey;"
+ command="cmd_replyList"/>
+ <menuitem id="replyNewsgroupMainMenu"
+ label="&replyNewsgroupCmd.label;"
+ accesskey="&replyNewsgroupCmd.accesskey;"
+ key="key_reply"
+ command="cmd_replyGroup"/>
+ <menuitem id="replySenderMainMenu"
+ label="&replySenderCmd.label;"
+ accesskey="&replySenderCmd.accesskey;"
+ command="cmd_replySender"/>
+ <menuitem id="replyallMainMenu"
+ label="&replyToAllMsgCmd.label;"
+ accesskey="&replyToAllMsgCmd.accesskey;"
+ key="key_replyall"
+ command="cmd_replyall"/>
+ <menuitem id="replySenderAndNewsgroupMainMenu"
+ label="&replyToSenderAndNewsgroupCmd.label;"
+ accesskey="&replyToSenderAndNewsgroupCmd.accesskey;"
+ key="key_replyall" command="cmd_replySenderAndGroup"/>
+ <menuitem id="replyAllRecipientsMainMenu"
+ label="&replyToAllRecipientsCmd.label;"
+ accesskey="&replyToAllRecipientsCmd.accesskey;"
+ command="cmd_replyAllRecipients"/>
+ <menuitem id="menu_forwardMsg"
+ label="&forwardMsgCmd.label;"
+ accesskey="&forwardMsgCmd.accesskey;"
+ key="key_forward"
+ command="cmd_forward"/>
+ <menu id="forwardAsMenu" label="&forwardAsMenu.label;" accesskey="&forwardAsMenu.accesskey;">
+ <menupopup id="menu_forwardAsPopup">
+ <menuitem id="menu_forwardAsInline"
+ label="&forwardAsInline.label;"
+ accesskey="&forwardAsInline.accesskey;"
+ command="cmd_forwardInline"/>
+ <menuitem id="menu_forwardAsAttachment"
+ label="&forwardAsAttachmentCmd.label;"
+ accesskey="&forwardAsAttachmentCmd.accesskey;"
+ command="cmd_forwardAttachment"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_editMsgAsNew"
+ label="&editAsNewMsgCmd.label;"
+ accesskey="&editAsNewMsgCmd.accesskey;"
+ key="key_editAsNew"
+ command="cmd_editAsNew"/>
+ <menuitem id="menu_editDraftMsg"
+ label="&editDraftMsgCmd.label;"
+ accesskey="&editDraftMsgCmd.accesskey;"
+ command="cmd_editDraftMsg"/>
+ <menuitem id="menu_newMsgFromTemplate"
+ label="&newMsgFromTemplateCmd.label;"
+ key="key_newMsgFromTemplate"
+ command="cmd_newMsgFromTemplate"/>
+ <menuitem id="menu_editTemplate"
+ label="&editTemplateMsgCmd.label;"
+ accesskey="&editTemplateMsgCmd.accesskey;"
+ command="cmd_editTemplateMsg"/>
+ <menuitem id="openMessageWindowMenuitem"
+ label="&openMessageWindowCmd.label;"
+ command="cmd_openMessage"
+ accesskey="&openMessageWindowCmd.accesskey;"
+ key="key_openMessage" observes="mailHideMenus"/>
+ <menu id="openFeedMessage"
+ label="&openFeedMessage.label;"
+ accesskey="&openFeedMessage.accesskey;">
+ <menupopup id="menu_openFeedMessage">
+ <menuitem id="menu_openFeedWebPageInWindow"
+ type="radio"
+ name="openFeedGroup"
+ label="&openFeedWebPageInWindow.label;"
+ accesskey="&openFeedWebPageInWindow.accesskey;"
+ oncommand="FeedMessageHandler.onOpenPref = 0"/>
+ <menuitem id="menu_openFeedSummaryInWindow"
+ type="radio"
+ name="openFeedGroup"
+ label="&openFeedSummaryInWindow.label;"
+ accesskey="&openFeedSummaryInWindow.accesskey;"
+ oncommand="FeedMessageHandler.onOpenPref = 1"/>
+ <menuitem id="menu_openFeedWebPageInMessagePane"
+ type="radio"
+ name="openFeedGroup"
+ label="&openFeedWebPageInMP.label;"
+ accesskey="&openFeedWebPageInMP.accesskey;"
+ oncommand="FeedMessageHandler.onOpenPref = 2"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="messageAfterOpenMsgSeparator"/>
+ <menu id="msgAttachmentMenu" label="&openAttachmentCmd.label;"
+ accesskey="&openAttachmentCmd.accesskey;" disabled="true">
+ <menupopup id="attachmentMenuList" onpopupshowing="FillAttachmentListPopup(this);"/>
+ </menu>
+ <menuseparator id="messageAfterAttachmentMenuSeparator"/>
+ <menuitem id="archiveMainMenu"
+ label="&archiveMsgCmd.label;"
+ accesskey="&archiveMsgCmd.accesskey;"
+ key="key_archive"
+ command="cmd_archive"/>
+ <menu id="moveMenu"
+ label="&moveMsgToMenu.label;"
+ accesskey="&moveMsgToMenu.accesskey;"
+ oncommand="MsgMoveMessage(event.target._folder);">
+ <menupopup id="menu_MovePopup"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ showRecent="true"
+ recentLabel="&moveCopyMsgRecentMenu.label;"
+ recentAccessKey="&moveCopyMsgRecentMenu.accesskey;"
+ showFavorites="true"
+ favoritesLabel="&contextMoveCopyMsgFavoritesMenu.label;"
+ favoritesAccessKey="&contextMoveCopyMsgFavoritesMenu.accesskey;"/>
+ </menu>
+ <menu id="copyMenu"
+ label="&copyMsgToMenu.label;"
+ accesskey="&copyMsgToMenu.accesskey;"
+ oncommand="MsgCopyMessage(event.target._folder);">
+ <menupopup id="menu_copyPopup"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ showRecent="true"
+ recentLabel="&moveCopyMsgRecentMenu.label;"
+ recentAccessKey="&moveCopyMsgRecentMenu.accesskey;"
+ showFavorites="true"
+ favoritesLabel="&contextMoveCopyMsgFavoritesMenu.label;"
+ favoritesAccessKey="&contextMoveCopyMsgFavoritesMenu.accesskey;"/>
+ </menu>
+ <menu id="tagMenu" label="&tagMenu.label;" accesskey="&tagMenu.accesskey;">
+ <menupopup id="tagMenu-tagpopup" onpopupshowing="InitMessageTags(this)">
+ <menuitem id="tagMenu-tagRemoveAll" oncommand="RemoveAllMessageTags();"/>
+ <menuseparator id="tagMenuAfterRemoveSeparator"/>
+ <menuseparator id="tagMenuBeforeCustomizeSeparator"/>
+ <menuitem id="tagMenu-tagCustomize"
+ label="&tagCustomize.label;"
+ accesskey="&tagCustomize.accesskey;"
+ oncommand="goPreferences('tags_pane');"/>
+ </menupopup>
+ </menu>
+ <menu id="markMenu" label="&markMenu.label;" accesskey="&markMenu.accesskey;">
+ <menupopup id="menu_MarkPopup" onpopupshowing="InitMessageMark()">
+ <menuitem id="markReadMenuItem"
+ label="&markAsReadCmd.label;"
+ accesskey="&markAsReadCmd.accesskey;"
+ key="key_markAsRead"
+ command="cmd_markAsRead"/>
+ <menuitem id="markUnreadMenuItem"
+ label="&markAsUnreadCmd.label;"
+ accesskey="&markAsUnreadCmd.accesskey;"
+ key="key_markAsUnread"
+ command="cmd_markAsUnread"/>
+ <menuitem id="markThreadReadMenuItem"
+ label="&markThreadAsReadCmd.label;"
+ accesskey="&markThreadAsReadCmd.accesskey;"
+ key="key_markThreadAsRead"
+ command="cmd_markThreadAsRead"/>
+ <menuitem id="markReadByDateMenuItem"
+ label="&markReadByDateCmd.label;"
+ accesskey="&markReadByDateCmd.accesskey;"
+ key="key_markReadByDate"
+ command="cmd_markReadByDate"/>
+ <menuitem id="markAllReadMenuItem"
+ label="&markAllReadCmd.label;"
+ accesskey="&markAllReadCmd.accesskey;"
+ key="key_markAllRead"
+ command="cmd_markAllRead"/>
+ <menuseparator id="markMenuAfterAllReadSeparator"/>
+ <menuitem id="markFlaggedMenuItem"
+ type="checkbox"
+ label="&markFlaggedCmd.label;"
+ accesskey="&markFlaggedCmd.accesskey;"
+ key="key_toggleFlagged"
+ command="cmd_markAsFlagged"/>
+ <menuseparator id="markMenuAfterFlaggedSeparator"/>
+ <menuitem id="markAsJunkMenuItem"
+ label="&markAsJunkCmd.label;"
+ accesskey="&markAsJunkCmd.accesskey;"
+ key="key_markJunk"
+ command="cmd_markAsJunk"/>
+ <menuitem id="markAsNotJunkMenuItem"
+ label="&markAsNotJunkCmd.label;"
+ accesskey="&markAsNotJunkCmd.accesskey;"
+ key="key_markNotJunk"
+ command="cmd_markAsNotJunk"/>
+ <menuitem id="recalculateJunkScoreMenuItem"
+ label="&recalculateJunkScoreCmd.label;"
+ accesskey="&recalculateJunkScoreCmd.accesskey;"
+ command="cmd_recalculateJunkScore"/>
+ <menuitem id="markAsShowRemoteMenuitem"
+ label="&markAsShowRemoteCmd.label;"
+ accesskey="&markAsShowRemoteCmd.accesskey;"
+ key="key_markShowRemote"
+ command="cmd_markAsShowRemote"/>
+ <menuitem id="markAsNotPhishMenuItem"
+ label="&markAsNotPhishCmd.label;"
+ accesskey="&markAsNotPhishCmd.accesskey;"
+ key="key_markNotPhish"
+ command="cmd_markAsNotPhish"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="messageMenuAfterMarkSeparator"/>
+ <menuitem id="createFilter"
+ label="&createFilter.label;"
+ accesskey="&createFilter.accesskey;"
+ command="cmd_createFilterFromMenu"/>
+ <menuseparator id="threadItemsSeparator"/>
+ <menuitem id="menu_cancel"
+ label="&cancelNewsMsgCmd.label;"
+ accesskey="&cancelNewsMsgCmd.accesskey;"
+ command="cmd_cancel"/>
+ <menuitem id="killThread"
+ label="&killThreadMenu.label;"
+ accesskey="&killThreadMenu.accesskey;"
+ key="key_killThread" command="cmd_killThread"/>
+ <menuitem id="killSubthread"
+ label="&killSubthreadMenu.label;"
+ accesskey="&killSubthreadMenu.accesskey;"
+ key="key_killSubthread" command="cmd_killSubthread"/>
+ <menuitem id="watchThread"
+ label="&watchThreadMenu.label;"
+ accesskey="&watchThreadMenu.accesskey;"
+ key="key_watchThread" command="cmd_watchThread"/>
+ </menupopup>
+</menu>
+
+<menu id="tasksMenu">
+ <menupopup id="taskPopup" onpopupshowing="document.commandDispatcher.updateCommands('create-menu-tasks')">
+ <menuitem id="menu_SearchMail"
+ label="&searchMailCmd.label;"
+ key="key_searchMail"
+ accesskey="&searchMailCmd.accesskey;"
+ command="cmd_search"/>
+ <menuitem id="menu_SearchAddresses"
+ label="&searchAddressesCmd.label;"
+ accesskey="&searchAddressesCmd.accesskey;"
+ oncommand="MsgSearchAddresses()"/>
+ <menuseparator id="tasksMenuAfterAddressesSeparator"/>
+ <menuitem id="menu_Filters"
+ label="&filtersCmd.label;"
+ accesskey="&filtersCmd.accesskey;"
+ command="cmd_displayMsgFilters"/>
+ <menuitem id="applyFilters"
+ label="&filtersApply.label;"
+ accesskey="&filtersApply.accesskey;"
+ command="cmd_applyFilters"/>
+ <menuitem id="applyFiltersToSelection"
+ label="&filtersApplyToMessage.label;"
+ accesskey="&filtersApplyToMessage.accesskey;"
+ command="cmd_applyFiltersToSelection"/>
+ <menuseparator id="tasksMenuAfterApplySeparator"/>
+ <menuitem id="runJunkControls"
+ label="&runJunkControls.label;"
+ accesskey="&runJunkControls.accesskey;"
+ command="cmd_runJunkControls"/>
+ <menuitem id="deleteJunk"
+ label="&deleteJunk.label;"
+ accesskey="&deleteJunk.accesskey;"
+ command="cmd_deleteJunk"/>
+ <menuseparator id="tasksMenuAfterDeleteSeparator"/>
+ <menuitem id="menu_import"
+ label="&importCmd.label;"
+ accesskey="&importCmd.accesskey;"
+ oncommand="toImport();"/>
+ <menuseparator/>
+ </menupopup>
+</menu>
+<menu id="windowMenu"/>
+<menu id="menu_Help"/>
+<spacer flex="100%"/>
+</menubar>
+
+<toolbox id="mail-toolbox"
+ mode="full"
+ defaultmode="full">
+ <toolbar class="toolbar-primary chromeclass-toolbar"
+ id="msgToolbar"
+ persist="collapsed"
+ grippytooltiptext="&mailToolbar.tooltip;"
+ toolbarname="&showMessengerToolbarCmd.label;"
+ accesskey="&showMessengerToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="button-getmsg,button-newmsg,separator,button-reply,button-replyall,button-forward,separator,button-goback,button-goforward,button-next,button-junk,button-delete,button-mark,spring,throbber-box"
+ context="toolbar-context-menu">
+ </toolbar>
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbarpalette id="MailToolbarPalette">
+ <toolbarbutton id="button-getmsg"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&getMsgButton.label;"
+ tooltiptext="&getMsgButton.tooltip;"
+ observes="button_getNewMessages"
+ oncommand="MsgGetMessagesForAccount();">
+ <menupopup id="button-getMsgPopup"
+ type="folder"
+ mode="getMail"
+ expandFolders="false"
+ onpopupshowing="getMsgToolbarMenu_init();"
+ oncommand="MsgGetMessagesForAccount(event.target._folder); event.stopPropagation();">
+ <menuitem id="button-getAllNewMsg"
+ label="&getAllNewMsgCmd.label;"
+ accesskey="&getAllNewMsgCmd.accesskey;"
+ command="cmd_getMsgsForAuthAccounts"/>
+ <menuseparator id="button-getAllNewMsgSeparator"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="button-newmsg"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&newMsgButton.label;"
+ tooltiptext="&newMsgButton.tooltip;"
+ oncommand="MsgNewMessage(event)">
+ <menupopup id="button-newMsgPopup"
+ onpopupshowing="InitNewMsgMenu(this);">
+ <menuitem id="button-newMsgHTML"
+ label="&newHTMLMessageCmd.label;"
+ accesskey="&newHTMLMessageCmd.accesskey;"
+ mode="HTML"/>
+ <menuitem id="button-newMsgPlain"
+ label="&newPlainTextMessageCmd.label;"
+ accesskey="&newPlainTextMessageCmd.accesskey;"
+ mode="PlainText"/>
+ <menuitem id="newMsgButton-mail-menuitem" hidden="true"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="button-reply"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&replyButton.label;"
+ tooltiptext="&replyButton.tooltip;"
+ observes="button_reply"
+ oncommand="MsgReplyMessage(event)">
+ <menupopup id="button-replyPopup"
+ onpopupshowing="InitMessageReply(this);">
+ <menuitem label="&replyMsgCmd.label;"
+ accesskey="&replyMsgCmd.accesskey;"
+ command="cmd_reply"
+ default="true"/>
+ <menuitem label="&replyListCmd.label;"
+ accesskey="&replyListCmd.accesskey;"
+ command="cmd_replyList"/>
+ <menuitem label="&replyNewsgroupCmd.label;"
+ accesskey="&replyNewsgroupCmd.accesskey;"
+ command="cmd_replyGroup"
+ default="true"/>
+ <menuitem label="&replySenderCmd.label;"
+ accesskey="&replySenderCmd.accesskey;"
+ command="cmd_replySender"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="button-replyall"
+ class="toolbarbutton-1"
+ label="&replyAllButton.label;"
+ tooltiptext="&replyAllButton.tooltip;"
+ tooltiptextmail="&replyAllButton.tooltip;"
+ tooltiptextnews="&replyAllButtonNews.tooltip;"
+ observes="button_replyall"
+ oncommand="MsgReplyToAllMessage(event)">
+ <menupopup id="button-replyallPopup">
+ <menuitem label="&replyToSenderAndNewsgroupCmd.label;"
+ accesskey="&replyToSenderAndNewsgroupCmd.accesskey;"
+ command="cmd_replySenderAndGroup"
+ default="true"/>
+ <menuitem label="&replyToAllRecipientsCmd.label;"
+ accesskey="&replyToAllRecipientsCmd.accesskey;"
+ command="cmd_replyAllRecipients"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="button-forward"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&forwardButton.label;"
+ tooltiptext="&forwardButton.tooltip;"
+ observes="button_forward"
+ oncommand="MsgForwardMessage(event)">
+ <menupopup id="button-forwardPopup"
+ onpopupshowing="InitMessageForward(this);">
+ <menuitem label="&forwardAsInline.label;"
+ accesskey="&forwardAsInline.accesskey;"
+ command="cmd_forwardInline"/>
+ <menuitem label="&forwardAsAttachmentCmd.label;"
+ accesskey="&forwardAsAttachmentCmd.accesskey;"
+ command="cmd_forwardAttachment"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="button-file"
+ type="menu"
+ class="toolbarbutton-1"
+ label="&fileButton.label;"
+ observes="button_file"
+ tooltiptext="&fileButton.tooltip;"
+ oncommand="MsgMoveMessage(event.target._folder);">
+ <menupopup id="button-filePopup"
+ type="folder"
+ mode="filing"
+ showRecent="true"
+ showFileHereLabel="true"
+ recentLabel="&moveCopyMsgRecentMenu.label;"
+ recentAccessKey="&moveCopyMsgRecentMenu.accesskey;"
+ showFavorites="true"
+ favoritesLabel="&contextMoveCopyMsgFavoritesMenu.label;"
+ favoritesAccessKey="&contextMoveCopyMsgFavoritesMenu.accesskey;"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-goback"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&goBackButton.label;"
+ tooltiptext="&goBackButton.tooltip;"
+ observes="button_goBack"
+ oncommand="goDoCommand('cmd_goBack')">
+ <menupopup id="button-goBackPopup"
+ onpopupshowing="InitBackToolbarMenu(this)"
+ oncommand="NavigateToUri(event.target);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-goforward"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&goForwardButton.label;"
+ tooltiptext="&goForwardButton.tooltip;"
+ observes="button_goForward"
+ oncommand="goDoCommand('cmd_goForward')">
+ <menupopup id="button-goForwardPopup"
+ onpopupshowing="InitForwardToolbarMenu(this)"
+ oncommand="NavigateToUri(event.target);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-next"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&nextButton.label;"
+ tooltiptext="&nextButton.tooltip;"
+ observes="button_next"
+ oncommand="goDoCommand('button_next')">
+ <menupopup id="button-nextPopup"
+ onpopupshowing="InitGoMessagesMenu();">
+ <menuitem label="&nextMsgCmd.label;"
+ accesskey="&nextMsgCmd.accesskey;"
+ command="cmd_nextMsg"/>
+ <menuitem label="&nextUnreadMsgCmd.label;"
+ accesskey="&nextUnreadMsgCmd.accesskey;"
+ command="cmd_nextUnreadMsg" default="true"/>
+ <menuitem label="&nextFlaggedMsgCmd.label;"
+ accesskey="&nextFlaggedMsgCmd.accesskey;"
+ command="cmd_nextFlaggedMsg"/>
+ <menuseparator/>
+ <menuitem label="&nextUnreadThread.label;"
+ accesskey="&nextUnreadThread.accesskey;"
+ command="cmd_nextUnreadThread"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbaritem id="button-junk"
+ title="&junkButton.label;"
+ observes="button_junk">
+ <deck id="junk-deck"
+ oncommand="goDoCommand('button_junk')">
+ <toolbarbutton id="button-isJunk"
+ class="toolbarbutton-1"
+ label="&junkButton.label;"
+ tooltiptext="&junkButton.tooltip;"
+ observes="button-junk"/>
+ <toolbarbutton id="button-notJunk"
+ class="toolbarbutton-1"
+ label="&notJunkButton.label;"
+ tooltiptext="&notJunkButton.tooltip;"
+ observes="button-junk"/>
+ </deck>
+ </toolbaritem>
+
+ <toolbaritem id="button-delete"
+ title="&deleteButton.label;"
+ observes="button_delete">
+ <deck id="delete-deck">
+ <toolbarbutton id="button-mark-deleted"
+ class="toolbarbutton-1"
+ label="&deleteButton.label;"
+ tooltiptext="&deleteButton.tooltip;"
+ observes="button-delete"
+ oncommand="goDoCommand(event.shiftKey ? 'button_shiftDelete' : 'button_delete')"/>
+ <toolbarbutton id="button-mark-undelete"
+ class="toolbarbutton-1"
+ label="&undeleteButton.label;"
+ tooltiptext="&undeleteButton.tooltip;"
+ observes="button-delete"
+ oncommand="goDoCommand('button_delete')"/>
+ </deck>
+ </toolbaritem>
+
+ <toolbarbutton id="button-mark"
+ class="toolbarbutton-1"
+ type="menu-button"
+ label="&markButton.label;"
+ oncommand="goDoCommand('button_mark')"
+ observes="button_mark" tooltiptext="&markButton.tooltip;">
+ <menupopup id="button-markPopup"
+ onpopupshowing="InitMessageMark()">
+ <menuitem id="markReadToolbarItem"
+ label="&markAsReadCmd.label;"
+ accesskey="&markAsReadCmd.accesskey;"
+ command="cmd_markAsRead"/>
+ <menuitem id="markUnreadToolbarItem"
+ label="&markAsUnreadCmd.label;"
+ accesskey="&markAsUnreadCmd.accesskey;"
+ command="cmd_markAsUnread"/>
+ <menuitem id="button-markThreadAsRead"
+ label="&markThreadAsReadCmd.label;"
+ accesskey="&markThreadAsReadCmd.accesskey;"
+ command="cmd_markThreadAsRead"/>
+ <menuitem id="button-markReadByDate"
+ label="&markReadByDateCmd.label;"
+ accesskey="&markReadByDateCmd.accesskey;"
+ command="cmd_markReadByDate"/>
+ <menuitem id="button-markAllRead"
+ label="&markAllReadCmd.label;"
+ accesskey="&markAllReadCmd.accesskey;"
+ command="cmd_markAllRead"/>
+ <menuseparator id="button-markAllReadSeparator"/>
+ <menuitem id="markFlaggedToolbarItem"
+ type="checkbox"
+ label="&markFlaggedCmd.label;"
+ accesskey="&markFlaggedCmd.accesskey;"
+ command="cmd_markAsFlagged"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="print-button"
+ label="&printButton.label;"
+ tooltiptext="&printButton.tooltip;"
+ observes="button_print"/>
+ <toolbarbutton id="button-stop"
+ class="toolbarbutton-1"
+ label="&stopButton.label;"
+ tooltiptext="&stopButton.tooltip;"
+ command="cmd_stop"/>
+ <toolbaritem id="button-search-container"
+ title="&searchButton.title;"
+ align="center"
+ class="toolbaritem-noline chromeclass-toolbar-additional">
+ <button id="button-search"
+ label="&searchButton.label;"
+ accesskey="&searchButton.accesskey;"
+ tooltiptext="&advancedButton.tooltip;"
+ observes="button_search"
+ oncommand="goDoCommand('button_search')"/>
+ <button id="button-advanced"
+ label="&advancedButton.label;"
+ accesskey="&advancedButton.accesskey;"
+ tooltiptext="&advancedButton.tooltip;"
+ observes="button_search"
+ oncommand="goDoCommand('button_search')"/>
+ </toolbaritem>
+ <toolbaritem id="throbber-box"/>
+ <!-- see utilityOverlay.xul
+ <toolbarbutton id="sync-button"/> -->
+ </toolbarpalette>
+
+</toolbox>
+
+<statusbar id="status-bar"
+ class="chromeclass-status" >
+ <statusbarpanel id="component-bar"/>
+ <statusbarpanel id="statusText"
+ label="&statusText.label;"
+ crop="right"
+ flex="1"/>
+ <statusbarpanel id="statusbar-progresspanel"
+ class="statusbarpanel-progress"
+ collapsed="true">
+ <progressmeter id="statusbar-icon"
+ class="progressmeter-statusbar"
+ mode="normal"
+ value="0"/>
+ </statusbarpanel>
+ <statusbarpanel id="unreadMessageCount"
+ hidden="true"/>
+ <statusbarpanel id="totalMessageCount"
+ hidden="true"/>
+ <statusbarpanel id="signed-status"
+ class="statusbarpanel-iconic"
+ collapsed="true"
+ oncommand="showMessageReadSecurityInfo();"/>
+ <statusbarpanel id="encrypted-status"
+ class="statusbarpanel-iconic"
+ collapsed="true"
+ oncommand="showMessageReadSecurityInfo();"/>
+ <statusbarpanel id="offline-status"
+ class="statusbarpanel-iconic"
+ checkfunc="MailCheckBeforeOfflineChange();" />
+</statusbar>
+
+</overlay>
diff --git a/comm/suite/mailnews/content/messageWindow.js b/comm/suite/mailnews/content/messageWindow.js
new file mode 100644
index 0000000000..fd61c6737f
--- /dev/null
+++ b/comm/suite/mailnews/content/messageWindow.js
@@ -0,0 +1,1044 @@
+/* -*- 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/. */
+
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+
+/* This is where functions related to the standalone message window are kept */
+
+// from MailNewsTypes.h
+const nsMsgKey_None = 0xFFFFFFFF;
+const nsMsgViewIndex_None = 0xFFFFFFFF;
+
+/* globals for a particular window */
+
+var gCurrentMessageUri;
+var gCurrentFolderUri;
+var gThreadPaneCommandUpdater = null;
+var gCurrentMessageIsDeleted = false;
+var gNextMessageViewIndexAfterDelete = -2;
+var gCurrentFolderToRerootForStandAlone;
+var gRerootOnFolderLoadForStandAlone = false;
+var gNextMessageAfterLoad = null;
+
+// the folderListener object
+var folderListener = {
+ onFolderAdded: function(parentFolder, child) {},
+ onMessageAdded: function(parentFolder, msg) {},
+ onFolderRemoved: function(parentFolder, child) {},
+ onMessageRemoved: function(parentFolder, msg)
+ {
+ if (parentFolder.URI != gCurrentFolderUri)
+ return;
+ if (extractMsgKeyFromURI() == msg.messageKey)
+ gCurrentMessageIsDeleted = true;
+ },
+
+ onFolderPropertyChanged: function(item, property, oldValue, newValue) {},
+ onFolderIntPropertyChanged: function(item, property, oldValue, newValue) {
+ if (item.URI == gCurrentFolderUri) {
+ if (property == "TotalMessages" || property == "TotalUnreadMessages") {
+ UpdateStandAloneMessageCounts();
+ }
+ }
+ },
+ onFolderBoolPropertyChanged: function(item, property, oldValue, newValue) {},
+ onFolderUnicharPropertyChanged: function(item, property, oldValue, newValue){},
+ onFolderPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+
+ onFolderEvent: function(folder, event) {
+ if (event == "DeleteOrMoveMsgCompleted")
+ HandleDeleteOrMoveMsgCompleted(folder);
+ else if (event == "DeleteOrMoveMsgFailed")
+ HandleDeleteOrMoveMsgFailed(folder);
+ else if (event == "FolderLoaded") {
+ if (folder) {
+ var uri = folder.URI;
+ if (uri == gCurrentFolderToRerootForStandAlone) {
+ gCurrentFolderToRerootForStandAlone = null;
+ folder.endFolderLoading();
+ if (gRerootOnFolderLoadForStandAlone) {
+ RerootFolderForStandAlone(uri);
+ }
+ }
+ }
+ }
+ else if (event == "JunkStatusChanged") {
+ HandleJunkStatusChanged(folder);
+ }
+ }
+}
+
+var messagepaneObserver = {
+ onDrop(aEvent) {
+ let dragSession = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService)
+ .getCurrentSession();
+ if (!this.canDrop(aEvent, dragSession)) {
+ return;
+ }
+ let sourceUri = aEvent.dataTransfer.getData("text/x-moz-message");
+ if (sourceUri != gCurrentMessageUri)
+ {
+ var msgHdr = GetMsgHdrFromUri(sourceUri);
+
+ // 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().
+ gCurrentMessageUri = sourceUri;
+ gCurrentFolderUri = msgHdr.folder.URI;
+ UpdateMailToolbar('onDrop');
+
+ // 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
+ CreateView(dragSession.sourceNode.ownerDocument.defaultView.gDBView);
+ LoadMessageByMsgKey(msgHdr.messageKey);
+ }
+ aEvent.stopPropagation();
+ },
+
+ onDragOver(aEvent) {
+ var messagepanebox = document.getElementById("messagepanebox");
+ messagepanebox.setAttribute("dragover", "true");
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ },
+
+ onDragExit(aEvent) {
+ var messagepanebox = document.getElementById("messagepanebox");
+ messagepanebox.removeAttribute("dragover");
+ },
+
+ canDrop(aEvent, aDragSession) {
+ // Allow drop from mail:3pane window only - 4xp.
+ var doc = aDragSession.sourceNode.ownerDocument;
+ var elem = doc.getElementById("messengerWindow");
+ return (elem && (elem.getAttribute("windowtype") == "mail:3pane"));
+ },
+};
+
+function nsMsgDBViewCommandUpdater()
+{}
+
+function UpdateStandAloneMessageCounts()
+{
+ // hook for extra toolbar items
+ Services.obs.notifyObservers(window,
+ "mail:updateStandAloneMessageCounts");
+}
+
+nsMsgDBViewCommandUpdater.prototype =
+{
+ updateCommandStatus : function()
+ {
+ // the back end is smart and is only telling us to update command status
+ // when the # of items in the selection has actually changed.
+ UpdateMailToolbar("dbview, std alone window");
+ },
+
+ displayMessageChanged : function(aFolder, aSubject, aKeywords)
+ {
+ setTitleFromFolder(aFolder, aSubject);
+ ClearPendingReadTimer(); // we are loading / selecting a new message so kill the mark as read timer for the currently viewed message
+ gCurrentMessageUri = gDBView.URIForFirstSelectedMessage;
+ UpdateStandAloneMessageCounts();
+ goUpdateCommand("button_delete");
+ goUpdateCommand("button_junk");
+ goUpdateCommand("button_goBack");
+ goUpdateCommand("button_goForward");
+ },
+
+ updateNextMessageAfterDelete : function()
+ {
+ SetNextMessageAfterDelete();
+ },
+
+ summarizeSelection: function() {return false},
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIMsgDBViewCommandUpdater) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ }
+}
+
+function HandleDeleteOrMoveMsgCompleted(folder)
+{
+ if ((folder.URI == gCurrentFolderUri) && gCurrentMessageIsDeleted)
+ {
+ gDBView.onDeleteCompleted(true);
+ gCurrentMessageIsDeleted = false;
+ if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
+ {
+ var nextMstKey = gDBView.getKeyAt(gNextMessageViewIndexAfterDelete);
+ if (nextMstKey != nsMsgKey_None &&
+ !Services.prefs.getBoolPref("mail.close_message_window.on_delete"))
+ LoadMessageByViewIndex(gNextMessageViewIndexAfterDelete);
+ else
+ window.close();
+ }
+ else
+ {
+ // close the stand alone window because there are no more messages in the folder
+ window.close();
+ }
+ }
+}
+
+function HandleDeleteOrMoveMsgFailed(folder)
+{
+ gDBView.onDeleteCompleted(false);
+ if ((folder.URI == gCurrentFolderUri) && gCurrentMessageIsDeleted)
+ gCurrentMessageIsDeleted = false;
+}
+
+function IsCurrentLoadedFolder(folder)
+{
+ return (folder.URI == gCurrentFolderUri);
+}
+
+function OnLoadMessageWindow()
+{
+ AddMailOfflineObserver();
+ CreateMailWindowGlobals();
+ verifyAccounts(null);
+
+ InitMsgWindow();
+
+ messenger.setWindow(window, msgWindow);
+ // FIX ME - later we will be able to use onload from the overlay
+ OnLoadMsgHeaderPane();
+
+ var nsIFolderListener = Ci.nsIFolderListener;
+ var notifyFlags = nsIFolderListener.removed | nsIFolderListener.event |
+ nsIFolderListener.intPropertyChanged;
+ MailServices.mailSession.AddFolderListener(folderListener, notifyFlags);
+
+ var originalView = null;
+ var folder = null;
+ var messageUri;
+ var loadCustomMessage = false; //set to true when either loading a message/rfc822 attachment or a .eml file
+ if (window.arguments)
+ {
+ if (window.arguments[0])
+ {
+ try
+ {
+ messageUri = window.arguments[0];
+ if (messageUri instanceof Ci.nsIURI)
+ {
+ loadCustomMessage = /type=application\/x-message-display/.test(messageUri.spec);
+ gCurrentMessageUri = messageUri.spec;
+ if (messageUri instanceof Ci.nsIMsgMailNewsUrl)
+ folder = messageUri.folder;
+ }
+ }
+ catch(ex)
+ {
+ folder = null;
+ dump("## ex=" + ex + "\n");
+ }
+
+ if (!gCurrentMessageUri)
+ gCurrentMessageUri = window.arguments[0];
+ }
+ else
+ gCurrentMessageUri = null;
+
+ if (window.arguments[1])
+ gCurrentFolderUri = window.arguments[1];
+ else
+ gCurrentFolderUri = folder ? folder.URI : null;
+
+ if (window.arguments[2])
+ originalView = window.arguments[2];
+
+ }
+
+ CreateView(originalView);
+
+ // Before and after callbacks for the customizeToolbar code
+ var mailToolbox = getMailToolbox();
+ mailToolbox.customizeInit = MailToolboxCustomizeInit;
+ mailToolbox.customizeDone = MailToolboxCustomizeDone;
+ mailToolbox.customizeChange = MailToolboxCustomizeChange;
+
+ setTimeout(OnLoadMessageWindowDelayed, 0, loadCustomMessage);
+
+ SetupCommandUpdateHandlers();
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+}
+
+function HandleAppCommandEvent(evt)
+{
+ evt.stopPropagation();
+ switch (evt.command)
+ {
+ case "Back":
+ goDoCommand('cmd_goBack');
+ break;
+ case "Forward":
+ goDoCommand('cmd_goForward');
+ break;
+ case "Stop":
+ goDoCommand('cmd_stop');
+ break;
+ case "Search":
+ goDoCommand('cmd_search');
+ break;
+ case "Bookmarks":
+ toAddressBook();
+ break;
+ case "Reload":
+ goDoCommand('cmd_reload');
+ break;
+ case "Home":
+ default:
+ break;
+ }
+}
+
+function OnLoadMessageWindowDelayed(loadCustomMessage)
+{
+ gDBView.suppressMsgDisplay = false;
+ if (loadCustomMessage)
+ gDBView.loadMessageByUrl(gCurrentMessageUri);
+ else
+ {
+ var msgKey = extractMsgKeyFromURI(gCurrentMessageUri);
+ var viewIndex = gDBView.findIndexFromKey(msgKey, true);
+ // the message may not appear in the view if loaded from a search dialog
+ if (viewIndex != nsMsgViewIndex_None)
+ LoadMessageByViewIndex(viewIndex);
+ else
+ messenger.openURL(gCurrentMessageUri);
+ }
+ gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
+ UpdateStandAloneMessageCounts();
+
+ // set focus to the message pane
+ window.content.focus();
+
+ // since we just changed the pane with focus we need to update the toolbar to reflect this
+ // XXX TODO
+ // can we optimize
+ // and just update cmd_delete and button_delete?
+ UpdateMailToolbar("focus");
+}
+
+function CreateView(originalView)
+{
+ var msgFolder = GetLoadedMsgFolder();
+
+ // extract the sort type, the sort order,
+ var sortType;
+ var sortOrder;
+ var viewFlags;
+ var viewType;
+
+ if (originalView)
+ {
+ viewType = originalView.viewType;
+ viewFlags = originalView.viewFlags;
+ sortType = originalView.sortType;
+ sortOrder = originalView.sortOrder;
+ }
+ else if (msgFolder)
+ {
+ var msgDatabase = msgFolder.msgDatabase;
+ if (msgDatabase)
+ {
+ var dbFolderInfo = msgDatabase.dBFolderInfo;
+ sortType = dbFolderInfo.sortType;
+ sortOrder = dbFolderInfo.sortOrder;
+ viewFlags = dbFolderInfo.viewFlags;
+ viewType = dbFolderInfo.viewType;
+ msgDatabase = null;
+ dbFolderInfo = null;
+ }
+ }
+ else
+ {
+ viewType = nsMsgViewType.eShowSearch;
+ }
+
+ // create a db view
+ CreateBareDBView(originalView, msgFolder, viewType, viewFlags, sortType, sortOrder);
+
+ var uri;
+ if (gCurrentMessageUri)
+ uri = gCurrentMessageUri;
+ else if (gCurrentFolderUri)
+ uri = gCurrentFolderUri;
+ else
+ uri = null;
+
+ SetUpToolbarButtons(uri);
+
+ // hook for extra toolbar items
+ Services.obs.notifyObservers(window, "mail:setupToolbarItems", uri);
+}
+
+function extractMsgKeyFromURI()
+{
+ var msgKey = -1;
+ var msgHdr = messenger.msgHdrFromURI(gCurrentMessageUri);
+ if (msgHdr)
+ msgKey = msgHdr.messageKey;
+ return msgKey;
+}
+
+function OnUnloadMessageWindow()
+{
+ window.removeEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ UnloadCommandUpdateHandlers();
+
+ // FIX ME - later we will be able to use onunload from the overlay
+ OnUnloadMsgHeaderPane();
+
+ OnMailWindowUnload();
+}
+
+function GetSelectedMsgFolders()
+{
+ var msgFolder = GetLoadedMsgFolder();
+ return msgFolder ? [msgFolder] : [];
+}
+
+function GetNumSelectedMessages()
+{
+ return (gCurrentMessageUri) ? 1 : 0;
+}
+
+function GetSelectedIndices(dbView)
+{
+ try {
+ return dbView.getIndicesForSelection();
+ }
+ catch (ex) {
+ dump("ex = " + ex + "\n");
+ return null;
+ }
+}
+
+function GetLoadedMsgFolder()
+{
+ return gCurrentFolderUri ? MailUtils.getFolderForURI(gCurrentFolderUri)
+ : null;
+}
+
+function GetLoadedMessage()
+{
+ return gCurrentMessageUri;
+}
+
+//Clear everything related to the current message. called after load start page.
+function ClearMessageSelection()
+{
+ gCurrentMessageUri = null;
+ gCurrentFolderUri = null;
+ UpdateMailToolbar("clear msg, std alone window");
+}
+
+function SetNextMessageAfterDelete()
+{
+ gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
+}
+
+function SelectMsgFolder(msgfolder) {
+ if (!msgfolder || msgfolder.isServer)
+ return;
+
+ let folderUri = msgfolder.URI;
+ if (folderUri == gCurrentFolderUri)
+ return;
+
+ // close old folder view
+ var dbview = GetDBView();
+ if (dbview)
+ dbview.close();
+
+ gCurrentFolderToRerootForStandAlone = folderUri;
+
+ if (msgfolder.manyHeadersToDownload)
+ {
+ gRerootOnFolderLoadForStandAlone = true;
+ try
+ {
+ msgfolder.startFolderLoading();
+ msgfolder.updateFolder(msgWindow);
+ }
+ catch(ex)
+ {
+ dump("Error loading with many headers to download: " + ex + "\n");
+ }
+ }
+ else
+ {
+ RerootFolderForStandAlone(folderUri);
+ gRerootOnFolderLoadForStandAlone = false;
+ msgfolder.startFolderLoading();
+
+ //Need to do this after rerooting folder. Otherwise possibility of receiving folder loaded
+ //notification before folder has actually changed.
+ msgfolder.updateFolder(msgWindow);
+ }
+}
+
+function RerootFolderForStandAlone(uri)
+{
+ gCurrentFolderUri = uri;
+
+ // create new folder view
+ CreateView(null);
+
+ // now do the work to load the appropriate message
+ if (gNextMessageAfterLoad) {
+ var type = gNextMessageAfterLoad;
+ gNextMessageAfterLoad = null;
+ LoadMessageByNavigationType(type);
+ }
+
+ SetUpToolbarButtons(gCurrentFolderUri);
+
+ UpdateMailToolbar("reroot folder in stand alone window");
+
+ // hook for extra toolbar items
+ Services.obs.notifyObservers(window, "mail:setupToolbarItems", uri);
+}
+
+function GetMsgHdrFromUri(messageUri)
+{
+ return messenger.msgHdrFromURI(messageUri);
+}
+
+function SelectMessage(messageUri)
+{
+ var msgHdr = GetMsgHdrFromUri(messageUri);
+ LoadMessageByMsgKey(msgHdr.messageKey);
+}
+
+function ReloadMessage()
+{
+ gDBView.reloadMessage();
+}
+
+// MessageWindowController object (handles commands when one of the trees does not have focus)
+var MessageWindowController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_delete":
+ case "cmd_stop":
+ case "cmd_undo":
+ case "cmd_redo":
+ case "cmd_killThread":
+ case "cmd_killSubthread":
+ case "cmd_watchThread":
+ case "button_delete":
+ case "button_shiftDelete":
+ case "button_junk":
+ case "cmd_shiftDelete":
+ case "cmd_saveAsTemplate":
+ case "cmd_getMsgsForAuthAccounts":
+ case "button_mark":
+ case "cmd_markAsRead":
+ case "cmd_markAsUnread":
+ case "cmd_markAllRead":
+ case "cmd_markThreadAsRead":
+ case "cmd_markReadByDate":
+ case "cmd_markAsFlagged":
+ case "button_file":
+ case "cmd_markAsJunk":
+ case "cmd_markAsNotJunk":
+ case "cmd_recalculateJunkScore":
+ case "cmd_markAsShowRemote":
+ case "cmd_markAsNotPhish":
+ case "cmd_applyFiltersToSelection":
+ case "cmd_applyFilters":
+ case "cmd_runJunkControls":
+ case "cmd_deleteJunk":
+ case "cmd_nextMsg":
+ case "button_next":
+ case "cmd_nextUnreadMsg":
+ case "cmd_nextFlaggedMsg":
+ case "cmd_nextUnreadThread":
+ case "cmd_previousMsg":
+ case "cmd_previousUnreadMsg":
+ case "cmd_previousFlaggedMsg":
+ case "cmd_goBack":
+ case "button_goBack":
+ case "cmd_goForward":
+ case "button_goForward":
+ return (gDBView.keyForFirstSelectedMessage != nsMsgKey_None);
+ case "cmd_viewPageSource":
+ return GetNumSelectedMessages() > 0;
+ case "cmd_reply":
+ case "button_reply":
+ case "cmd_replyList":
+ case "cmd_replyGroup":
+ case "cmd_replySender":
+ case "cmd_replyall":
+ case "cmd_replySenderAndGroup":
+ case "cmd_replyAllRecipients":
+ case "button_replyall":
+ case "cmd_forward":
+ case "button_forward":
+ case "cmd_forwardInline":
+ case "cmd_forwardAttachment":
+ case "cmd_editAsNew":
+ case "cmd_editDraftMsg":
+ case "cmd_newMsgFromTemplate":
+ case "cmd_editTemplateMsg":
+ case "cmd_getNextNMessages":
+ case "cmd_find":
+ case "cmd_findNext":
+ case "cmd_findPrev":
+ case "button_search":
+ case "cmd_search":
+ case "cmd_reload":
+ case "cmd_saveAsFile":
+ case "cmd_getNewMessages":
+ case "button_getNewMessages":
+ case "button_print":
+ case "cmd_print":
+ case "cmd_printpreview":
+ case "cmd_printSetup":
+ case "cmd_settingsOffline":
+ case "cmd_createFilterFromPopup":
+ case "cmd_createFilterFromMenu":
+ case "cmd_viewAllHeader":
+ case "cmd_viewNormalHeader":
+ return true;
+ case "cmd_synchronizeOffline":
+ case "cmd_downloadFlagged":
+ case "cmd_downloadSelected":
+ return !Services.io.offline;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ var loadedFolder;
+ var enabled = new Object();
+ enabled.value = false;
+ var checkStatus = new Object();
+
+ switch (command)
+ {
+ case "cmd_createFilterFromPopup":
+ case "cmd_createFilterFromMenu":
+ loadedFolder = GetLoadedMsgFolder();
+ return (loadedFolder && loadedFolder.server.canHaveFilters);
+ case "cmd_delete":
+ UpdateDeleteCommand();
+ // fall through
+ case "button_delete":
+ if (command == "button_delete")
+ UpdateDeleteToolbarButton(false);
+ // fall through
+ case "cmd_shiftDelete":
+ case "button_shiftDelete":
+ loadedFolder = GetLoadedMsgFolder();
+ return gCurrentMessageUri && loadedFolder && loadedFolder.canDeleteMessages;
+ case "button_junk":
+ UpdateJunkToolbarButton();
+ // fall through
+ case "cmd_markAsJunk":
+ case "cmd_markAsNotJunk":
+ if (gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.junk, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_recalculateJunkScore":
+ if (GetNumSelectedMessages() > 0 && gDBView)
+ gDBView.getCommandStatus(nsMsgViewCommandType.runJunkControls, enabled, checkStatus);
+ return enabled.value;
+ case "cmd_reply":
+ case "button_reply":
+ case "cmd_replyList":
+ case "cmd_replyGroup":
+ case "cmd_replySender":
+ case "cmd_replyall":
+ case "button_replyall":
+ case "cmd_replySenderAndGroup":
+ case "cmd_replyAllRecipients":
+ case "cmd_forward":
+ case "button_forward":
+ case "cmd_forwardInline":
+ case "cmd_forwardAttachment":
+ case "cmd_editAsNew":
+ case "cmd_editDraftMsg":
+ case "cmd_newMsgFromTemplate":
+ case "cmd_editTemplateMsg":
+ case "cmd_print":
+ case "cmd_printpreview":
+ case "button_print":
+ case "cmd_saveAsFile":
+ return true;
+ case "cmd_saveAsTemplate":
+ var target = getMessageBrowser().contentPrincipal.URI.scheme;
+ return target != "news";
+ case "cmd_viewPageSource":
+ case "cmd_reload":
+ case "cmd_find":
+ case "button_mark":
+ case "cmd_markAllRead":
+ case "cmd_markThreadAsRead":
+ case "cmd_markReadByDate":
+ case "cmd_viewAllHeader":
+ case "cmd_viewNormalHeader":
+ return true;
+ case "cmd_markAsRead":
+ return CanMarkMsgAsRead(true);
+ case "cmd_markAsUnread":
+ return CanMarkMsgAsRead(false);
+ case "cmd_markAsFlagged":
+ case "button_file":
+ return (gCurrentMessageUri != null);
+ case "cmd_markAsShowRemote":
+ return (GetNumSelectedMessages() > 0 && checkMsgHdrPropertyIsNot("remoteContentPolicy", kAllowRemoteContent));
+ case "cmd_markAsNotPhish":
+ return (GetNumSelectedMessages() > 0 && checkMsgHdrPropertyIsNot("notAPhishMessage", kNotAPhishMessage));
+ case "cmd_printSetup":
+ return true;
+ case "cmd_getNewMessages":
+ case "button_getNewMessages":
+ case "cmd_getMsgsForAuthAccounts":
+ return IsGetNewMessagesEnabled();
+ case "cmd_getNextNMessages":
+ return IsGetNextNMessagesEnabled();
+ case "cmd_downloadFlagged":
+ case "cmd_downloadSelected":
+ case "cmd_synchronizeOffline":
+ return !Services.io.offline;
+ case "cmd_settingsOffline":
+ return IsAccountOfflineEnabled();
+ case "cmd_nextMsg":
+ case "button_next":
+ case "cmd_nextUnreadMsg":
+ case "cmd_nextFlaggedMsg":
+ case "cmd_nextUnreadThread":
+ case "cmd_previousMsg":
+ case "cmd_previousUnreadMsg":
+ case "cmd_previousFlaggedMsg":
+ case "cmd_applyFiltersToSelection":
+ return true;
+ case "cmd_findNext":
+ case "cmd_findPrev":
+ return MsgCanFindAgain();
+ case "cmd_goBack":
+ case "button_goBack":
+ return gDBView && gDBView.navigateStatus(nsMsgNavigationType.back);
+ case "cmd_goForward":
+ case "button_goForward":
+ return gDBView && gDBView.navigateStatus(nsMsgNavigationType.forward);
+ case "button_search":
+ case "cmd_search":
+ loadedFolder = GetLoadedMsgFolder();
+ return (loadedFolder && loadedFolder.server.canSearchMessages);
+ case "cmd_stop":
+ return true;
+ case "cmd_undo":
+ case "cmd_redo":
+ return SetupUndoRedoCommand(command);
+ case "cmd_applyFilters":
+ case "cmd_runJunkControls":
+ case "cmd_deleteJunk":
+ return false;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ // if the user invoked a key short cut then it is possible that we got here for a command which is
+ // really disabled. kick out if the command should be disabled.
+ if (!this.isCommandEnabled(command)) return;
+
+ var navigationType = nsMsgNavigationType.nextUnreadMessage;
+
+ switch ( command )
+ {
+ case "cmd_getNewMessages":
+ MsgGetMessage();
+ break;
+ case "cmd_undo":
+ messenger.undo(msgWindow);
+ break;
+ case "cmd_redo":
+ messenger.redo(msgWindow);
+ break;
+ case "cmd_getMsgsForAuthAccounts":
+ MsgGetMessagesForAllAuthenticatedAccounts();
+ break;
+ case "cmd_getNextNMessages":
+ MsgGetNextNMessages();
+ break;
+ case "cmd_reply":
+ MsgReplyMessage(null);
+ break;
+ case "cmd_replyList":
+ MsgReplyList(null);
+ break;
+ case "cmd_replyGroup":
+ MsgReplyGroup(null);
+ break;
+ case "cmd_replySender":
+ MsgReplySender(null);
+ break;
+ case "cmd_replyall":
+ MsgReplyToAllMessage(null);
+ break;
+ case "cmd_replySenderAndGroup":
+ MsgReplyToSenderAndGroup(null);
+ break;
+ case "cmd_replyAllRecipients":
+ MsgReplyToAllRecipients(null);
+ break;
+ case "cmd_forward":
+ MsgForwardMessage(null);
+ break;
+ case "cmd_forwardInline":
+ MsgForwardAsInline(null);
+ break;
+ case "cmd_forwardAttachment":
+ MsgForwardAsAttachment(null);
+ break;
+ case "cmd_editAsNew":
+ MsgEditMessageAsNew(null);
+ break;
+ case "cmd_editDraftMsg":
+ MsgEditDraftMessage(null);
+ break;
+ case "cmd_newMsgFromTemplate":
+ MsgNewMessageFromTemplate(null);
+ break;
+ case "cmd_editTemplateMsg":
+ MsgEditTemplateMessage(null);
+ break;
+ case "cmd_createFilterFromPopup":
+ CreateFilter(document.popupNode);
+ break;
+ case "cmd_createFilterFromMenu":
+ MsgCreateFilter();
+ break;
+ case "cmd_delete":
+ case "button_delete":
+ MsgDeleteMessage(false);
+ UpdateDeleteToolbarButton(false);
+ break;
+ case "cmd_shiftDelete":
+ case "button_shiftDelete":
+ MsgDeleteMessage(true);
+ break;
+ case "button_junk":
+ MsgJunk();
+ break;
+ case "cmd_stop":
+ MsgStop();
+ break;
+ case "cmd_printSetup":
+ PrintUtils.showPageSetup();
+ break;
+ case "cmd_print":
+ PrintEnginePrint();
+ break;
+ case "cmd_printpreview":
+ PrintEnginePrintPreview();
+ break;
+ case "cmd_saveAsFile":
+ MsgSaveAsFile();
+ break;
+ case "cmd_saveAsTemplate":
+ MsgSaveAsTemplate();
+ break;
+ case "cmd_viewPageSource":
+ MsgViewPageSource();
+ break;
+ case "cmd_reload":
+ ReloadMessage();
+ break;
+ case "cmd_find":
+ MsgFind();
+ break;
+ case "cmd_findNext":
+ MsgFindAgain(false);
+ break;
+ case "cmd_findPrev":
+ MsgFindAgain(true);
+ break;
+ case "button_search":
+ case "cmd_search":
+ MsgSearchMessages();
+ break;
+ case "button_mark":
+ MsgMarkMsgAsRead();
+ return;
+ case "cmd_markAsRead":
+ MsgMarkMsgAsRead(true);
+ return;
+ case "cmd_markAsUnread":
+ MsgMarkMsgAsRead(false);
+ return;
+ case "cmd_markThreadAsRead":
+ MsgMarkThreadAsRead();
+ return;
+ case "cmd_markAllRead":
+ MsgMarkAllRead();
+ return;
+ case "cmd_markReadByDate":
+ MsgMarkReadByDate();
+ return;
+ case "cmd_viewAllHeader":
+ MsgViewAllHeaders();
+ return;
+ case "cmd_viewNormalHeader":
+ MsgViewNormalHeaders();
+ return;
+ case "cmd_markAsFlagged":
+ MsgMarkAsFlagged();
+ return;
+ case "cmd_markAsJunk":
+ JunkSelectedMessages(true);
+ return;
+ case "cmd_markAsNotJunk":
+ JunkSelectedMessages(false);
+ return;
+ case "cmd_recalculateJunkScore":
+ analyzeMessagesForJunk();
+ return;
+ case "cmd_markAsShowRemote":
+ LoadMsgWithRemoteContent();
+ return;
+ case "cmd_markAsNotPhish":
+ MsgIsNotAScam();
+ return;
+ case "cmd_downloadFlagged":
+ MsgDownloadFlagged();
+ return;
+ case "cmd_downloadSelected":
+ MsgDownloadSelected();
+ return;
+ case "cmd_synchronizeOffline":
+ MsgSynchronizeOffline();
+ return;
+ case "cmd_settingsOffline":
+ MsgSettingsOffline();
+ return;
+ case "cmd_nextUnreadMsg":
+ case "button_next":
+ performNavigation(nsMsgNavigationType.nextUnreadMessage);
+ break;
+ case "cmd_nextUnreadThread":
+ performNavigation(nsMsgNavigationType.nextUnreadThread);
+ break;
+ case "cmd_nextMsg":
+ performNavigation(nsMsgNavigationType.nextMessage);
+ break;
+ case "cmd_nextFlaggedMsg":
+ performNavigation(nsMsgNavigationType.nextFlagged);
+ break;
+ case "cmd_previousMsg":
+ performNavigation(nsMsgNavigationType.previousMessage);
+ break;
+ case "cmd_previousUnreadMsg":
+ performNavigation(nsMsgNavigationType.previousUnreadMessage);
+ break;
+ case "cmd_previousFlaggedMsg":
+ performNavigation(nsMsgNavigationType.previousFlagged);
+ break;
+ case "cmd_goBack":
+ performNavigation(nsMsgNavigationType.back);
+ break;
+ case "cmd_goForward":
+ performNavigation(nsMsgNavigationType.forward);
+ break;
+ case "cmd_applyFiltersToSelection":
+ MsgApplyFiltersToSelection();
+ break;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ }
+};
+
+function LoadMessageByNavigationType(type)
+{
+ var resultId = new Object;
+ var resultIndex = new Object;
+ var threadIndex = new Object;
+
+ gDBView.viewNavigate(type, resultId, resultIndex, threadIndex, true /* wrap */);
+
+ // if we found something....display it.
+ if ((resultId.value != nsMsgKey_None) && (resultIndex.value != nsMsgKey_None))
+ {
+ // load the message key
+ LoadMessageByMsgKey(resultId.value);
+ // if we changed folders, the message counts changed.
+ UpdateStandAloneMessageCounts();
+
+ // new message has been loaded
+ return true;
+ }
+
+ // no message found to load
+ return false;
+}
+
+function performNavigation(type)
+{
+ // Try to load a message by navigation type if we can find
+ // the message in the same folder.
+ if (LoadMessageByNavigationType(type))
+ return;
+
+ CrossFolderNavigation(type);
+}
+
+function SetupCommandUpdateHandlers()
+{
+ top.controllers.insertControllerAt(0, MessageWindowController);
+}
+
+function UnloadCommandUpdateHandlers()
+{
+ top.controllers.removeController(MessageWindowController);
+}
+
+function GetDBView()
+{
+ return gDBView;
+}
+
+function LoadMessageByMsgKey(messageKey)
+{
+ var viewIndex = gDBView.findIndexFromKey(messageKey, true);
+ gDBView.loadMessageByViewIndex(viewIndex);
+ // we only want to update the toolbar if there was no previous selected message.
+ if (nsMsgKey_None == gDBView.keyForFirstSelectedMessage)
+ UpdateMailToolbar("update toolbar for message Window");
+}
+
+function LoadMessageByViewIndex(viewIndex)
+{
+ gDBView.loadMessageByViewIndex(viewIndex);
+ // we only want to update the toolbar if there was no previous selected message.
+ if (nsMsgKey_None == gDBView.keyForFirstSelectedMessage)
+ UpdateMailToolbar("update toolbar for message Window");
+}
diff --git a/comm/suite/mailnews/content/messageWindow.xul b/comm/suite/mailnews/content/messageWindow.xul
new file mode 100644
index 0000000000..9ec502086c
--- /dev/null
+++ b/comm/suite/mailnews/content/messageWindow.xul
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+<?xml-stylesheet href="chrome://messenger/skin/messageWindow.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/mailWindowOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+]>
+
+<window id="messengerWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ title="&messengerWindow.title;"
+ titlemodifier="&titleModifier.label;"
+ titlemenuseparator="&titleSeparator.label;"
+ onload="OnLoadMessageWindow()"
+ onunload="OnUnloadMessageWindow()"
+ width="750"
+ height="500"
+ persist="width height screenX screenY sizemode"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ macanimationtype="document"
+ drawtitle="true"
+ windowtype="mail:messageWindow">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_offlinePrompts" src="chrome://messenger/locale/offline.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://messenger/content/commandglue.js"/>
+ <script src="chrome://messenger/content/mailWindow.js"/>
+ <script src="chrome://messenger/content/messageWindow.js"/>
+ <script src="chrome://messenger/content/accountUtils.js"/>
+ <script src="chrome://messenger/content/mailContextMenus.js"/>
+ <script src="chrome://messenger/content/phishingDetector.js"/>
+ <script src="chrome://communicator/content/contentAreaClick.js"/>
+ <script src="chrome://global/content/nsDragAndDrop.js"/>
+ <script src="chrome://messenger/content/msgViewNavigation.js"/>
+ <script src="chrome://messenger/content/tabmail.js"/>
+
+ <commandset id="mailCommands">
+ <commandset id="mailFileMenuItems"/>
+ <commandset id="mailDownloadCommands"/>
+ <commandset id="mailViewMenuItems"/>
+ <commandset id="mailEditMenuItems"/>
+ <commandset id="mailSearchMenuItems"/>
+ <commandset id="mailGoMenuItems"/>
+ <commandset id="mailMessageMenuItems"/>
+ <commandset id="mailToolbarItems"/>
+ <commandset id="mailGetMsgMenuItems"/>
+ <commandset id="mailMarkMenuItems"/>
+ <commandset id="mailToolsMenuItems"/>
+ <commandset id="mailEditContextMenuItems"/>
+ <commandset id="tasksCommands"/>
+ <commandset id="commandKeys"/>
+ <command id="cmd_close" oncommand="window.close();"/>
+ </commandset>
+
+ <broadcasterset id="mailBroadcasters">
+ <broadcaster id="mailHideMenus" hidden="true"/>
+ <broadcaster id="mailDisableKeys" disabled="true"/>
+ <!-- File Menu -->
+ <broadcaster id="Communicator:WorkMode"/>
+ </broadcasterset>
+
+ <broadcasterset id="mainBroadcasterSet"/>
+
+ <keyset id="mailKeys">
+ <keyset id="tasksKeys"/>
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+ <popupset id="messagePopupSet">
+ <menupopup id="mailContext"/>
+ <menupopup id="attachmentListContext"/>
+ <menupopup id="copyUrlPopup"/>
+ <menupopup id="messageIdContext"/>
+ <menupopup id="emailAddressPopup"/>
+ <menupopup id="toolbar-context-menu"/>
+ <menupopup id="remoteContentOptions"/>
+ <tooltip id="aHTMLTooltip"
+ onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
+ <panel id="customizeToolbarSheetPopup"/>
+ </popupset>
+
+ <vbox id="titlebar"/>
+
+ <toolbox id="mail-toolbox">
+ <toolbar id="mail-toolbar-menubar2">
+ <toolbaritem id="menubar-items">
+ <menubar id="mail-menubar"/>
+ </toolbaritem>
+ </toolbar>
+ <toolbar id="msgToolbar"/>
+ <toolbarset id="customToolbars"/>
+ </toolbox>
+
+ <!-- msg header view -->
+<vbox id="messagesBox" flex="1">
+ <notificationbox id="messagepanebox"
+ class="browser-notificationbox"
+ flex="3"
+ persist="collapsed"
+ ondragover="messagepaneObserver.onDragOver(event);"
+ ondrop="messagepaneObserver.onDrop(event);"
+ ondragexit="messagepaneObserver.onDragExit(event);">
+
+ <hbox id="msgHeaderView"/>
+
+ <!-- message view -->
+ <browser id="messagepane"
+ name="messagepane"
+ height="0"
+ flex="1"
+ minwidth="1"
+ minheight="1"
+ context="mailContext"
+ tooltip="aHTMLTooltip"
+ disablesecurity="true"
+ disablehistory="true"
+ autofind="false"
+ type="content"
+ primary="true"
+ onresize="return messagePaneOnResize(event);"
+ onclick="return messagePaneOnClick(event);"/>
+ </notificationbox>
+</vbox>
+
+ <statusbar class="chromeclass-status" id="status-bar"/>
+
+</window>
diff --git a/comm/suite/mailnews/content/messenger.css b/comm/suite/mailnews/content/messenger.css
new file mode 100644
index 0000000000..785c1178e8
--- /dev/null
+++ b/comm/suite/mailnews/content/messenger.css
@@ -0,0 +1,236 @@
+/* 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/. */
+
+/* ===== messenger.css ==================================================
+ == Content specific styles for Messenger.
+ ======================================================================= */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* ::::: mail xbl bindings ::::: */
+
+description[selectable="true"] {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#extdescription");
+}
+
+descriptionitem {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#descriptionitem");
+}
+
+.descriptionitem-iconic {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#descriptionitem-iconic");
+}
+
+mail-messageid {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-messageid");
+}
+
+mail-messageids-headerfield {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-messageids-headerfield");
+}
+
+mail-emailaddress {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-emailaddress");
+ -moz-user-focus: normal;
+}
+
+mail-emailheaderfield {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-emailheaderfield");
+}
+
+mail-toggle-headerfield {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-toggle-headerfield");
+}
+
+mail-multi-emailHeaderField {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-multi-emailHeaderField");
+}
+
+mail-headerfield {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-headerfield");
+}
+
+mail-urlfield {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-urlfield");
+}
+
+mail-tagfield {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#mail-headerfield-tags");
+}
+
+menupopup[type="folder"] {
+ -moz-binding: url("chrome://messenger/content/folderWidgets.xml#folder-menupopup");
+}
+
+.addrbooksPopup {
+ -moz-binding: url("chrome://messenger/content/addressbook/addrbookWidgets.xml#addrbooks-menupopup");
+}
+
+.map-list {
+ -moz-binding: url("chrome://messenger/content/addressbook/addrbookWidgets.xml#map-list");
+}
+
+#searchTermList > listitem {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#listitem");
+}
+
+searchattribute {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchattribute");
+}
+
+searchoperator {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchoperator");
+}
+
+searchvalue {
+ display: -moz-deck;
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchvalue");
+}
+
+searchterm {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#searchterm");
+}
+
+.ruleaction {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleaction");
+}
+
+.ruleactiontype {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontype-menulist");
+}
+
+.ruleactiontarget[type] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base");
+}
+
+.ruleactiontarget[type="movemessage"], .ruleactiontarget[type="copymessage"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder");
+}
+
+.ruleactiontarget[type="addtagtomessage"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-tag");
+}
+
+.ruleactiontarget[type="setpriorityto"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-priority");
+}
+
+.ruleactiontarget[type="setjunkscore"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-junkscore");
+}
+
+.ruleactiontarget[type="forwardmessage"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-forwardto");
+}
+
+.ruleactiontarget[type="replytomessage"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-replyto");
+}
+
+.folderSummaryPopup
+{
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#folderSummary-popup");
+}
+
+folderSummary
+{
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#folderSummary");
+}
+
+folderSummaryMessage
+{
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#folderSummary-message");
+}
+
+folderSummaryLocation
+{
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#folderSummary-location");
+}
+
+folderSummarySubfoldersSummary
+{
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#folderSummary-subfoldersSummary");
+}
+
+dummy.usesMailWidgets {
+ -moz-binding: url("chrome://messenger/content/mailWidgets.xml#dummy");
+}
+
+/* tabmail */
+
+#tabmail
+{
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail");
+}
+
+.tabmail-tabs {
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-tabs");
+}
+
+.tabmail-arrowscrollbox {
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-arrowscrollbox");
+}
+
+.tabmail-tab {
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-tab");
+}
+
+.tabs-newbutton {
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-new-tab-button");
+}
+
+.tab-close-button,
+.tabs-closebutton {
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-close-tab-button");
+}
+
+.tab-close-button {
+ display: none;
+}
+
+.tabmail-tabs:not([closebuttons="noclose"]):not([closebuttons="closeatend"]) > .tabmail-tab[selected="true"] > .tab-close-button {
+ display: -moz-box;
+}
+
+.tabmail-tabs[closebuttons="alltabs"] .tab-close-button {
+ display: -moz-box;
+}
+
+.tabs-alltabs-popup {
+ /* override toolkit's .menulist-menupopup binding */
+ -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-alltabs-popup") ! important;
+}
+
+/* Used for selecting appropriate button for when next to search box */
+
+#button-search {
+ display: -moz-box;
+}
+
+#search-container + #button-search-container > #button-search,
+#wrapper-search-container + toolbarpaletteitem[place="toolbar"] > #button-search-container > #button-search {
+ display: none;
+}
+
+#button-advanced {
+ display: none;
+}
+
+#search-container + #button-search-container > #button-advanced,
+#wrapper-search-container + toolbarpaletteitem[place="toolbar"] > #button-search-container > #button-advanced {
+ display: -moz-box;
+}
+
+/* Wallpaper patch for Bug 517924 */
+
+#expandedHeaderView {
+ overflow-y: auto;
+ overflow-x: hidden;
+ max-height: 14em;
+}
+
+/* Lightning toobar menu button */
+.button-appmenu {
+display: none;
+}
diff --git a/comm/suite/mailnews/content/messenger.xul b/comm/suite/mailnews/content/messenger.xul
new file mode 100644
index 0000000000..42c73a96ec
--- /dev/null
+++ b/comm/suite/mailnews/content/messenger.xul
@@ -0,0 +1,275 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+<?xml-stylesheet href="chrome://messenger/skin/mailWindow1.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/threadPane.xul"?>
+<?xul-overlay href="chrome://messenger/content/folderPane.xul"?>
+<?xul-overlay href="chrome://messenger/content/mailWindowOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+]>
+
+<window id="messengerWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ title="&messengerWindow.title;"
+ titlemodifier="&titleModifier.label;"
+ titlemenuseparator="&titleSeparator.label;"
+ onload="OnLoadMessenger()"
+ onunload="OnUnloadMessenger()"
+ onclose="return MailWindowIsClosing();"
+ screenX="10" screenY="10"
+ persist="width height screenX screenY sizemode"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ macanimationtype="document"
+ drawtitle="true"
+ windowtype="mail:3pane">
+
+<stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+</stringbundleset>
+
+<script src="chrome://messenger/content/commandglue.js"/>
+<script src="chrome://messenger/content/msgViewNavigation.js"/>
+<script src="chrome://messenger/content/mailWindow.js"/>
+<script src="chrome://messenger/content/msgMail3PaneWindow.js"/>
+<script src="chrome://messenger/content/mail3PaneWindowCommands.js"/>
+<script src="chrome://messenger/content/mailContextMenus.js"/>
+<script src="chrome://messenger/content/accountUtils.js"/>
+<script src="chrome://messenger/content/folderPane.js"/>
+<script src="chrome://messenger/content/phishingDetector.js"/>
+<script src="chrome://communicator/content/contentAreaClick.js"/>
+<script src="chrome://global/content/nsDragAndDrop.js"/>
+<script src="chrome://messenger/content/searchBar.js"/>
+<script src="chrome://messenger/content/tabmail.js"/>
+
+<commandset id="mailCommands">
+ <commandset id="mailFileMenuItems"/>
+ <commandset id="mailDownloadCommands"/>
+ <commandset id="mailViewMenuItems"/>
+ <commandset id="mailEditMenuItems"/>
+ <commandset id="mailEditContextMenuItems"/>
+ <commandset id="mailSearchMenuItems"/>
+ <commandset id="mailGoMenuItems"/>
+ <commandset id="mailMessageMenuItems"/>
+ <commandset id="mailToolbarItems"/>
+ <commandset id="mailGetMsgMenuItems"/>
+ <commandset id="mailMarkMenuItems"/>
+ <commandset id="mailToolsMenuItems"/>
+ <commandset id="globalEditMenuItems"/>
+ <commandset id="selectEditMenuItems"/>
+ <commandset id="clipboardEditMenuItems"/>
+ <commandset id="FocusRingUpdate_Mail"
+ commandupdater="true"
+ events="focus"
+ oncommandupdate="FocusRingUpdate_Mail()"/>
+ <commandset id="tasksCommands"/>
+ <command id="cmd_close" oncommand="MsgCloseTabOrWindow();"/>
+</commandset>
+
+<broadcasterset id="mailBroadcasters">
+ <broadcaster id="mailHideMenus"/>
+ <broadcaster id="mailDisableKeys"/>
+ <broadcaster id="mailDisableViewsSearch" disabled="true"/>
+ <!-- File Menu -->
+ <broadcaster id="Communicator:WorkMode"/>
+</broadcasterset>
+
+<broadcasterset id="mainBroadcasterSet"/>
+
+<keyset id="mailKeys">
+ <!-- Tab/F6 Keys -->
+ <key keycode="VK_TAB" oncommand="SwitchPaneFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_TAB" oncommand="SwitchPaneFocus(event);" modifiers="control"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="control"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="shift"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);"/>
+
+ <keyset id="tasksKeys"/>
+</keyset>
+
+ <popupset id="mainPopupSet">
+ <menupopup id="mailContext"/>
+ <menupopup id="folderPaneContext"/>
+ <menupopup id="attachmentListContext"/>
+ <tooltip id="attachmentListTooltip"/>
+ <menupopup id="copyUrlPopup"/>
+ <menupopup id="messageIdContext"/>
+ <menupopup id="emailAddressPopup"/>
+ <menupopup id="toolbar-context-menu"/>
+ <tooltip id="folderpopup" class="folderSummaryPopup"/>
+ <tooltip id="aHTMLTooltip"
+ onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
+ <panel id="customizeToolbarSheetPopup"/>
+ <menupopup id="networkProperties"/>
+ <menupopup id="remoteContentOptions"/>
+ </popupset>
+
+ <vbox id="titlebar"/>
+
+ <toolbox id="mail-toolbox" class="toolbox-top">
+ <toolbar id="mail-toolbar-menubar2"
+ type="menubar">
+ <toolbaritem id="menubar-items">
+ <menubar id="mail-menubar"/>
+ </toolbaritem>
+ </toolbar>
+ <toolbar id="msgToolbar"/>
+ <toolbarset id="customToolbars"/>
+ <toolbar id="searchToolbar"
+ class="chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&searchToolbar.tooltip;"
+ toolbarname="&showSearchToolbarCmd.label;"
+ accesskey="&showSearchToolbarCmd.accesskey;"
+ customizable="true"
+ nowindowdrag="true"
+ mode="full"
+ iconsize="small"
+ labelalign="end"
+ defaultmode="full"
+ defaulticonsize="small"
+ defaultlabelalign="end"
+ defaultset="mailviews-container,spring,search-container,button-search-container"
+ context="toolbar-context-menu"/>
+ </toolbox>
+
+ <!-- XXX This extension point (tabmail-container) is only temporary!
+ (See bug 460252 for details.)
+ We will readd a mechanism for sidebar panes in bug 178003.
+ -->
+ <hbox id="tabmail-container" flex="1">
+ <tabmail id="tabmail" flex="1" panelcontainer="tabpanelcontainer">
+ <box id="tabmail-buttons" orientation="horizontal"/>
+ <toolbar id="tabbar-toolbar"
+ xpfe="false"
+ toolboxid="mail-toolbox"
+ toolbarname="&showTabsToolbarCmd.label;"
+ accesskey="&showTabsToolbarCmd.accesskey;"
+ customizable="true"
+ nowindowdrag="true"
+ mode="icons"
+ iconsize="small"
+ labelalign="end"
+ defaultmode="icons"
+ defaulticonsize="small"
+ defaultlabelalign="end"
+ context="toolbar-context-menu"/>
+ <tabpanels id="tabpanelcontainer" flex="1" class="plain" selectedIndex="0">
+ <!-- The main mail three pane frame -->
+ <box id="mailContent" orient="vertical" flex="1">
+ <box id="messengerBox"
+ orient="horizontal"
+ flex="1"
+ minheight="100"
+ height="100"
+ persist="height">
+ <vbox id="folderPaneBox"
+ minwidth="100"
+ width="200"
+ persist="collapsed width hidden">
+ <tree id="folderTree">
+ <treechildren tooltip="folderpopup"/>
+ </tree>
+ </vbox>
+
+ <splitter id="folderpane-splitter"
+ collapse="before"
+ resizeafter="grow"
+ persist="state collapsed"
+ oncommand="MsgToggleFolderPane(false);">
+ <grippy/>
+ </splitter>
+
+ <box id="messagesBox"
+ orient="vertical"
+ flex="1"
+ minwidth="100"
+ width="100"
+ persist="width">
+ <deck id="displayDeck"
+ flex="1"
+ selectedIndex="0"
+ minheight="100"
+ height="100"
+ persist="height"
+ onselect="ObserveDisplayDeckChange(event);">
+ <!-- first panel in displayDeck is Account Central -->
+ <vbox id="accountCentralBox">
+ <iframe name="accountCentralPane"
+ width="150"
+ flex="1"
+ src="about:blank"/>
+ </vbox>
+ <!-- second panel is the threadPane -->
+ <vbox id="threadPaneBox">
+ <tree id="threadTree"
+ treelines="true"
+ keepcurrentinview="true"
+ flex="1"
+ context="mailContext"
+ class="window-focusborder"
+ focusring="false"/>
+ </vbox>
+ <!-- extensions may overlay in additional panels; don't assume that there are only 2! -->
+ </deck>
+
+ <!-- if you change this id, please change GetThreadAndMessagePaneSplitter() and MsgToggleMessagePane() -->
+ <splitter id="threadpane-splitter"
+ collapse="after"
+ persist="state collapsed hidden"
+ collapsed="true"
+ oncommand="MsgToggleMessagePane(false);">
+ <grippy/>
+ </splitter>
+
+ <notificationbox id="messagepanebox"
+ flex="2"
+ minheight="100"
+ height="200"
+ minwidth="100"
+ width="200"
+ persist="height width"
+ class="browser-notificationbox window-focusborder"
+ focusring="false">
+ <hbox id="msgHeaderView"/>
+ <!-- The messagepanewrapper hbox exists to allow extensions
+ to add sidebars to the message pane. -->
+ <hbox id="messagepanewrapper" flex="1">
+ <browser id="messagepane"
+ name="messagepane"
+ height="0"
+ flex="1"
+ minwidth="1"
+ minheight="1"
+ tooltip="aHTMLTooltip"
+ context="mailContext"
+ disablesecurity="true"
+ disablehistory="true"
+ autofind="false"
+ type="content"
+ primary="true"
+ onresize="return messagePaneOnResize(event);"
+ onclick="return messagePaneOnClick(event);"/>
+ </hbox>
+ </notificationbox>
+ </box>
+ </box>
+ </box>
+ </tabpanels>
+ </tabmail>
+ </hbox>
+
+ <statusbar id="status-bar" class="chromeclass-status mailwindow-statusbar"/>
+</window>
diff --git a/comm/suite/mailnews/content/msgFolderPickerOverlay.js b/comm/suite/mailnews/content/msgFolderPickerOverlay.js
new file mode 100644
index 0000000000..b097cd553e
--- /dev/null
+++ b/comm/suite/mailnews/content/msgFolderPickerOverlay.js
@@ -0,0 +1,100 @@
+/* -*- 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 { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var gMessengerBundle;
+
+// call this from dialog onload() to set the menu item to the correct value
+function MsgFolderPickerOnLoad(pickerID) {
+ var uri = null;
+ try {
+ uri = window.arguments[0].preselectedURI;
+ } catch (ex) {
+ uri = null;
+ }
+
+ if (uri) {
+ // dump("on loading, set titled button to " + uri + "\n");
+
+ // verify that the value we are attempting to
+ // pre-flight the menu with is valid for this
+ // picker type
+ var msgfolder = MailUtils.getExistingFolder(uri);
+ if (!msgfolder) {
+ return;
+ }
+
+ var verifyFunction = null;
+
+ switch (pickerID) {
+ case "msgNewFolderPicker":
+ verifyFunction = msgfolder.canCreateSubfolders;
+ break;
+ case "msgRenameFolderPicker":
+ verifyFunction = msgfolder.canRename;
+ break;
+ default:
+ verifyFunction = msgfolder.canFileMessages;
+ break;
+ }
+
+ if (verifyFunction) {
+ SetFolderPicker(uri, pickerID);
+ }
+ }
+}
+
+function PickedMsgFolder(selection, pickerID) {
+ var selectedUri = selection.getAttribute("id");
+ SetFolderPicker(selectedUri, pickerID);
+}
+
+function SetFolderPickerElement(uri, picker) {
+ var msgfolder = MailUtils.getExistingFolder(uri);
+
+ if (!msgfolder) {
+ return;
+ }
+
+ var selectedValue = null;
+ var serverName;
+
+ if (msgfolder.isServer) {
+ selectedValue = msgfolder.name;
+ } else {
+ if (msgfolder.server) {
+ serverName = msgfolder.server.prettyName;
+ } else {
+ dump("Can't find server for " + uri + "\n");
+ serverName = "???";
+ }
+
+ switch (picker.id) {
+ case "runFiltersFolder":
+ selectedValue = msgfolder.name;
+ break;
+ case "msgTrashFolderPicker":
+ selectedValue = msgfolder.name;
+ break;
+ default:
+ if (!gMessengerBundle) {
+ gMessengerBundle = document.getElementById("bundle_messenger");
+ }
+ selectedValue = gMessengerBundle.getFormattedString(
+ "verboseFolderFormat",
+ [msgfolder.name, serverName]
+ );
+ break;
+ }
+ }
+
+ picker.setAttribute("label", selectedValue);
+ picker.setAttribute("uri", uri);
+}
+
+function SetFolderPicker(uri, pickerID) {
+ SetFolderPickerElement(uri, document.getElementById(pickerID));
+}
diff --git a/comm/suite/mailnews/content/msgHdrViewOverlay.js b/comm/suite/mailnews/content/msgHdrViewOverlay.js
new file mode 100644
index 0000000000..c1f12388e7
--- /dev/null
+++ b/comm/suite/mailnews/content/msgHdrViewOverlay.js
@@ -0,0 +1,1971 @@
+/* -*- 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 {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {GlodaUtils} = ChromeUtils.import("resource:///modules/gloda/utils.js");
+
+
+/* This is where functions related to displaying the headers for a selected message in the
+ message pane live. */
+
+////////////////////////////////////////////////////////////////////////////////////
+// Warning: if you go to modify any of these JS routines please get a code review from
+// scott@scott-macgregor.org. It's critical that the code in here for displaying
+// the message headers for a selected message remain as fast as possible. In particular,
+// right now, we only introduce one reflow per message. i.e. if you click on a message in the thread
+// pane, we batch up all the changes for displaying the header pane (to, cc, attachements button, etc.)
+// and we make a single pass to display them. It's critical that we maintain this one reflow per message
+// view in the message header pane.
+////////////////////////////////////////////////////////////////////////////////////
+
+var gViewAllHeaders = false;
+var gCollectIncoming = false;
+var gCollectOutgoing = false;
+var gCollectNewsgroup = false;
+var gCollapsedHeaderViewMode = false;
+var gCollectAddressTimer = null;
+var gBuildAttachmentsForCurrentMsg = false;
+var gBuildAttachmentPopupForCurrentMsg = true;
+var gBuiltExpandedView = false;
+var gBuiltCollapsedView = false;
+var gMessengerBundle;
+
+// Show the friendly display names for people I know, instead of the name + email address.
+var gShowCondensedEmailAddresses;
+
+var abAddressCollector = null;
+
+// other components may listen to on start header & on end header notifications for each message we display
+// to do that you need to add yourself to our gMessageListeners array with an object that supports the three properties:
+// onStartHeaders, onEndHeaders and onEndAttachments.
+var gMessageListeners = new Array();
+
+// For every possible "view" in the message pane, you need to define the header names you want to
+// see in that view. In addition, include information describing how you want that header field to be
+// presented. i.e. if it's an email address field, if you want a toggle inserted on the node in case
+// of multiple email addresses, etc. We'll then use this static table to dynamically generate header view entries
+// which manipulate the UI.
+// When you add a header to one of these view lists you can specify the following properties:
+// name: the name of the header. i.e. "to", "subject". This must be in lower case and the name of the
+// header is used to help dynamically generate ids for objects in the document. (REQUIRED)
+// useToggle: true if the values for this header are multiple email addresses and you want a
+// a toggle icon to show a short vs. long list (DEFAULT: false)
+// useShortView: (only works on some fields like From). If the field has a long presentation and a
+// short presentation we'll use the short one. i.e. if you are showing the From field and you
+// set this to true, we can show just "John Doe" instead of "John Doe <jdoe@netscape.net>".
+// (DEFAULT: false)
+//
+// outputFunction: this is a method which takes a headerEntry (see the definition below) and a header value
+// This allows you to provide your own methods for actually determining how the header value
+// is displayed. (DEFAULT: updateHeaderValue which just sets the header value on the text node)
+
+// Our first view is the collapsed view. This is very light weight view of the data. We only show a couple
+// fields.
+var gCollapsedHeaderList = [ {name:"subject", outputFunction:updateHeaderValueInTextNode},
+ {name:"from", useToggle:true, useShortView:true, outputFunction:OutputEmailAddresses},
+ {name:"date", outputFunction:updateHeaderValueInTextNode}];
+
+// We also have an expanded header view. This shows many of your more common (and useful) headers.
+var gExpandedHeaderList = [ {name:"subject"},
+ {name:"from", useToggle:true, outputFunction:OutputEmailAddresses},
+ {name:"sender", outputFunction:OutputEmailAddresses},
+ {name:"reply-to", useToggle:true, outputFunction:OutputEmailAddresses},
+ {name:"date"},
+ {name:"to", useToggle:true, outputFunction:OutputEmailAddresses},
+ {name:"cc", useToggle:true, outputFunction:OutputEmailAddresses},
+ {name:"bcc", useToggle:true, outputFunction:OutputEmailAddresses},
+ {name:"newsgroups", outputFunction:OutputNewsgroups},
+ {name:"references", outputFunction:OutputMessageIds},
+ {name:"followup-to", outputFunction:OutputNewsgroups},
+ {name:"content-base"},
+ {name:"tags"} ];
+
+// These are all the items that use a mail-multi-emailHeaderField widget and
+// therefore may require updating if the address book changes.
+const gEmailAddressHeaderNames = ["from", "reply-to", "to", "cc", "bcc"];
+
+// Now, for each view the message pane can generate, we need a global table of headerEntries. These
+// header entry objects are generated dynamically based on the static data in the header lists (see above)
+// and elements we find in the DOM based on properties in the header lists.
+var gCollapsedHeaderView = {};
+var gExpandedHeaderView = {};
+
+// currentHeaderData --> this is an array of header name and value pairs for the currently displayed message.
+// it's purely a data object and has no view information. View information is contained in the view objects.
+// for a given entry in this array you can ask for:
+// .headerName ---> name of the header (i.e. 'to'). Always stored in lower case
+// .headerValue --> value of the header "johndoe@netscape.net"
+var currentHeaderData = {};
+
+// For the currently displayed message, we store all the attachment data. When displaying a particular
+// view, it's up to the view layer to extract this attachment data and turn it into something useful.
+// For a given entry in the attachments list, you can ask for the following properties:
+// .contentType --> the content type of the attachment
+// url --> an imap, or mailbox url which can be used to fetch the message
+// uri --> an RDF URI which refers to the message containig the attachment
+// isExternalAttachment --> boolean flag stating whether the attachment is external or not.
+var currentAttachments = new Array();
+
+const nsIAbDirectory = Ci.nsIAbDirectory;
+const nsIAbListener = Ci.nsIAbListener;
+const nsIAbCard = Ci.nsIAbCard;
+
+// createHeaderEntry --> our constructor method which creates a header Entry
+// based on an entry in one of the header lists. A header entry is different from a header list.
+// a header list just describes how you want a particular header to be presented. The header entry
+// actually has knowledge about the DOM and the actual DOM elements associated with the header.
+// prefix --> the name of the view (i.e. "collapsed", "expanded")
+// headerListInfo --> entry from a header list.
+function createHeaderEntry(prefix, headerListInfo)
+{
+ var useShortView = false;
+ var partialIDName = prefix + headerListInfo.name;
+ this.enclosingBox = document.getElementById(partialIDName + 'Box');
+ this.textNode = document.getElementById(partialIDName + 'Value');
+ this.isNewHeader = false;
+ this.isValid = false;
+
+ if ("useShortView" in headerListInfo)
+ {
+ useShortView = headerListInfo.useShortView;
+ if (useShortView)
+ this.enclosingBox = this.textNode;
+ else
+ this.enclosingBox.emailAddressNode = this.textNode;
+ }
+
+ if ("useToggle" in headerListInfo)
+ {
+ this.useToggle = headerListInfo.useToggle;
+ if (this.useToggle) // find the toggle icon in the document
+ {
+ this.toggleIcon = this.enclosingBox.toggleIcon;
+ this.longTextNode = this.enclosingBox.longEmailAddresses;
+ this.textNode = this.enclosingBox.emailAddresses;
+ }
+ }
+ else
+ this.useToggle = false;
+
+ if (this.textNode)
+ this.textNode.useShortView = useShortView;
+
+ if ("outputFunction" in headerListInfo)
+ this.outputFunction = headerListInfo.outputFunction;
+ else
+ this.outputFunction = updateHeaderValue;
+
+ // Stash this so that the <mail-multi-emailheaderfield/> binding can
+ // later attach it to any <mail-emailaddress> tags it creates for later
+ // extraction and use by UpdateEmailNodeDetails.
+ this.enclosingBox.headerName = headerListInfo.name;
+
+}
+
+function initializeHeaderViewTables()
+{
+ // iterate over each header in our header list arrays and create header entries
+ // for each one. These header entries are then stored in the appropriate header table
+ for (let index = 0; index < gCollapsedHeaderList.length; index++)
+ {
+ gCollapsedHeaderView[gCollapsedHeaderList[index].name] =
+ new createHeaderEntry('collapsed', gCollapsedHeaderList[index]);
+ }
+
+ for (let index = 0; index < gExpandedHeaderList.length; index++)
+ {
+ var headerName = gExpandedHeaderList[index].name;
+ gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', gExpandedHeaderList[index]);
+ }
+
+ var extraHeaders = Services.prefs.getCharPref("mailnews.headers.extraExpandedHeaders").match(/[^ ]+/g);
+ if (extraHeaders) {
+ for (let index = 0; index < extraHeaders.length; index++)
+ {
+ let extraHeader = extraHeaders[index];
+ gExpandedHeaderView[extraHeader.toLowerCase()] = new createNewHeaderView(extraHeader, extraHeader + ':');
+ }
+ }
+
+ if (Services.prefs.getBoolPref("mailnews.headers.showOrganization"))
+ {
+ let organizationEntry = {name:"organization", outputFunction:updateHeaderValue};
+ gExpandedHeaderView[organizationEntry.name] = new createHeaderEntry('expanded', organizationEntry);
+ }
+
+ if (Services.prefs.getBoolPref("mailnews.headers.showUserAgent"))
+ {
+ let userAgentEntry = {name:"user-agent", outputFunction:updateHeaderValue};
+ gExpandedHeaderView[userAgentEntry.name] = new createHeaderEntry('expanded', userAgentEntry);
+ }
+
+ if (Services.prefs.getBoolPref("mailnews.headers.showMessageId"))
+ {
+ let messageIdEntry = {name:"message-id", outputFunction:OutputMessageIds};
+ gExpandedHeaderView[messageIdEntry.name] = new createHeaderEntry('expanded', messageIdEntry);
+ }
+}
+
+function OnLoadMsgHeaderPane()
+{
+ // load any preferences that at are global with regards to
+ // displaying a message...
+ gCollectIncoming = Services.prefs.getBoolPref("mail.collect_email_address_incoming");
+ gCollectNewsgroup = Services.prefs.getBoolPref("mail.collect_email_address_newsgroup");
+ gCollectOutgoing = Services.prefs.getBoolPref("mail.collect_email_address_outgoing");
+ gShowCondensedEmailAddresses = Services.prefs.getBoolPref("mail.showCondensedAddresses");
+
+ Services.prefs.addObserver("mail.showCondensedAddresses", MsgHdrViewObserver);
+ Services.prefs.addObserver("mail.show_headers", MsgHdrViewObserver);
+ Services.prefs.addObserver("mailnews.display.html_as", MsgHdrViewObserver);
+ Services.prefs.addObserver("mail.inline_attachments", MsgHdrViewObserver);
+
+ initializeHeaderViewTables();
+
+ // Add an address book listener so we can update the header view when things
+ // change.
+ MailServices.ab.addAddressBookListener(AddressBookListener,
+ Ci.nsIAbListener.all);
+
+ var toggleHeaderView = GetHeaderPane();
+ var initialCollapsedSetting = toggleHeaderView.getAttribute("state");
+ if (initialCollapsedSetting == "true")
+ gCollapsedHeaderViewMode = true;
+
+ // dispatch an event letting any listeners know that we have loaded the message pane
+ toggleHeaderView.dispatchEvent(new Event('messagepane-loaded',
+ { bubbles: false, cancelable: true }));
+}
+
+function OnUnloadMsgHeaderPane()
+{
+ Services.prefs.removeObserver("mail.showCondensedAddresses", MsgHdrViewObserver);
+ Services.prefs.removeObserver("mail.show_headers", MsgHdrViewObserver);
+ Services.prefs.removeObserver("mailnews.display.html_as", MsgHdrViewObserver);
+ Services.prefs.removeObserver("mail.inline_attachments", MsgHdrViewObserver);
+
+ MailServices.ab.removeAddressBookListener(AddressBookListener);
+
+ // dispatch an event letting any listeners know that we have unloaded the message pane
+ GetHeaderPane().dispatchEvent(new Event('messagepane-unloaded',
+ { bubbles: false, cancelable: true }));
+}
+
+var MsgHdrViewObserver = {
+ observe: function(subject, topic, prefName) {
+ // Verify that we're changing mail pane config prefs.
+ if (topic == "nsPref:changed") {
+ if (prefName == "mail.showCondensedAddresses") {
+ gShowCondensedEmailAddresses =
+ Services.prefs.getBoolPref("mail.showCondensedAddresses");
+ ReloadMessage();
+ } else if (prefName == "mail.show_headers" ||
+ prefName == "mailnews.display.html_as" ||
+ prefName == "mail.inline_attachments") {
+ ReloadMessage();
+ }
+ }
+ }
+};
+
+var AddressBookListener =
+{
+ onItemAdded: function(aParentDir, aItem) {
+ OnAddressBookDataChanged(nsIAbListener.itemAdded,
+ aParentDir, aItem);
+ },
+ onItemRemoved: function(aParentDir, aItem) {
+ OnAddressBookDataChanged(aItem instanceof nsIAbCard ?
+ nsIAbListener.directoryItemRemoved :
+ nsIAbListener.directoryRemoved,
+ aParentDir, aItem);
+ },
+ onItemPropertyChanged: function(aItem, aProperty, aOldValue, aNewValue) {
+ // We only need updates for card changes, address book and mailing list
+ // ones don't affect us here.
+ if (aItem instanceof nsIAbCard)
+ OnAddressBookDataChanged(nsIAbListener.itemChanged, null, aItem);
+ }
+};
+
+function OnAddressBookDataChanged(aAction, aParentDir, aItem)
+{
+ gEmailAddressHeaderNames.forEach(function (aHeaderName)
+ {
+ var headerEntry = null;
+
+ // Ensure both collapsed and expanded are updated in case we toggle
+ // between the two.
+ if (aHeaderName in gCollapsedHeaderView)
+ {
+ headerEntry = gCollapsedHeaderView[aHeaderName];
+ if (headerEntry)
+ headerEntry.enclosingBox.updateExtraAddressProcessing(aAction,
+ aParentDir,
+ aItem);
+ }
+ if (aHeaderName in gExpandedHeaderView)
+ {
+ headerEntry = gExpandedHeaderView[aHeaderName];
+ if (headerEntry)
+ headerEntry.enclosingBox.updateExtraAddressProcessing(aAction,
+ aParentDir,
+ aItem);
+ }
+ });
+}
+
+// The messageHeaderSink is the class that gets notified of a message's headers as we display the message
+// through our mime converter.
+
+var messageHeaderSink = {
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIMsgHeaderSink]),
+ onStartHeaders: function()
+ {
+ this.mSaveHdr = null;
+ // clear out any pending collected address timers...
+ if (gCollectAddressTimer)
+ {
+ clearTimeout(gCollectAddressTimer);
+ gCollectAddressTimer = null;
+ }
+
+ // every time we start to redisplay a message, check the view all headers pref....
+ var showAllHeadersPref = Services.prefs.getIntPref("mail.show_headers");
+ if (showAllHeadersPref == 2)
+ {
+ gViewAllHeaders = true;
+ }
+ else
+ {
+ if (gViewAllHeaders) // if we currently are in view all header mode, rebuild our header view so we remove most of the header data
+ {
+ hideHeaderView(gExpandedHeaderView);
+ RemoveNewHeaderViews(gExpandedHeaderView);
+ gExpandedHeaderView = {};
+ initializeHeaderViewTables();
+ }
+
+ gViewAllHeaders = false;
+ }
+
+ ClearCurrentHeaders();
+ gBuiltExpandedView = false;
+ gBuiltCollapsedView = false;
+ gBuildAttachmentsForCurrentMsg = false;
+ gBuildAttachmentPopupForCurrentMsg = true;
+ ClearAttachmentList();
+ ClearEditMessageBox("editDraftBox");
+ ClearEditMessageBox("editTemplateBox");
+ gMessageNotificationBar.clearMsgNotifications();
+
+ for (let index in gMessageListeners)
+ gMessageListeners[index].onStartHeaders();
+ },
+
+ onEndHeaders: function()
+ {
+ ClearHeaderView(gCollapsedHeaderView);
+ ClearHeaderView(gExpandedHeaderView);
+
+ EnsureSubjectValue(); // make sure there is a subject even if it's empty so we'll show the subject and the twisty
+
+ // Load feed web page if so configured. This entry point works for
+ // messagepane loads in 3pane folder tab, 3pane message tab, and the
+ // standalone message window.
+ if (!FeedMessageHandler.shouldShowSummary(gMessageDisplay.displayedMessage, false))
+ FeedMessageHandler.setContent(gMessageDisplay.displayedMessage, false);
+
+ ShowMessageHeaderPane();
+ UpdateMessageHeaders();
+ ShowEditMessageBox("editDraftBox", Ci.nsMsgFolderFlags.Drafts);
+ ShowEditMessageBox("editTemplateBox", Ci.nsMsgFolderFlags.Templates);
+
+ for (let index in gMessageListeners)
+ gMessageListeners[index].onEndHeaders();
+ },
+
+ processHeaders: function(headerNameEnumerator, headerValueEnumerator, dontCollectAddress)
+ {
+ this.onStartHeaders();
+
+ const kMailboxSeparator = ", ";
+ var index = 0;
+ while (headerNameEnumerator.hasMore())
+ {
+ var header = new Object;
+ header.headerValue = headerValueEnumerator.getNext();
+ header.headerName = headerNameEnumerator.getNext();
+
+ // For consistency's sake, let us force all header names to be lower
+ // case so we don't have to worry about looking for: Cc and CC, etc.
+ var lowerCaseHeaderName = header.headerName.toLowerCase();
+
+ // If we have an x-mailer, x-mimeole, or x-newsreader string,
+ // put it in the user-agent slot which we know how to handle already.
+ if (/^x-(mailer|mimeole|newsreader)$/.test(lowerCaseHeaderName))
+ lowerCaseHeaderName = "user-agent";
+
+ if (this.mDummyMsgHeader)
+ {
+ if (lowerCaseHeaderName == "from")
+ this.mDummyMsgHeader.author = header.headerValue;
+ else if (lowerCaseHeaderName == "to")
+ this.mDummyMsgHeader.recipients = header.headerValue;
+ else if (lowerCaseHeaderName == "cc")
+ this.mDummyMsgHeader.ccList = header.headerValue;
+ else if (lowerCaseHeaderName == "subject")
+ this.mDummyMsgHeader.subject = header.headerValue;
+ else if (lowerCaseHeaderName == "reply-to")
+ this.mDummyMsgHeader.replyTo = header.headerValue;
+ else if (lowerCaseHeaderName == "message-id")
+ this.mDummyMsgHeader.messageId = header.headerValue;
+ else if (lowerCaseHeaderName == "list-post")
+ this.mDummyMsgHeader.listPost = header.headerValue;
+ else if (lowerCaseHeaderName == "date")
+ this.mDummyMsgHeader.date = Date.parse(header.headerValue) * 1000;
+ }
+
+ // We emit both the original, raw date header and a localized version.
+ // Pretend that the localized version is the real version.
+ if (lowerCaseHeaderName == "date")
+ continue;
+ if (lowerCaseHeaderName == "x-mozilla-localizeddate")
+ {
+ lowerCaseHeaderName = "date";
+ header.headerName = "Date";
+ }
+
+ // according to RFC 2822, certain headers
+ // can occur "unlimited" times
+ if (lowerCaseHeaderName in currentHeaderData)
+ {
+ // sometimes, you can have multiple To or Cc lines....
+ // in this case, we want to append these headers into one.
+ if (lowerCaseHeaderName == 'to' || lowerCaseHeaderName == 'cc')
+ currentHeaderData[lowerCaseHeaderName].headerValue = currentHeaderData[lowerCaseHeaderName].headerValue + ',' + header.headerValue;
+ else
+ {
+ // use the index to create a unique header name like:
+ // received5, received6, etc
+ currentHeaderData[lowerCaseHeaderName + index++] = header;
+ }
+ }
+ else
+ currentHeaderData[lowerCaseHeaderName] = header;
+
+ if (lowerCaseHeaderName == "from")
+ {
+ if (header.headerValue)
+ {
+ try
+ {
+ var createCard = (gCollectIncoming && !dontCollectAddress) || (gCollectNewsgroup && dontCollectAddress);
+ if (createCard || gCollectOutgoing)
+ {
+ // collect, add card if doesn't exist and gCollectOutgoing is set,
+ // otherwise only update existing cards, unknown preferred send format
+ gCollectAddressTimer = setTimeout(collectAddresses,
+ 2000,
+ header.headerValue,
+ createCard);
+ }
+ }
+ catch(ex) {}
+ }
+ } // if lowerCaseHeaderName == "from"
+ } // while we have more headers to parse
+
+ // process message tags as if they were headers in the message
+ SetTagHeader();
+
+ if (("from" in currentHeaderData) && ("sender" in currentHeaderData))
+ {
+ var senderMailbox = kMailboxSeparator +
+ MailServices.headerParser.extractHeaderAddressMailboxes(
+ currentHeaderData.sender.headerValue) + kMailboxSeparator;
+ var fromMailboxes = kMailboxSeparator +
+ MailServices.headerParser.extractHeaderAddressMailboxes(
+ currentHeaderData.from.headerValue) + kMailboxSeparator;
+ if (fromMailboxes.includes(senderMailbox))
+ delete currentHeaderData.sender;
+ }
+
+ this.onEndHeaders();
+ },
+
+ handleAttachment: function(contentType, url, displayName, uri,
+ isExternalAttachment)
+ {
+ this.skipAttachment = true;
+
+ // Don't show vcards as external attachments in the UI. libmime already
+ // renders them inline.
+ try
+ {
+ if (!this.mSaveHdr)
+ this.mSaveHdr = messenger.msgHdrFromURI(uri);
+ }
+ catch (ex) {}
+ if (contentType == "text/x-vcard")
+ {
+ var inlineAttachments = Services.prefs.getBoolPref("mail.inline_attachments");
+ var displayHtmlAs = Services.prefs.getIntPref("mailnews.display.html_as");
+ if (inlineAttachments && !displayHtmlAs)
+ return;
+ }
+
+ var size = null;
+ if (isExternalAttachment)
+ {
+ var file = GetFileFromString(url);
+ if (file && file.exists())
+ size = file.fileSize;
+ else
+ dump("Couldn't open external attachment!");
+ }
+
+ currentAttachments.push(new createNewAttachmentInfo(contentType,
+ url,
+ displayName,
+ uri,
+ isExternalAttachment,
+ size));
+ this.skipAttachment = false;
+
+ // If we have an attachment, set the nsMsgMessageFlags.Attachment flag
+ // on the hdr to cause the "message with attachment" icon to show up
+ // in the thread pane.
+ // We only need to do this on the first attachment.
+ var numAttachments = currentAttachments.length;
+ if (numAttachments == 1) {
+ // we also have to enable the Message/Attachments menuitem
+ var node = document.getElementById("msgAttachmentMenu");
+ if (node)
+ node.removeAttribute("disabled");
+
+ try {
+ // convert the uri into a hdr
+ this.mSaveHdr.markHasAttachments(true);
+ }
+ catch (ex) {
+ dump("ex = " + ex + "\n");
+ }
+ }
+ },
+
+ addAttachmentField: function(aField, aValue)
+ {
+ if (this.skipAttachment)
+ return;
+
+ let last = currentAttachments[currentAttachments.length - 1];
+ if (aField == "X-Mozilla-PartSize" && !last.isExternalAttachment &&
+ last.contentType != "text/x-moz-deleted")
+ {
+ let size = parseInt(aValue);
+ // libmime returns -1 if it never managed to figure out the size.
+ if (size != -1)
+ last.size = size;
+ }
+ else if (aField == "X-Mozilla-PartDownloaded" && aValue == "0")
+ {
+ // We haven't downloaded the attachment, so any size we get from
+ // libmime is almost certainly inaccurate. Just get rid of it. (Note:
+ // this relies on the fact that PartDownloaded comes after PartSize from
+ // the MIME emitter.)
+ last.size = null;
+ }
+ },
+
+ onEndAllAttachments: function()
+ {
+ // AddSaveAllAttachmentsMenu();
+ if (gCollapsedHeaderViewMode)
+ displayAttachmentsForCollapsedView();
+ else
+ displayAttachmentsForExpandedView();
+
+ for (let index in gMessageListeners) {
+ if ("onEndAttachments" in gMessageListeners[index])
+ gMessageListeners[index].onEndAttachments();
+ }
+ },
+
+ onEndMsgDownload: function(url)
+ {
+ // if we don't have any attachments, turn off the attachments flag
+ if (!this.mSaveHdr)
+ {
+ var messageUrl = url.QueryInterface(Ci.nsIMsgMessageUrl);
+ try
+ {
+ this.mSaveHdr = messenger.msgHdrFromURI(messageUrl.uri);
+ }
+ catch (ex) {}
+
+ }
+ if (!currentAttachments.length && this.mSaveHdr)
+ this.mSaveHdr.markHasAttachments(false);
+
+ let browser = getBrowser();
+ if (currentAttachments.length &&
+ Services.prefs.getBoolPref("mail.inline_attachments") &&
+ this.mSaveHdr && gFolderDisplay.selectedMessageIsFeed &&
+ browser && browser.contentDocument && browser.contentDocument.body) {
+ for (let img of browser.contentDocument.body.getElementsByClassName("moz-attached-image")) {
+ for (let attachment of currentAttachments) {
+ let partID = img.src.split("&part=")[1];
+ partID = partID ? partID.split("&")[0] : null;
+ if (attachment.partID && partID == attachment.partID) {
+ img.src = attachment.url;
+ break;
+ }
+ }
+ }
+ }
+
+ OnMsgParsed(url);
+ },
+
+ onEndMsgHeaders: function(url)
+ {
+ OnMsgLoaded(url);
+ },
+
+ onMsgHasRemoteContent: function(aMsgHdr, aContentURI, aCanOverride)
+ {
+ gMessageNotificationBar.setRemoteContentMsg(aMsgHdr, aContentURI, aCanOverride);
+ },
+
+ mSecurityInfo : null,
+ mSaveHdr: null,
+ get securityInfo()
+ {
+ return this.mSecurityInfo;
+ },
+ set securityInfo(aSecurityInfo)
+ {
+ this.mSecurityInfo = aSecurityInfo;
+ },
+
+ mDummyMsgHeader: null,
+
+ get dummyMsgHeader()
+ {
+ if (!this.mDummyMsgHeader)
+ this.mDummyMsgHeader = new nsDummyMsgHeader();
+ return this.mDummyMsgHeader;
+ },
+ mProperties: null,
+ get properties()
+ {
+ if (!this.mProperties)
+ this.mProperties = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag2);
+ return this.mProperties;
+ },
+
+ resetProperties: function()
+ {
+ this.mProperties = null;
+ }
+};
+
+// Private method which generates a space delimited list of tag keys for the
+// current message. This list is then stored in currentHeaderData["tags"].
+function SetTagHeader()
+{
+ // it would be nice if we passed in the msgHdr from the back end
+ var msgHdr;
+ try
+ {
+ msgHdr = gDBView.hdrForFirstSelectedMessage;
+ }
+ catch (ex)
+ {
+ return; // no msgHdr to add our tags to
+ }
+
+ // get the list of known tags
+ var tagArray = MailServices.tags.getAllTags();
+ var tagKeys = {};
+ for (var tagInfo of tagArray)
+ if (tagInfo.tag)
+ tagKeys[tagInfo.key] = true;
+
+ // extract the tag keys from the msgHdr
+ var msgKeyArray = msgHdr.getStringProperty("keywords").split(" ");
+
+ // attach legacy label to the front if not already there
+ var label = msgHdr.label;
+ if (label)
+ {
+ var labelKey = "$label" + label;
+ if (!msgKeyArray.includes(labelKey))
+ msgKeyArray.unshift(labelKey);
+ }
+
+ // Rebuild the keywords string with just the keys that are actual tags or
+ // legacy labels and not other keywords like Junk and NonJunk.
+ // Retain their order, though, with the label as oldest element.
+ for (let i = msgKeyArray.length - 1; i >= 0; --i)
+ if (!(msgKeyArray[i] in tagKeys))
+ msgKeyArray.splice(i, 1); // remove non-tag key
+ var msgKeys = msgKeyArray.join(" ");
+
+ if (msgKeys)
+ currentHeaderData.tags = {headerName: "tags", headerValue: msgKeys};
+ else // no more tags, so clear out the header field
+ delete currentHeaderData.tags;
+}
+
+function EnsureSubjectValue()
+{
+ if (!('subject' in currentHeaderData))
+ {
+ var foo = new Object;
+ foo.headerValue = "";
+ foo.headerName = 'subject';
+ currentHeaderData[foo.headerName] = foo;
+ }
+}
+
+// Private method used by messageHeaderSink::processHeaders.
+function collectAddresses(aAddresses, aCreateCard)
+{
+ if (!abAddressCollector)
+ abAddressCollector = Cc["@mozilla.org/addressbook/services/addressCollector;1"]
+ .getService(Ci.nsIAbAddressCollector);
+ var sendFormat = Ci.nsIAbPreferMailFormat.unknown;
+ abAddressCollector.collectAddress(aAddresses, aCreateCard, sendFormat);
+}
+
+// Public method called by the tag front end code when the tags for the selected
+// message has changed.
+function OnTagsChange()
+{
+ // rebuild the tag headers
+ SetTagHeader();
+
+ // now update the expanded header view to rebuild the tags,
+ // and then show or hide the tag header box.
+ if (gBuiltExpandedView)
+ {
+ var headerEntry = gExpandedHeaderView.tags;
+ if (headerEntry)
+ {
+ headerEntry.valid = ("tags" in currentHeaderData);
+ if (headerEntry.valid)
+ headerEntry.outputFunction(headerEntry, currentHeaderData.tags.headerValue);
+
+ // if we are showing the expanded header view then we may need to collapse or
+ // show the tag header box...
+ if (!gCollapsedHeaderViewMode)
+ headerEntry.enclosingBox.collapsed = !headerEntry.valid;
+ }
+ }
+}
+
+// flush out any local state being held by a header entry for a given
+// table
+function ClearHeaderView(headerTable)
+{
+ for (let index in headerTable)
+ {
+ let headerEntry = headerTable[index];
+ if (headerEntry.useToggle)
+ {
+ headerEntry.enclosingBox.clearHeaderValues();
+ }
+
+ headerEntry.valid = false;
+ }
+}
+
+// make sure that any valid header entry in the table is collapsed
+function hideHeaderView(headerTable)
+{
+ for (let index in headerTable)
+ {
+ headerTable[index].enclosingBox.collapsed = true;
+ }
+}
+
+// make sure that any valid header entry in the table specified is
+// visible
+function showHeaderView(headerTable)
+{
+ for (let index in headerTable)
+ {
+ let headerEntry = headerTable[index];
+ if (headerEntry.valid)
+ {
+ headerEntry.enclosingBox.collapsed = false;
+ }
+ else // if the entry is invalid, always make sure it's collapsed
+ headerEntry.enclosingBox.collapsed = true;
+ }
+}
+
+// make sure the appropriate fields within the currently displayed view header mode
+// are collapsed or visible...
+function updateHeaderViews()
+{
+ if (gCollapsedHeaderViewMode)
+ {
+ showHeaderView(gCollapsedHeaderView);
+ displayAttachmentsForCollapsedView();
+ }
+ else
+ {
+ showHeaderView(gExpandedHeaderView);
+ displayAttachmentsForExpandedView();
+ }
+}
+
+function ToggleHeaderView()
+{
+ var expandedNode = document.getElementById("expandedHeaderView");
+ var collapsedNode = document.getElementById("collapsedHeaderView");
+
+ if (gCollapsedHeaderViewMode)
+ {
+ gCollapsedHeaderViewMode = false;
+ // hide the current view
+ hideHeaderView(gCollapsedHeaderView);
+ // update the current view
+ UpdateMessageHeaders();
+
+ // now uncollapse / collapse the right views
+ expandedNode.collapsed = false;
+ collapsedNode.collapsed = true;
+ }
+ else
+ {
+ gCollapsedHeaderViewMode = true;
+ // hide the current view
+ hideHeaderView(gExpandedHeaderView);
+ // update the current view
+ UpdateMessageHeaders();
+
+ // now uncollapse / collapse the right views
+ collapsedNode.collapsed = false;
+ expandedNode.collapsed = true;
+ }
+
+ var toggleHeaderView = GetHeaderPane();
+ if (gCollapsedHeaderViewMode)
+ toggleHeaderView.setAttribute("state", "true");
+ else
+ toggleHeaderView.setAttribute("state", "false");
+}
+
+// default method for updating a header value into a header entry
+function updateHeaderValue(headerEntry, headerValue)
+{
+ headerEntry.enclosingBox.headerValue = headerValue;
+}
+
+function updateHeaderValueInTextNode(headerEntry, headerValue)
+{
+ headerEntry.textNode.value = headerValue;
+}
+
+function createNewHeaderView(headerName, label)
+{
+ var idName = 'expanded' + headerName + 'Box';
+ var newHeader = document.createElement("mail-headerfield");
+
+ newHeader.setAttribute('id', idName);
+ newHeader.setAttribute('label', label);
+ // all mail-headerfield elements are keyword related
+ newHeader.setAttribute('keywordrelated','true');
+ newHeader.collapsed = true;
+
+ // this new element needs to be inserted into the view...
+ var topViewNode = document.getElementById('expandedHeaders');
+
+ topViewNode.appendChild(newHeader);
+
+ this.enclosingBox = newHeader;
+ this.isNewHeader = true;
+ this.isValid = false;
+ this.useToggle = false;
+ this.outputFunction = updateHeaderValue;
+}
+
+/**
+ * Removes all non-predefined header nodes from the view.
+ *
+ * @param aHeaderTable Table of header entries.
+ */
+function RemoveNewHeaderViews(aHeaderTable)
+{
+ for (let index in aHeaderTable)
+ {
+ let headerEntry = aHeaderTable[index];
+ if (headerEntry.isNewHeader)
+ headerEntry.enclosingBox.remove();
+ }
+}
+
+// UpdateMessageHeaders: Iterate through all the current header data we received from mime for this message
+// for each header entry table, see if we have a corresponding entry for that header. i.e. does the particular
+// view care about this header value. if it does then call updateHeaderEntry
+function UpdateMessageHeaders()
+{
+ // iterate over each header we received and see if we have a matching entry in each
+ // header view table...
+
+ for (let headerName in currentHeaderData)
+ {
+ let headerField = currentHeaderData[headerName];
+ let headerEntry = null;
+
+ if (headerName == "subject")
+ {
+ try {
+ if (gDBView.keyForFirstSelectedMessage == nsMsgKey_None)
+ {
+ let folder = null;
+ if (gCurrentFolderUri)
+ folder = MailUtils.getFolderForURI(gCurrentFolderUri);
+ setTitleFromFolder(folder, headerField.headerValue);
+ }
+ } catch (ex) {}
+ }
+
+ if (gCollapsedHeaderViewMode && !gBuiltCollapsedView)
+ {
+ if (headerName in gCollapsedHeaderView)
+ headerEntry = gCollapsedHeaderView[headerName];
+ }
+ else if (!gCollapsedHeaderViewMode && !gBuiltExpandedView)
+ {
+ if (headerName in gExpandedHeaderView)
+ headerEntry = gExpandedHeaderView[headerName];
+
+ if (!headerEntry && gViewAllHeaders)
+ {
+ // for view all headers, if we don't have a header field for this value....cheat and create one....then
+ // fill in a headerEntry
+ if (headerName == "message-id" || headerName == "in-reply-to")
+ {
+ let messageIdEntry = {name:headerName, outputFunction:OutputMessageIds};
+ gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', messageIdEntry);
+ }
+ else
+ {
+ gExpandedHeaderView[headerName] = new createNewHeaderView(headerName,
+ currentHeaderData[headerName].headerName + ':');
+ }
+
+ headerEntry = gExpandedHeaderView[headerName];
+ }
+ } // if we are in expanded view....
+
+ if (headerEntry)
+ {
+ let show = Services.prefs.getBoolPref("mailnews.headers.showReferences");
+ if (headerName == "references" &&
+ !(gViewAllHeaders || show ||
+ (gDBView.msgFolder && gDBView.msgFolder.server.type == "nntp")))
+ {
+ // hide references header if view all headers mode isn't selected, the pref show references is
+ // deactivated and the currently displayed message isn't a newsgroup posting
+ headerEntry.valid = false;
+ }
+ else
+ {
+ headerEntry.outputFunction(headerEntry, headerField.headerValue);
+ headerEntry.valid = true;
+ }
+ }
+ }
+
+ if (gCollapsedHeaderViewMode)
+ gBuiltCollapsedView = true;
+ else
+ gBuiltExpandedView = true;
+
+ // now update the view to make sure the right elements are visible
+ updateHeaderViews();
+}
+
+function ClearCurrentHeaders()
+{
+ currentHeaderData = {};
+ currentAttachments = new Array();
+}
+
+function IsListPost()
+{
+ if ("list-post" in currentHeaderData)
+ return /<mailto:.+@.+>/.test(currentHeaderData["list-post"].headerValue);
+
+ return false;
+}
+
+function ShowMessageHeaderPane()
+{
+ var node;
+ if (gCollapsedHeaderViewMode)
+ {
+ node = document.getElementById("collapsedHeaderView");
+ if (node)
+ node.collapsed = false;
+ }
+ else
+ {
+ node = document.getElementById("expandedHeaderView");
+ if (node)
+ node.collapsed = false;
+ }
+
+ /* workaround for 39655 */
+ if (gFolderJustSwitched)
+ {
+ let el = GetHeaderPane();
+ el.setAttribute("style", el.getAttribute("style"));
+ gFolderJustSwitched = false;
+ }
+
+ document.commandDispatcher.updateCommands("message-header-pane");
+}
+
+function HideMessageHeaderPane()
+{
+ var node = document.getElementById("collapsedHeaderView");
+ if (node)
+ node.collapsed = true;
+
+ node = document.getElementById("expandedHeaderView");
+ if (node)
+ node.collapsed = true;
+
+ // we also have to disable the Message/Attachments menuitem
+ node = document.getElementById("msgAttachmentMenu");
+ if (node)
+ node.setAttribute("disabled", "true");
+
+ document.commandDispatcher.updateCommands("message-header-pane");
+}
+
+function OutputNewsgroups(headerEntry, headerValue)
+{
+ headerValue = headerValue.replace(/,/g,", ");
+ updateHeaderValue(headerEntry, headerValue);
+}
+
+// take string of message-ids separated by whitespace, split it
+// into message-ids and send them together with the index number
+// to the corresponding mail-messageids-headerfield element
+function OutputMessageIds(headerEntry, headerValue)
+{
+ var messageIdArray = headerValue.split(/\s+/);
+
+ headerEntry.enclosingBox.clearHeaderValues();
+ for (let i = 0; i < messageIdArray.length; i++)
+ headerEntry.enclosingBox.addMessageIdView(messageIdArray[i]);
+
+ headerEntry.enclosingBox.fillMessageIdNodes();
+}
+
+// OutputEmailAddresses --> knows how to take a comma separated list of email addresses,
+// extracts them one by one, linkifying each email address into a mailto url.
+// Then we add the link-ified email address to the parentDiv passed in.
+//
+// emailAddresses --> comma separated list of the addresses for this header field
+
+function OutputEmailAddresses(headerEntry, emailAddresses)
+{
+ if (!emailAddresses)
+ return;
+
+ // The email addresses are still RFC2047 encoded but libmime has already
+ // converted from "raw UTF-8" to "wide" (UTF-16) characters.
+ var addresses =
+ MailServices.headerParser.parseEncodedHeaderW(emailAddresses);
+
+ for (let addr of addresses) {
+ // If we want to include short/long toggle views and we have a long view,
+ // always add it. If we aren't including a short/long view OR if we are and
+ // we haven't parsed enough addresses to reach the cutoff valve yet then
+ // add it to the default (short) div.
+ let address = {};
+ address.emailAddress = addr.email || "";
+ address.fullAddress = addr.toString() || "";
+ address.displayName = addr.name || "";
+ if (headerEntry.useToggle)
+ headerEntry.enclosingBox.addAddressView(address);
+ else
+ updateEmailAddressNode(headerEntry.enclosingBox.emailAddressNode,
+ address);
+ }
+
+ if (headerEntry.useToggle)
+ headerEntry.enclosingBox.buildViews();
+}
+
+function updateEmailAddressNode(emailAddressNode, address)
+{
+ emailAddressNode.setAttribute("emailAddress", address.emailAddress);
+ emailAddressNode.setAttribute("fullAddress", address.fullAddress);
+ emailAddressNode.setAttribute("displayName", address.displayName);
+
+ UpdateEmailNodeDetails(address.emailAddress, emailAddressNode);
+}
+
+function UpdateEmailNodeDetails(aEmailAddress, aDocumentNode, aCardDetails)
+{
+ // If we haven't been given specific details, search for a card.
+ var cardDetails = aCardDetails || GetCardForEmail(aEmailAddress);
+ aDocumentNode.cardDetails = cardDetails;
+
+ var condense = gShowCondensedEmailAddresses;
+ // Get the id of the mail-multi-emailHeaderField binding parent.
+ var parentElementId = aDocumentNode.parentNode.parentNode.parentNode.id;
+ // Don't condense the address for the from and reply-to fields.
+ // Ids: "collapsedfromValue", "expandedfromBox", "expandedreply-toBox".
+ if (/^(collapsedfromValue|expanded(from|reply-to)Box)$/.test(parentElementId))
+ condense = false;
+
+ var displayName = "";
+ if (condense && cardDetails.card)
+ {
+ if (cardDetails.card.getProperty("PreferDisplayName", true) != true)
+ displayName = aDocumentNode.getAttribute("displayName");
+ if (!displayName)
+ displayName = cardDetails.card.displayName;
+ }
+
+ if (displayName)
+ {
+ aDocumentNode.setAttribute("tooltiptext", aEmailAddress);
+ }
+ else
+ {
+ aDocumentNode.removeAttribute("tooltiptext");
+ displayName = aDocumentNode.getAttribute("fullAddress") ||
+ aDocumentNode.getAttribute("displayName");
+ }
+
+ aDocumentNode.setAttribute("label", displayName);
+}
+
+function UpdateExtraAddressProcessing(aAddressData, aDocumentNode, aAction,
+ aParentDir, aItem)
+{
+ switch (aAction)
+ {
+ case nsIAbListener.itemChanged:
+ if (aAddressData &&
+ aDocumentNode.cardDetails.card &&
+ aItem.hasEmailAddress(aAddressData.emailAddress)) {
+ aDocumentNode.cardDetails.card = aItem;
+ UpdateEmailNodeDetails(aAddressData.emailAddress, aDocumentNode,
+ aDocumentNode.cardDetails);
+ }
+ break;
+ case nsIAbListener.itemAdded:
+ // Is it a new address book?
+ if (aItem instanceof nsIAbDirectory)
+ {
+ // If we don't have a match, search again for updates (e.g. a interface
+ // to an existing book may just have been added).
+ if (!aDocumentNode.cardDetails.card)
+ UpdateEmailNodeDetails(aAddressData.emailAddress, aDocumentNode);
+ }
+ else if (aItem instanceof nsIAbCard)
+ {
+ // If we don't have a card, does this new one match?
+ if (!aDocumentNode.cardDetails.card &&
+ aItem.hasEmailAddress(aAddressData.emailAddress))
+ {
+ // Just in case we have a bogus parent directory.
+ if (aParentDir instanceof nsIAbDirectory)
+ {
+ let cardDetails = { book: aParentDir, card: aItem };
+ UpdateEmailNodeDetails(aAddressData.emailAddress, aDocumentNode,
+ cardDetails);
+ }
+ else
+ {
+ UpdateEmailNodeDetails(aAddressData.emailAddress, aDocumentNode);
+ }
+ }
+ }
+ break;
+ case nsIAbListener.directoryItemRemoved:
+ // Unfortunately we don't necessarily get the same card object back.
+ if (aAddressData &&
+ aDocumentNode.cardDetails.card &&
+ aDocumentNode.cardDetails.book == aParentDir &&
+ aItem.hasEmailAddress(aAddressData.emailAddress))
+ {
+ UpdateEmailNodeDetails(aAddressData.emailAddress, aDocumentNode);
+ }
+ break;
+ case nsIAbListener.directoryRemoved:
+ if (aDocumentNode.cardDetails.book == aItem)
+ UpdateEmailNodeDetails(aAddressData.emailAddress, aDocumentNode);
+ break;
+ }
+}
+
+function SetupEmailAddressPopup(aAddressNode)
+{
+ document.getElementById("emailAddressPlaceHolder")
+ .setAttribute("label", aAddressNode.getAttribute("emailAddress"));
+
+ var addItem = document.getElementById("addToAddressBookItem");
+ var editItem = document.getElementById("editContactItem");
+ var viewItem = document.getElementById("viewContactItem");
+
+ if (aAddressNode.cardDetails.card)
+ {
+ addItem.setAttribute("hidden", true);
+ if (!aAddressNode.cardDetails.book.readOnly)
+ {
+ editItem.removeAttribute("hidden");
+ viewItem.setAttribute("hidden", true);
+ }
+ else
+ {
+ editItem.setAttribute("hidden", true);
+ viewItem.removeAttribute("hidden");
+ }
+ }
+ else
+ {
+ addItem.removeAttribute("hidden");
+ editItem.setAttribute("hidden", true);
+ viewItem.setAttribute("hidden", true);
+ }
+}
+
+/**
+ * Returns an object with two properties, book and card. If the email address
+ * is found in the address books, then book will contain an nsIAbDirectory,
+ * and card will contain an nsIAbCard. If the email address is not found, both
+ * properties will be null.
+ *
+ * @param emailAddress The email address to find.
+ * @return An object with two properties, book and card.
+ * @see nsIAbDirectory.cardForEmailAddress()
+ */
+function GetCardForEmail(aEmailAddress)
+{
+ var books = MailServices.ab.directories;
+
+ var result = { book: null, card: null};
+
+ while (!result.card && books.hasMoreElements())
+ {
+ var ab = books.getNext();
+ if (ab instanceof nsIAbDirectory)
+ {
+ try
+ {
+ var card = ab.cardForEmailAddress(aEmailAddress);
+ if (card)
+ {
+ result.book = ab;
+ result.card = card;
+ }
+ }
+ catch (ex)
+ {
+ // Unsearchable address books throw |NS_ERROR_NOT_IMPLEMENTED|.
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Create a new attachment object which goes into the data attachment array.
+ * This method checks whether the passed attachment is empty or not.
+ *
+ * @param contentType The attachment's mimetype
+ * @param url The URL for the attachment
+ * @param displayName The name to be displayed for this attachment (usually the
+ filename)
+ * @param uri The URI for the message containing the attachment
+ * @param isExternalAttachment True if the attachment has been detached
+ * @param size The size in bytes of the attachment
+ */
+function createNewAttachmentInfo(contentType, url, displayName, uri,
+ isExternalAttachment, size)
+{
+ this.contentType = contentType;
+ this.displayName = displayName;
+ this.uri = uri;
+ this.isExternalAttachment = isExternalAttachment;
+ this.attachment = this;
+ this.size = size;
+ let match;
+
+ // Remote urls, unlike non external mail part urls, may also contain query
+ // strings starting with ?; PART_RE does not handle this.
+ if (url.startsWith("http") || url.startsWith("file")) {
+ match = url.match(/[?&]part=[^&]+$/);
+ match = match && match[0];
+ this.partID = match && match.split("part=")[1];
+ url = url.replace(match, "");
+ }
+ else {
+ match = GlodaUtils.PART_RE.exec(url);
+ this.partID = match && match[1];
+ }
+
+ // Make sure to communicate it if it's an external http attachment and not a
+ // local attachment. For feeds attachments (enclosures) are always remote,
+ // so there is nothing to communicate.
+ if (isExternalAttachment && url.startsWith("http") &&
+ !gFolderDisplay.selectedMessageIsFeed) {
+ if (this.displayName) {
+ this.displayName = url + " - " + this.displayName;
+ }
+ else {
+ this.displayName = url;
+ }
+ }
+
+ this.url = url;
+
+}
+
+createNewAttachmentInfo.prototype.saveAttachment = function saveAttachment()
+{
+ if (this.isExternalAttachment)
+ // TODO: This displays "Save As" instead of "Save Attachment" in the title
+ internalSave(this.url, null,
+ this.displayName, null,
+ this.contentType, false,
+ null, null, null, document);
+ else
+ messenger.saveAttachment(this.contentType,
+ this.url,
+ encodeURIComponent(this.displayName),
+ this.uri,
+ false);
+}
+
+createNewAttachmentInfo.prototype.viewAttachment = function viewAttachment()
+{
+ var url = this.url;
+ if (!this.isExternalAttachment)
+ url += "&filename=" + encodeURIComponent(this.displayName);
+ openDialog("chrome://global/content/viewSource.xul",
+ "_blank", "all,dialog=no", {URL: url});
+}
+
+createNewAttachmentInfo.prototype.openAttachment = function openAttachment()
+{
+ switch (this.contentType)
+ {
+ // As of bug 599119, isTypeSupported returns true for messages, but
+ // attached messages don't open reliably in the browser, so pretend
+ // they're not supported and open a message window for them instead.
+ case "message/rfc822":
+ var url = this.url + "&type=application/x-message-display";
+ window.openDialog("chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,dialog=no",
+ Services.io.newURI(url));
+ return;
+ case "text/x-moz-deleted":
+ return;
+ }
+
+ var webNavigationInfo =
+ Cc["@mozilla.org/webnavigation-info;1"]
+ .getService(Ci.nsIWebNavigationInfo);
+
+ if (webNavigationInfo.isTypeSupported(this.contentType, null))
+ openAsExternal(this.url);
+ else
+ messenger.openAttachment(this.contentType,
+ this.url,
+ encodeURIComponent(this.displayName),
+ this.uri,
+ this.isExternalAttachment);
+}
+
+createNewAttachmentInfo.prototype.printAttachment = function printAttachment()
+{
+ /* we haven't implemented the ability to print attachments yet...
+ messenger.printAttachment(this.contentType,
+ this.url,
+ encodeURIComponent(this.displayName),
+ this.uri);
+ */
+}
+
+createNewAttachmentInfo.prototype.deleteAttachment = function deleteAttachment()
+{
+ messenger.detachAttachment(this.contentType,
+ this.url,
+ encodeURIComponent(this.displayName),
+ this.uri,
+ false);
+}
+
+createNewAttachmentInfo.prototype.detachAttachment = function detachAttachment()
+{
+ messenger.detachAttachment(this.contentType,
+ this.url,
+ encodeURIComponent(this.displayName),
+ this.uri,
+ true);
+}
+
+function CanDetachAttachments()
+{
+ var canDetach = !gFolderDisplay.selectedMessageIsNews &&
+ (!gFolderDisplay.selectedMessageIsImap ||
+ !Services.io.offline);
+ if (canDetach && ("content-type" in currentHeaderData))
+ canDetach = !ContentTypeIsSMIME(currentHeaderData["content-type"].headerValue);
+ return canDetach;
+}
+
+/** Return true if the content type is an S/MIME one. */
+function ContentTypeIsSMIME(contentType)
+{
+ // S/MIME is application/pkcs7-mime and application/pkcs7-signature
+ // - also match application/x-pkcs7-mime and application/x-pkcs7-signature.
+ return /application\/(x-)?pkcs7-(mime|signature)/.test(contentType);
+}
+
+function onShowAttachmentContextMenu()
+{
+ // if no attachments are selected, disable the Open and Save...
+ var attachmentList = document.getElementById('attachmentList');
+ var selectedAttachments = [...attachmentList.selectedItems];
+ var openMenu = document.getElementById('context-openAttachment');
+ var viewMenu = document.getElementById('context-viewAttachment');
+ var saveMenu = document.getElementById('context-saveAttachment');
+ var detachMenu = document.getElementById('context-detachAttachment');
+ var deleteMenu = document.getElementById('context-deleteAttachment');
+ var saveAllMenu = document.getElementById('context-saveAllAttachments');
+ var detachAllMenu = document.getElementById('context-detachAllAttachments');
+ var deleteAllMenu = document.getElementById('context-deleteAllAttachments');
+
+ var canDetach = CanDetachAttachments();
+ var deletedAmongSelected = false;
+ var detachedAmongSelected = false;
+ var anyDeleted = false; // at least one deleted attachment in the list
+ var anyDetached = false; // at least one detached attachment in the list
+
+ // Check if one or more of the selected attachments are deleted.
+ for (let i = 0; i < selectedAttachments.length && !deletedAmongSelected; i++)
+ deletedAmongSelected =
+ (selectedAttachments[i].attachment.contentType == 'text/x-moz-deleted');
+
+ // Check if one or more of the selected attachments are detached.
+ for (let i = 0; i < selectedAttachments.length && !detachedAmongSelected; i++)
+ detachedAmongSelected = selectedAttachments[i].attachment.isExternalAttachment;
+
+ // Check if any attachments are deleted.
+ for (let i = 0; i < currentAttachments.length && !anyDeleted; i++)
+ anyDeleted = (currentAttachments[i].contentType == 'text/x-moz-deleted');
+
+ // Check if any attachments are detached.
+ for (let i = 0; i < currentAttachments.length && !anyDetached; i++)
+ anyDetached = currentAttachments[i].isExternalAttachment;
+
+ if (!deletedAmongSelected && selectedAttachments.length == 1)
+ {
+ openMenu.removeAttribute('disabled');
+ viewMenu.removeAttribute('disabled');
+ }
+ else
+ {
+ openMenu.setAttribute('disabled', true);
+ viewMenu.setAttribute('disabled', true);
+ }
+
+ saveMenu.setAttribute('disabled', deletedAmongSelected);
+ detachMenu.setAttribute('disabled', !canDetach || deletedAmongSelected
+ || detachedAmongSelected);
+ deleteMenu.setAttribute('disabled', !canDetach || deletedAmongSelected
+ || detachedAmongSelected);
+ saveAllMenu.setAttribute('disabled', anyDeleted);
+ detachAllMenu.setAttribute('disabled', !canDetach || anyDeleted || anyDetached);
+ deleteAllMenu.setAttribute('disabled', !canDetach || anyDeleted || anyDetached);
+}
+
+function MessageIdClick(node, event)
+{
+ if (event.button == 0)
+ {
+ var messageId = GetMessageIdFromNode(node, true);
+ OpenMessageForMessageId(messageId);
+ }
+}
+
+// this is our onclick handler for the attachment list.
+// A double click in a listitem simulates "opening" the attachment....
+function attachmentListClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0)
+ return;
+
+ if (event.detail == 2) // double click
+ {
+ var target = event.target;
+ if (target.localName == "listitem")
+ target.attachment.openAttachment();
+ }
+}
+
+// on command handlers for the attachment list context menu...
+// commandPrefix matches one of our existing functions
+// (openAttachment, saveAttachment, etc.)
+function handleAttachmentSelection(commandPrefix)
+{
+ var attachmentList = document.getElementById('attachmentList');
+ var selectedAttachments = [...attachmentList.selectedItems];
+ if (selectedAttachments.length > 1)
+ HandleMultipleAttachments(commandPrefix, selectedAttachments);
+ else
+ selectedAttachments[0].attachment[commandPrefix]();
+}
+
+function createAttachmentDisplayName(aAttachment)
+{
+ // Strip any white space at the end of the display name to avoid
+ // attachment name spoofing (especially Windows will drop trailing dots
+ // and whitespace from filename extensions). Leading and internal
+ // whitespace will be taken care of by the crop="center" attribute.
+ // We must not change the actual filename, though.
+ return aAttachment.displayName.trimRight();
+}
+
+function displayAttachmentsForExpandedView()
+{
+ var numAttachments = currentAttachments.length;
+ if (numAttachments > 0 && !gBuildAttachmentsForCurrentMsg)
+ {
+ let attachmentList = document.getElementById('attachmentList');
+
+ for (let index in currentAttachments)
+ {
+ let attachment = currentAttachments[index];
+
+ // create a listitem for the attachment listbox
+ let displayName = createAttachmentDisplayName(attachment);
+ let nameAndSize = displayName;
+ if (attachment.size != null)
+ nameAndSize += " (" + messenger.formatFileSize(attachment.size) + ")";
+ let item = attachmentList.appendItem(nameAndSize, "");
+ item.setAttribute("crop", "center");
+ item.setAttribute("class", "listitem-iconic attachment-item");
+ item.setAttribute("tooltiptext", attachment.displayName);
+ item.attachment = attachment;
+ item.setAttribute("attachmentUrl", attachment.url);
+ item.setAttribute("attachmentContentType", attachment.contentType);
+ item.setAttribute("attachmentUri", attachment.uri);
+ item.setAttribute("attachmentSize", attachment.size);
+ if (attachment.contentType == "text/x-moz-deleted")
+ item.setAttribute('disabled', 'true');
+ else
+ setApplicationIconForAttachment(attachment, item);
+ } // for each attachment
+
+ gBuildAttachmentsForCurrentMsg = true;
+ }
+
+ var expandedAttachmentBox = document.getElementById('expandedAttachmentBox');
+ expandedAttachmentBox.collapsed = numAttachments <= 0;
+}
+
+// attachment --> the attachment struct containing all the information on the attachment
+// listitem --> the listitem currently showing the attachment.
+function setApplicationIconForAttachment(attachment, listitem)
+{
+ // generate a moz-icon url for the attachment so we'll show a nice icon next to it.
+ listitem.setAttribute('image', "moz-icon:" + "//" + attachment.displayName + "?size=16&contentType=" + attachment.contentType);
+}
+
+function displayAttachmentsForCollapsedView()
+{
+ var numAttachments = currentAttachments.length;
+ var attachmentNode = document.getElementById('collapsedAttachmentBox');
+ attachmentNode.collapsed = numAttachments <= 0; // make sure the attachment button is visible
+}
+
+// Public method called when we create the attachments file menu
+function FillAttachmentListPopup(popup)
+{
+ // the FE sometimes call this routine TWICE...I haven't been able to figure out why yet...
+ // protect against it...
+ if (!gBuildAttachmentPopupForCurrentMsg)
+ return;
+
+ var attachmentIndex = 0;
+
+ // otherwise we need to build the attachment view...
+ // First clear out the old view...
+ ClearAttachmentMenu(popup);
+
+ var canDetachOrDeleteAll = CanDetachAttachments();
+
+ for (let index in currentAttachments)
+ {
+ ++attachmentIndex;
+ addAttachmentToPopup(popup, currentAttachments[index], attachmentIndex);
+ if (canDetachOrDeleteAll &&
+ (currentAttachments[index].isExternalAttachment ||
+ currentAttachments[index].contentType == 'text/x-moz-deleted'))
+ canDetachOrDeleteAll = false;
+ }
+
+ gBuildAttachmentPopupForCurrentMsg = false;
+
+ var detachAllMenu = document.getElementById('file-detachAllAttachments');
+ var deleteAllMenu = document.getElementById('file-deleteAllAttachments');
+
+ detachAllMenu.setAttribute('disabled', !canDetachOrDeleteAll);
+ deleteAllMenu.setAttribute('disabled', !canDetachOrDeleteAll);
+}
+
+// Public method used to clear the file attachment menu
+function ClearAttachmentMenu(popup)
+{
+ if ( popup )
+ {
+ while (popup.firstChild.localName == 'menu')
+ popup.firstChild.remove();
+ }
+}
+
+// Public method used to determine the number of attachments for the currently displayed message...
+function GetNumberOfAttachmentsForDisplayedMessage()
+{
+ return currentAttachments.length;
+}
+
+// private method used to build up a menu list of attachments
+function addAttachmentToPopup(popup, attachment, attachmentIndex)
+{
+ if (popup)
+ {
+ var item = document.createElement('menu');
+ if ( item )
+ {
+ if (!gMessengerBundle)
+ gMessengerBundle = document.getElementById("bundle_messenger");
+
+ // insert the item just before the separator
+ item = popup.insertBefore(item, popup.childNodes[attachmentIndex - 1]);
+ item.setAttribute('class', 'menu-iconic attachment-item');
+
+ var displayName = createAttachmentDisplayName(attachment);
+ var formattedDisplayNameString = gMessengerBundle.getFormattedString("attachmentDisplayNameFormat",
+ [attachmentIndex, displayName]);
+
+ item.setAttribute("crop", "center");
+ item.setAttribute('label', formattedDisplayNameString);
+ item.setAttribute('accesskey', attachmentIndex);
+
+ var openpopup = document.createElement('menupopup');
+ openpopup = item.appendChild(openpopup);
+ if (attachment.contentType == "text/x-moz-deleted") {
+ item.setAttribute('disabled', 'true');
+ return;
+ }
+ openpopup.attachment = attachment;
+ openpopup.addEventListener('popupshowing', FillAttachmentItemPopup);
+ setApplicationIconForAttachment(attachment, item);
+ }
+ }
+}
+
+function FillAttachmentItemPopup(event)
+{
+ var openpopup = event.target;
+ var canDetach = CanDetachAttachments() && !openpopup.attachment.isExternalAttachment;
+ openpopup.removeEventListener('popupshowing', FillAttachmentItemPopup);
+
+ var menuitementry = document.getElementById("context-openAttachment").cloneNode(false);
+ menuitementry.setAttribute('oncommand', 'this.parentNode.attachment.openAttachment();');
+ menuitementry = openpopup.appendChild(menuitementry);
+
+ menuitementry = document.getElementById("context-viewAttachment").cloneNode(false);
+ menuitementry.setAttribute('oncommand', 'this.parentNode.attachment.viewAttachment();');
+ menuitementry = openpopup.appendChild(menuitementry);
+
+ menuitementry = document.getElementById("context-saveAttachment").cloneNode(false);
+ menuitementry.setAttribute('oncommand', 'this.parentNode.attachment.saveAttachment()');
+ menuitementry = openpopup.appendChild(menuitementry);
+
+ openpopup.appendChild(document.createElement("menuseparator"));
+
+ menuitementry = document.getElementById("context-detachAttachment").cloneNode(false);
+ menuitementry.setAttribute('oncommand', 'this.parentNode.attachment.detachAttachment()');
+ if (!canDetach)
+ menuitementry.setAttribute('disabled', 'true');
+ menuitementry = openpopup.appendChild(menuitementry);
+
+ menuitementry = document.getElementById("context-deleteAttachment").cloneNode(false);
+ menuitementry.setAttribute('oncommand', 'this.attachment.deleteAttachment()');
+ if (!canDetach)
+ menuitementry.setAttribute('disabled', 'true');
+ menuitementry = openpopup.appendChild(menuitementry);
+}
+
+function HandleMultipleAttachments(commandPrefix, selectedAttachments)
+{
+ try
+ {
+ // convert our attachment data into some c++ friendly structs
+ var attachmentContentTypeArray = new Array();
+ var attachmentUrlArray = new Array();
+ var attachmentDisplayNameArray = new Array();
+ var attachmentMessageUriArray = new Array();
+
+ // populate these arrays..
+ for (let index in selectedAttachments)
+ {
+ let attachment = selectedAttachments[index].attachment;
+ attachmentContentTypeArray[index] = attachment.contentType;
+ attachmentUrlArray[index] = attachment.url;
+ attachmentDisplayNameArray[index] = encodeURI(attachment.displayName);
+ attachmentMessageUriArray[index] = attachment.uri;
+ }
+
+ // okay the list has been built... now call our action code...
+ switch (commandPrefix)
+ {
+ case "saveAttachment":
+ messenger.saveAllAttachments(attachmentContentTypeArray,
+ attachmentUrlArray,
+ attachmentDisplayNameArray,
+ attachmentMessageUriArray);
+ break;
+ case "detachAttachment":
+ messenger.detachAllAttachments(attachmentContentTypeArray,
+ attachmentUrlArray,
+ attachmentDisplayNameArray,
+ attachmentMessageUriArray,
+ true /* save */);
+ break;
+ case "deleteAttachment":
+ messenger.detachAllAttachments(attachmentContentTypeArray,
+ attachmentUrlArray,
+ attachmentDisplayNameArray,
+ attachmentMessageUriArray,
+ false /* don't save */);
+ break;
+ default:
+ dump (commandPrefix + "** unknown handle all attachments action **\n");
+ }
+ }
+ catch (ex)
+ {
+ dump ("** failed to handle all attachments **\n");
+ }
+}
+
+function ClearAttachmentList()
+{
+ // we also have to disable the Message/Attachments menuitem
+ var node = document.getElementById("msgAttachmentMenu");
+ if (node)
+ node.setAttribute("disabled", "true");
+
+ // clear selection
+ var list = document.getElementById('attachmentList');
+ list.clearSelection();
+
+ while (list.hasChildNodes())
+ list.lastChild.remove();
+}
+
+function ShowEditMessageBox(aMessageBox, aFlag) {
+ try {
+ // it would be nice if we passed in the msgHdr from the back end
+ var msgHdr = gDBView.hdrForFirstSelectedMessage;
+ if (!msgHdr || !msgHdr.folder)
+ return;
+ if (msgHdr.folder.isSpecialFolder(aFlag, true))
+ document.getElementById(aMessageBox).collapsed = false;
+ }
+ catch (ex) {}
+}
+
+function ClearEditMessageBox(aMessageBox) {
+ var editBox = document.getElementById(aMessageBox);
+ if (editBox)
+ editBox.collapsed = true;
+}
+
+// CopyWebsiteAddress takes the website address title button, extracts
+// the website address we stored in there and copies it to the clipboard
+function CopyWebsiteAddress(websiteAddressNode)
+{
+ if (websiteAddressNode)
+ {
+ var websiteAddress = websiteAddressNode.getAttribute("value");
+
+ var contractid = "@mozilla.org/widget/clipboardhelper;1";
+ var iid = Ci.nsIClipboardHelper;
+ var clipboard = Cc[contractid].getService(iid);
+ clipboard.copyString(websiteAddress);
+ }
+}
+
+function BookmarkWebsite(aWebsiteAddressNode)
+{
+ if (aWebsiteAddressNode)
+ {
+ let websiteAddress = aWebsiteAddressNode.getAttribute("value");
+
+ if (currentHeaderData && "content-base" in currentHeaderData)
+ {
+ let url = currentHeaderData["content-base"].headerValue;
+ if (url != websiteAddress)
+ return;
+
+ let title = currentHeaderData["subject"].headerValue;
+ PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(url), title);
+ }
+ }
+}
+
+var attachmentAreaDNDObserver = {
+ onDragStart(aEvent) {
+ var target = aEvent.target;
+ if (target.localName == "listitem") {
+ let index = 0;
+ let selection = target.parentNode.selectedItems;
+ for (let item of selection) {
+ let attachment = item.attachment;
+ if (attachment.contentType == "text/x-moz-deleted") {
+ continue;
+ }
+
+ let name = attachment.name || attachment.displayName;
+ if (!attachment.url || !name) {
+ continue;
+ }
+
+ let info = attachment.url;
+ // Only add type/filename info for non-file URLs that don't already
+ // have it.
+ if (!/(^file:|&filename=)/.test(info)) {
+ info += "&type=" + attachment.contentType + "&filename=" +
+ encodeURIComponent(name);
+ }
+ let dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("text/x-moz-url",
+ info + "\n" + name + "\n" + attachment.size,
+ index);
+ dt.mozSetDataAt("text/x-moz-url-data", attachment.url, index);
+ dt.mozSetDataAt("text/x-moz-url-desc", name, index);
+ dt.mozSetDataAt("application/x-moz-file-promise-url", attachment.url,
+ index);
+ dt.mozSetDataAt("application/x-moz-file-promise",
+ new nsFlavorDataProvider(), index);
+ index++;
+ }
+ }
+ aEvent.stopPropagation();
+ }
+};
+
+function nsFlavorDataProvider()
+{
+}
+
+nsFlavorDataProvider.prototype =
+{
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFlavorDataProvider]),
+
+ getFlavorData : function(aTransferable, aFlavor, aData, aDataLen)
+ {
+ // get the url for the attachment
+ if (aFlavor == "application/x-moz-file-promise")
+ {
+ var urlPrimitive = { };
+ var dataSize = { };
+ aTransferable.getTransferData("application/x-moz-file-promise-url", urlPrimitive, dataSize);
+
+ var srcUrlPrimitive = urlPrimitive.value.QueryInterface(Ci.nsISupportsString);
+
+ // now get the destination file location from kFilePromiseDirectoryMime
+ var dirPrimitive = {};
+ aTransferable.getTransferData("application/x-moz-file-promise-dir", dirPrimitive, dataSize);
+ var destDirectory = dirPrimitive.value.QueryInterface(Ci.nsIFile);
+
+ // now save the attachment to the specified location
+ // XXX: we need more information than just the attachment url to save it, fortunately, we have an array
+ // of all the current attachments so we can cheat and scan through them
+
+ var attachment = null;
+ for (let index in currentAttachments)
+ {
+ attachment = currentAttachments[index];
+ if (attachment.url == srcUrlPrimitive)
+ break;
+ }
+
+ // call our code for saving attachments
+ if (attachment)
+ {
+ var destFilePath = messenger.saveAttachmentToFolder(attachment.contentType, attachment.url, encodeURIComponent(attachment.displayName), attachment.uri, destDirectory);
+ aData.value = destFilePath.QueryInterface(Ci.nsISupports);
+ aDataLen.value = 4;
+ }
+ }
+ }
+}
+
+function nsDummyMsgHeader()
+{
+}
+
+nsDummyMsgHeader.prototype =
+{
+ mProperties : new Array,
+ getStringProperty : function(aProperty)
+ {
+ return this.mProperties[aProperty];
+ },
+ setStringProperty : function(aProperty, aVal)
+ {
+ this.mProperties[aProperty] = aVal;
+ },
+ getUint32Property : function(aProperty)
+ {
+ if (aProperty in this.mProperties)
+ return parseInt(this.mProperties[aProperty]);
+ return 0;
+ },
+ setUint32Property : function(aProperty, aVal)
+ {
+ this.mProperties[aProperty] = aVal.toString();
+ },
+ markHasAttachments : function(hasAttachments) {},
+ messageSize : 0,
+ recipients : null,
+ from : null,
+ subject : "",
+ get mime2DecodedSubject() { return this.subject; },
+ ccList : null,
+ messageId : null,
+ listPost : null,
+ date : 0,
+ accountKey : "",
+ flags : 0,
+ folder : null
+};
diff --git a/comm/suite/mailnews/content/msgHdrViewOverlay.xul b/comm/suite/mailnews/content/msgHdrViewOverlay.xul
new file mode 100644
index 0000000000..ea2c12e091
--- /dev/null
+++ b/comm/suite/mailnews/content/msgHdrViewOverlay.xul
@@ -0,0 +1,273 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % msgHdrViewPopupDTD SYSTEM "chrome://messenger/locale/msgHdrViewPopup.dtd" >
+%msgHdrViewPopupDTD;
+<!ENTITY % msgHdrViewOverlayDTD SYSTEM "chrome://messenger/locale/msgHdrViewOverlay.dtd" >
+%msgHdrViewOverlayDTD;
+]>
+
+<?xml-stylesheet href="chrome://messenger/skin/messageHeader.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messageKeywords.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgHdrViewSMIMEOverlay.css" type="text/css"?>
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://messenger/content/msgHdrViewOverlay.js"/>
+
+<script src="chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.js"/>
+
+<stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+</stringbundleset>
+
+<menupopup id="messageIdContext" popupanchor="bottomleft"
+ onpopupshowing="FillMessageIdContextMenu(document.popupNode);">
+ <menuitem id="messageIdContext-messageIdTarget"
+ disabled="true"/>
+ <menuseparator/>
+ <menuitem id="messageIdContext-openMessageForMsgId"
+ label="&OpenMessageForMsgId.label;"
+ accesskey="&OpenMessageForMsgId.accesskey;"
+ oncommand="var messageId = GetMessageIdFromNode(document.popupNode, true);
+ OpenMessageForMessageId(messageId)"/>
+ <menuitem id="messageIdContext-openBrowserWithMsgId"
+ label="&OpenBrowserWithMsgId.label;"
+ accesskey="&OpenBrowserWithMsgId.accesskey;"
+ oncommand="var messageId = GetMessageIdFromNode(document.popupNode, true);
+ OpenBrowserWithMessageId(messageId)"/>
+ <menuitem id="messageIdContext-copyMessageId"
+ label="&CopyMessageId.label;"
+ accesskey="&CopyMessageId.accesskey;"
+ oncommand="var messageId = GetMessageIdFromNode(document.popupNode, false);
+ CopyString(messageId);"/>
+</menupopup>
+
+<menupopup id="emailAddressPopup" popupanchor="bottomleft"
+ onpopupshowing="SetupEmailAddressPopup(document.popupNode);
+ goUpdateCommand('cmd_createFilterFromPopup');">
+ <menuitem id="emailAddressPlaceHolder" label="" disabled="true"/>
+ <menuseparator/>
+ <menuitem id="sendMailToItem"
+ label="&SendMailTo.label;"
+ accesskey="&SendMailTo.accesskey;"
+ oncommand="SendMailToNode(document.popupNode, event)"/>
+ <menuitem id="createFilterFromItem"
+ label="&CreateFilterFrom.label;"
+ accesskey="&CreateFilterFrom.accesskey;"
+ command="cmd_createFilterFromPopup"/>
+ <menuitem id="addToAddressBookItem"
+ label="&AddToAddressBook.label;"
+ accesskey="&AddToAddressBook.accesskey;"
+ oncommand="AddContact(document.popupNode);"/>
+ <menuitem id="editContactItem"
+ label="&EditContact.label;"
+ accesskey="&EditContact.accesskey;"
+ hidden="true"
+ oncommand="EditContact(document.popupNode);"/>
+ <menuitem id="viewContactItem"
+ label="&ViewContact.label;"
+ accesskey="&ViewContact.accesskey;"
+ hidden="true"
+ oncommand="EditContact(document.popupNode);"/>
+ <menuitem id="copyEmailAddressItem"
+ label="&CopyEmailAddress.label;"
+ accesskey="&CopyEmailAddress.accesskey;"
+ oncommand="CopyEmailAddress(document.popupNode);"/>
+ <menuitem id="copyNameAndEmailAddressItem"
+ label="&CopyNameAndEmailAddress.label;"
+ accesskey="&CopyNameAndEmailAddress.accesskey;"
+ oncommand="CopyEmailAddress(document.popupNode, true);"/>
+</menupopup>
+
+<menupopup id="attachmentListContext" onpopupshowing="return onShowAttachmentContextMenu();">
+ <menuitem id="context-openAttachment" label="&openAttachmentCmd.label;" accesskey="&openAttachmentCmd.accesskey;"
+ oncommand="handleAttachmentSelection('openAttachment');"/>
+ <menuitem id="context-viewAttachment" label="&viewAttachmentCmd.label;" accesskey="&viewAttachmentCmd.accesskey;"
+ oncommand="handleAttachmentSelection('viewAttachment');"/>
+ <menuitem id="context-saveAttachment" label="&saveAsAttachmentCmd.label;" accesskey="&saveAsAttachmentCmd.accesskey;"
+ oncommand="handleAttachmentSelection('saveAttachment');"/>
+ <menuseparator/>
+ <menuitem id="context-detachAttachment" label="&detachAttachmentCmd.label;" accesskey="&detachAttachmentCmd.accesskey;"
+ oncommand="handleAttachmentSelection('detachAttachment');"/>
+ <menuitem id="context-deleteAttachment" label="&deleteAttachmentCmd.label;" accesskey="&deleteAttachmentCmd.accesskey;"
+ oncommand="handleAttachmentSelection('deleteAttachment');"/>
+ <menuseparator/>
+ <menuitem id="context-saveAllAttachments" oncommand="HandleMultipleAttachments('saveAttachment', currentAttachments);"
+ label="&saveAllAttachmentsCmd.label;" accesskey="&saveAllAttachmentsCmd.accesskey;"/>
+ <menuitem id="context-detachAllAttachments" oncommand="HandleMultipleAttachments('detachAttachment', currentAttachments);"
+ label="&detachAllAttachmentsCmd.label;" accesskey="&detachAllAttachmentsCmd.accesskey;"/>
+ <menuitem id="context-deleteAllAttachments" oncommand="HandleMultipleAttachments('deleteAttachment', currentAttachments);"
+ label="&deleteAllAttachmentsCmd.label;" accesskey="&deleteAllAttachmentsCmd.accesskey;"/>
+</menupopup>
+
+<menupopup id="attachmentMenuList">
+ <menuseparator/>
+ <menuitem id="file-saveAllAttachments" label="&saveAllAttachmentsCmd.label;"
+ accesskey="&saveAllAttachmentsCmd.accesskey;" oncommand="HandleMultipleAttachments('saveAttachment', currentAttachments);"/>
+ <menuitem id="file-detachAllAttachments" label="&detachAllAttachmentsCmd.label;"
+ accesskey="&detachAllAttachmentsCmd.accesskey;" oncommand="HandleMultipleAttachments('detachAttachment', currentAttachments);" />
+ <menuitem id="file-deleteAllAttachments" label="&deleteAllAttachmentsCmd.label;"
+ accesskey="&deleteAllAttachmentsCmd.accesskey;" oncommand="HandleMultipleAttachments('deleteAttachment', currentAttachments);" />
+</menupopup>
+
+<menupopup id="copyUrlPopup">
+ <menuitem label="&openInBrowser.label;"
+ accesskey="&openInBrowser.accesskey;"
+ oncommand="openAsExternal(document.popupNode.getAttribute('value'));"/>
+ <menuitem label="&bookmarkLinkCmd.label;"
+ accesskey="&bookmarkLinkCmd.accesskey;"
+ oncommand="BookmarkWebsite(document.popupNode);"/>
+ <menuitem label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ oncommand="CopyWebsiteAddress(document.popupNode);"/>
+</menupopup>
+
+<hbox id="msgHeaderView" persist="state">
+
+<grid id="collapsedHeaderView" class="header-part1" flex="1" collapsed="true">
+ <rows>
+ <row flex="1"/>
+ </rows>
+ <columns>
+ <column class="collapsedToggleHdrBox">
+ <hbox align="start">
+ <image id="toggleHeaderView" class="collapsedHeaderViewButton"
+ onclick="ToggleHeaderView();"/>
+ </hbox>
+ </column>
+
+ <column id="collapsedsubjectBox" collapsed="true" flex="1">
+ <hbox>
+ <label class="collapsedHeaderDisplayName" value="&subjectField.label;" control="collapsedsubjectValue"/>
+ <textbox id="collapsedsubjectValue"
+ class="collapsedHeaderValue plain"
+ readonly="true" crop="right" flex="1"/>
+ </hbox>
+ </column>
+
+ <column id="collapsedfromBox" flex="1">
+ <hbox align="start">
+ <mail-multi-emailHeaderField id="collapsedfromValue" class="collapsedHeaderDisplayName" label="&fromField.label;" collapsed="true" flex="1"/>
+ </hbox>
+ </column>
+
+ <column id = "collapseddateBox" collapsed="true">
+ <hbox align="start">
+ <textbox id="collapseddateValue"
+ class="collapsedHeaderValue plain"
+ readonly="true"/>
+ </hbox>
+ </column>
+
+ <column id="collapsedKeywordBox">
+ <hbox align="start">
+ <image id="collapsedKeywordImage"/>
+ </hbox>
+ </column>
+
+ <column id="collapsedAttachmentBox" collapsed="true">
+ <hbox align="start">
+ <image id="collapsedAttachment" class="collapsedAttachmentButton" onclick="ToggleHeaderView();" />
+ </hbox>
+ </column>
+ </columns>
+</grid>
+
+<hbox id="expandedHeaderView" class="header-part1" flex="1" collapsed="true">
+
+ <vbox id="expandedHeaders" flex="1">
+ <mail-toggle-headerfield id="expandedsubjectBox"
+ class="subjectvalue"
+ label="&subjectField.label;"
+ ontwistyclick="ToggleHeaderView();"
+ collapsed="true"/>
+
+ <mail-multi-emailHeaderField id="expandedfromBox" label="&fromField.label;" collapsed="true"/>
+ <mail-emailheaderfield id="expandedsenderBox" label="&senderField.label;" collapsed="true"/>
+ <mail-headerfield id="expandedorganizationBox" label="&organizationField.label;" collapsed="true"/>
+ <mail-multi-emailHeaderField id="expandedreply-toBox" label="&replyToField.label;" collapsed="true"/>
+
+ <mail-headerfield id="expandeddateBox"
+ label="&dateField.label;"
+ collapsed="true"/>
+
+ <mail-multi-emailHeaderField id="expandedtoBox" label="&toField.label;" collapsed="true"/>
+ <mail-multi-emailHeaderField id="expandedccBox" label="&ccField.label;" collapsed="true"/>
+ <mail-multi-emailHeaderField id="expandedbccBox" label="&bccField.label;" collapsed="true"/>
+
+ <mail-headerfield id="expandednewsgroupsBox"
+ label="&newsgroupsField.label;"
+ collapsed="true"/>
+ <mail-headerfield id="expandedfollowup-toBox"
+ label="&followupToField.label;"
+ collapsed="true"/>
+ <mail-messageids-headerfield id="expandedmessage-idBox" label="&messageIdField.label;" collapsed="true"/>
+ <mail-messageids-headerfield id="expandedin-reply-toBox" label="&inReplyToField.label;" collapsed="true"/>
+ <mail-messageids-headerfield id="expandedreferencesBox" label="&referencesField.label;" collapsed="true"/>
+ <mail-tagfield id="expandedtagsBox" label="&tagsHdr.label;" collapsed="true"/>
+ <mail-urlfield id="expandedcontent-baseBox" label="&originalWebsite.label;" collapsed="true"/>
+ <mail-headerfield id="expandeduser-agentBox"
+ label="&userAgentField.label;"
+ collapsed="true"/>
+ </vbox>
+
+ <vbox id="smimeBox" collapsed="true">
+ <spacer flex="1"/>
+ <image id="signedHdrIcon"
+ onclick="showMessageReadSecurityInfo();"
+ collapsed="true"/>
+ <image id="encryptedHdrIcon"
+ onclick="showMessageReadSecurityInfo();"
+ collapsed="true"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ <vbox id="expandedKeywordBox">
+ <spacer flex="1"/>
+ <image id="expandedKeywordImage"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ <vbox id="editDraftBox" class="header-part1" collapsed="true">
+ <spacer flex="1"/>
+ <button id="editDraftButton"
+ label="&editDraft.label;"
+ accesskey="&editDraft.accesskey;"
+ oncommand="MsgComposeDraftMessage(null);"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ <vbox id="editTemplateBox" class="header-part1" collapsed="true">
+ <spacer flex="1"/>
+ <button id="editTemplateButton"
+ label="&editTemplate.label;"
+ accesskey="&editTemplate.accesskey;"
+ oncommand="MsgEditTemplateMessage(null);"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ <vbox>
+ <spacer flex="1"/>
+ <image style="padding: 5px" id="fromBuddyIcon"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ <vbox id="expandedAttachmentBox" class="header-part1" collapsed="true">
+ <label id="attachmentText"
+ value="&attachmentsTree.label;"
+ accesskey="&attachmentsTree.accesskey;"
+ crop="right"
+ control="attachmentList"/>
+ <listbox id="attachmentList" rows="3" seltype="multiple"
+ onclick="attachmentListClick(event);"
+ ondragstart="attachmentAreaDNDObserver.onDragStart(event);"
+ context="attachmentListContext"/>
+ </vbox>
+</hbox>
+</hbox>
+</overlay>
diff --git a/comm/suite/mailnews/content/msgMail3PaneWindow.js b/comm/suite/mailnews/content/msgMail3PaneWindow.js
new file mode 100644
index 0000000000..5cd3aa0693
--- /dev/null
+++ b/comm/suite/mailnews/content/msgMail3PaneWindow.js
@@ -0,0 +1,1265 @@
+/* -*- 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/. */
+
+/* This is where functions related to the 3 pane window are kept */
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+const {msgDBCacheManager} = ChromeUtils.import("resource:///modules/msgDBCacheManager.js");
+const {PeriodicFilterManager} = ChromeUtils.import("resource:///modules/PeriodicFilterManager.jsm");
+
+// from MailNewsTypes.h
+const nsMsgKey_None = 0xFFFFFFFF;
+const nsMsgViewIndex_None = 0xFFFFFFFF;
+const kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
+
+var gSearchInput;
+
+var gUnreadCount = null;
+var gTotalCount = null;
+
+var gCurrentLoadingFolderURI;
+var gCurrentFolderToReroot;
+var gCurrentLoadingFolderSortType = 0;
+var gCurrentLoadingFolderSortOrder = 0;
+var gCurrentLoadingFolderViewType = 0;
+var gCurrentLoadingFolderViewFlags = 0;
+var gRerootOnFolderLoad = false;
+var gCurrentDisplayedMessage = null;
+var gNextMessageAfterDelete = null;
+var gNextMessageAfterLoad = null;
+var gNextMessageViewIndexAfterDelete = -2;
+var gCurrentlyDisplayedMessage=nsMsgViewIndex_None;
+var gStartMsgKey = nsMsgKey_None;
+var gSearchEmailAddress = null;
+var gRightMouseButtonDown = false;
+// Global var to keep track of which row in the thread pane has been selected
+// This is used to make sure that the row with the currentIndex has the selection
+// after a Delete or Move of a message that has a row index less than currentIndex.
+var gThreadPaneCurrentSelectedIndex = -1;
+// Account Wizard can exceptionally override this feature.
+var gLoadStartFolder = true;
+
+// Global var to keep track of if the 'Delete Message' or 'Move To' thread pane
+// context menu item was triggered. This helps prevent the tree view from
+// not updating on one of those menu item commands.
+var gThreadPaneDeleteOrMoveOccurred = false;
+
+//If we've loaded a message, set to true. Helps us keep the start page around.
+var gHaveLoadedMessage;
+
+var gDisplayStartupPage = false;
+
+function SelectAndScrollToKey(aMsgKey)
+{
+ // select the desired message
+ // if the key isn't found, we won't select anything
+ if (!gDBView)
+ return false;
+ gDBView.selectMsgByKey(aMsgKey);
+
+ // is there a selection?
+ // if not, bail out.
+ var indicies = GetSelectedIndices(gDBView);
+ if (!indicies || !indicies.length)
+ return false;
+
+ // now scroll to it
+ EnsureRowInThreadTreeIsVisible(indicies[0]);
+ return true;
+}
+
+// A helper routine called after a folder is loaded to make sure
+// we select and scroll to the correct message (could be the first new message,
+// could be the last displayed message, etc.)
+function ScrollToMessageAfterFolderLoad(folder)
+{
+ var scrolled = Services.prefs.getBoolPref("mailnews.scroll_to_new_message") &&
+ ScrollToMessage(nsMsgNavigationType.firstNew, true, false /* selectMessage */);
+ if (!scrolled && folder && Services.prefs.getBoolPref("mailnews.remember_selected_message"))
+ {
+ // If we failed to scroll to a new message,
+ // reselect the last selected message
+ var lastMessageLoaded = folder.lastMessageLoaded;
+ if (lastMessageLoaded != nsMsgKey_None)
+ scrolled = SelectAndScrollToKey(lastMessageLoaded);
+ }
+
+ if (!scrolled)
+ {
+ // if we still haven't scrolled,
+ // scroll to the newest, which might be the top or the bottom
+ // depending on our sort order and sort type
+ if (gDBView && gDBView.sortOrder == nsMsgViewSortOrder.ascending)
+ {
+ switch (gDBView.sortType)
+ {
+ case nsMsgViewSortType.byDate:
+ case nsMsgViewSortType.byReceived:
+ case nsMsgViewSortType.byId:
+ case nsMsgViewSortType.byThread:
+ scrolled = ScrollToMessage(nsMsgNavigationType.lastMessage, true, false /* selectMessage */);
+ break;
+ }
+ }
+
+ // if still we haven't scrolled,
+ // scroll to the top.
+ if (!scrolled)
+ EnsureRowInThreadTreeIsVisible(0);
+ }
+}
+
+// the folderListener object
+var folderListener =
+{
+ onFolderAdded: function(parentFolder, child) {},
+ onMessageAdded: function(parentFolder, msg) {},
+ onFolderRemoved: function(parentFolder, child) {},
+ onMessageRemoved: function(parentFolder, msg) {},
+
+ onFolderPropertyChanged: function(item, property, oldValue, newValue) {},
+ onFolderBoolPropertyChanged: function(item, property, oldValue, newValue) {},
+ onFolderUnicharPropertyChanged: function(item, property, oldValue, newValue) {},
+ onFolderPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+
+ onFolderIntPropertyChanged: function(item, property, oldValue, newValue)
+ {
+ // handle the currently visible folder
+ if (item == gMsgFolderSelected)
+ {
+ if (property == "TotalMessages" || property == "TotalUnreadMessages")
+ {
+ UpdateStatusMessageCounts(gMsgFolderSelected);
+ }
+ }
+
+ // check folders shown in tabs
+ if (item instanceof Ci.nsIMsgFolder)
+ {
+ // find corresponding tabinfos
+ // we may have the folder openened in more than one tab
+ let tabmail = GetTabMail();
+ for (let i = 0; i < tabmail.tabInfo.length; ++i)
+ {
+ // if we never switched away from the tab, we only have just one
+ let tabFolder = tabmail.tabInfo[i].msgSelectedFolder || gMsgFolderSelected;
+ if (tabFolder == item)
+ {
+ // update tab title incl. any icon styles
+ tabmail.setTabTitle(tabmail.tabInfo[i]);
+ }
+ }
+ }
+ },
+
+ onFolderEvent: function(folder, event) {
+ if (event == "FolderLoaded") {
+ if (folder) {
+ var scrolled = false;
+ var msgFolder = folder.QueryInterface(Ci.nsIMsgFolder);
+ var uri = folder.URI;
+ var rerootingFolder = (uri == gCurrentFolderToReroot);
+ if (rerootingFolder) {
+ viewDebug("uri = gCurrentFolderToReroot, setting gQSViewIsDirty\n");
+ gQSViewIsDirty = true;
+ gCurrentFolderToReroot = null;
+ if (msgFolder) {
+ msgFolder.endFolderLoading();
+ // Suppress command updating when rerooting the folder.
+ // When rerooting, we'll be clearing the selection
+ // which will cause us to update commands.
+ if (gDBView) {
+ gDBView.suppressCommandUpdating = true;
+ // If the db's view isn't set, something went wrong and we
+ // should reroot the folder, which will re-open the view.
+ if (!gDBView.db)
+ gRerootOnFolderLoad = true;
+ }
+ if (gRerootOnFolderLoad)
+ RerootFolder(uri, msgFolder, gCurrentLoadingFolderViewType, gCurrentLoadingFolderViewFlags, gCurrentLoadingFolderSortType, gCurrentLoadingFolderSortOrder);
+
+ if (gDBView)
+ gDBView.suppressCommandUpdating = false;
+
+ gCurrentLoadingFolderSortType = 0;
+ gCurrentLoadingFolderSortOrder = 0;
+ gCurrentLoadingFolderViewType = 0;
+ gCurrentLoadingFolderViewFlags = 0;
+
+ // Used for rename folder msg loading after folder is loaded.
+ scrolled = LoadCurrentlyDisplayedMessage();
+
+ if (gStartMsgKey != nsMsgKey_None) {
+ scrolled = SelectAndScrollToKey(gStartMsgKey);
+ gStartMsgKey = nsMsgKey_None;
+ }
+
+ if (gNextMessageAfterLoad) {
+ var type = gNextMessageAfterLoad;
+ gNextMessageAfterLoad = null;
+
+ // Scroll to and select the proper message.
+ scrolled = ScrollToMessage(type, true, true /* selectMessage */);
+ }
+ }
+ }
+ if (uri == gCurrentLoadingFolderURI) {
+ viewDebug("uri == current loading folder uri\n");
+ gCurrentLoadingFolderURI = "";
+ // Scroll to message for virtual folders is done in
+ // gSearchNotificationListener.OnSearchDone (see searchBar.js).
+ if (!scrolled && gMsgFolderSelected &&
+ !(gMsgFolderSelected.flags & Ci.nsMsgFolderFlags.Virtual))
+ ScrollToMessageAfterFolderLoad(msgFolder);
+ SetBusyCursor(window, false);
+ }
+ // Folder loading is over,
+ // now issue quick search if there is an email address.
+ if (gVirtualFolderTerms)
+ viewDebug("in folder loaded gVirtualFolderTerms = " +
+ gVirtualFolderTerms + "\n");
+ if (gMsgFolderSelected)
+ viewDebug("in folder loaded gMsgFolderSelected = " +
+ gMsgFolderSelected.URI + "\n");
+ if (rerootingFolder)
+ {
+ if (gSearchEmailAddress)
+ {
+ Search(gSearchEmailAddress);
+ gSearchEmailAddress = null;
+ }
+ else if (gVirtualFolderTerms)
+ {
+ gDefaultSearchViewTerms = null;
+ viewDebug("searching gVirtualFolderTerms\n");
+ gDBView.viewFolder = gMsgFolderSelected;
+ ViewChangeByFolder(gMsgFolderSelected);
+ }
+ else if (gMsgFolderSelected &&
+ gMsgFolderSelected.flags & Ci.nsMsgFolderFlags.Virtual)
+ {
+ viewDebug("selected folder is virtual\n");
+ gDefaultSearchViewTerms = null;
+ }
+ else
+ {
+ // Get the view value from the folder.
+ if (msgFolder)
+ {
+ // If our new view is the same as the old view and we already
+ // have the list of search terms built up for the old view,
+ // just re-use it.
+ var result = GetMailViewForFolder(msgFolder);
+ if (GetSearchInput() && gCurrentViewValue == result && gDefaultSearchViewTerms)
+ {
+ viewDebug("searching gDefaultSearchViewTerms and rerootingFolder\n");
+ Search("");
+ }
+ else
+ {
+ viewDebug("changing view by value\n");
+ ViewChangeByValue(result);
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (event == "ImapHdrDownloaded") {
+ if (folder) {
+ var imapFolder = folder.QueryInterface(Ci.nsIMsgImapMailFolder);
+ if (imapFolder) {
+ var hdrParser = imapFolder.hdrParser;
+ if (hdrParser) {
+ var msgHdr = hdrParser.GetNewMsgHdr();
+ if (msgHdr)
+ {
+ var hdrs = hdrParser.headers;
+ if (hdrs && hdrs.includes("X-attachment-size:")) {
+ msgHdr.OrFlags(Ci.nsMsgMessageFlags
+ .Attachment);
+ }
+ if (hdrs && hdrs.includes("X-image-size:")) {
+ msgHdr.setStringProperty("imageSize", "1");
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (event == "DeleteOrMoveMsgCompleted") {
+ HandleDeleteOrMoveMsgCompleted(folder);
+ }
+ else if (event == "DeleteOrMoveMsgFailed") {
+ HandleDeleteOrMoveMsgFailed(folder);
+ }
+ else if (event == "AboutToCompact") {
+ if (gDBView)
+ gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
+ }
+ else if (event == "CompactCompleted") {
+ HandleCompactCompleted(folder);
+ }
+ else if (event == "RenameCompleted") {
+ // Clear this so we don't try to clear its new messages.
+ gMsgFolderSelected = null;
+ gFolderTreeView.selectFolder(folder);
+ }
+ else if (event == "JunkStatusChanged") {
+ HandleJunkStatusChanged(folder);
+ }
+ }
+}
+
+function HandleDeleteOrMoveMsgFailed(folder)
+{
+ gDBView.onDeleteCompleted(false);
+ if(IsCurrentLoadedFolder(folder)) {
+ if(gNextMessageAfterDelete) {
+ gNextMessageAfterDelete = null;
+ gNextMessageViewIndexAfterDelete = -2;
+ }
+ }
+
+ // fix me???
+ // ThreadPaneSelectionChange(true);
+}
+
+// WARNING
+// this is a fragile and complicated function.
+// be careful when hacking on it.
+// Don't forget about things like different imap
+// delete models, multiple views (from multiple thread panes,
+// search windows, stand alone message windows)
+function HandleDeleteOrMoveMsgCompleted(folder)
+{
+ // you might not have a db view. this can happen if
+ // biff fires when the 3 pane is set to account central.
+ if (!gDBView)
+ return;
+
+ gDBView.onDeleteCompleted(true);
+
+ if (!IsCurrentLoadedFolder(folder)) {
+ // default value after delete/move/copy is over
+ gNextMessageViewIndexAfterDelete = -2;
+ return;
+ }
+
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ var treeSelection = treeView.selection;
+
+ if (gNextMessageViewIndexAfterDelete == -2) {
+ // a move or delete can cause our selection can change underneath us.
+ // this can happen when the user
+ // deletes message from the stand alone msg window
+ // or the search view, or another 3 pane
+ if (treeSelection.count == 0) {
+ // this can happen if you double clicked a message
+ // in the thread pane, and deleted it from the stand alone msg window
+ // see bug #172392
+ treeSelection.clearSelection();
+ setTitleFromFolder(folder, null);
+ ClearMessagePane();
+ UpdateMailToolbar("delete from another view, 0 rows now selected");
+ }
+ else if (treeSelection.count == 1) {
+ // this can happen if you had two messages selected
+ // in the thread pane, and you deleted one of them from another view
+ // (like the view in the stand alone msg window)
+ // since one item is selected, we should load it.
+ var startIndex = {};
+ var endIndex = {};
+ treeSelection.getRangeAt(0, startIndex, endIndex);
+
+ // select the selected item, so we'll load it
+ treeSelection.select(startIndex.value);
+ treeView.selectionChanged();
+
+ EnsureRowInThreadTreeIsVisible(startIndex.value);
+
+ UpdateMailToolbar("delete from another view, 1 row now selected");
+ }
+ else {
+ // this can happen if you have more than 2 messages selected
+ // in the thread pane, and you deleted one of them from another view
+ // (like the view in the stand alone msg window)
+ // since multiple messages are still selected, do nothing.
+ }
+ }
+ else {
+ if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
+ {
+ var viewSize = treeView.rowCount;
+ if (gNextMessageViewIndexAfterDelete >= viewSize)
+ {
+ if (viewSize > 0)
+ gNextMessageViewIndexAfterDelete = viewSize - 1;
+ else
+ {
+ gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
+
+ // there is nothing to select since viewSize is 0
+ treeSelection.clearSelection();
+ setTitleFromFolder(folder, null);
+ ClearMessagePane();
+ UpdateMailToolbar("delete from current view, 0 rows left");
+ }
+ }
+ }
+
+ // if we are about to set the selection with a new element then DON'T clear
+ // the selection then add the next message to select. This just generates
+ // an extra round of command updating notifications that we are trying to
+ // optimize away.
+ if (gNextMessageViewIndexAfterDelete != nsMsgViewIndex_None)
+ {
+ // When deleting a message we don't update the commands
+ // when the selection goes to 0
+ // (we have a hack in nsMsgDBView which prevents that update)
+ // so there is no need to
+ // update commands when we select the next message after the delete;
+ // the commands already
+ // have the right update state...
+ gDBView.suppressCommandUpdating = true;
+
+ // This check makes sure that the tree does not perform a
+ // selection on a non selected row (row < 0), else assertions will
+ // be thrown.
+ if (gNextMessageViewIndexAfterDelete >= 0)
+ treeSelection.select(gNextMessageViewIndexAfterDelete);
+
+ // If gNextMessageViewIndexAfterDelete has the same value
+ // as the last index we had selected, the tree won't generate a
+ // selectionChanged notification for the tree view. So force a manual
+ // selection changed call.
+ // (don't worry it's cheap if we end up calling it twice).
+ if (treeView)
+ treeView.selectionChanged();
+
+ EnsureRowInThreadTreeIsVisible(gNextMessageViewIndexAfterDelete);
+ gDBView.suppressCommandUpdating = false;
+
+ // hook for extra toolbar items
+ // XXX TODO
+ // I think there is a bug in the suppression code above.
+ // What if I have two rows selected, and I hit delete,
+ // and so we load the next row.
+ // What if I have commands that only enable where
+ // exactly one row is selected?
+ UpdateMailToolbar("delete from current view, at least one row selected");
+ }
+ }
+
+ // default value after delete/move/copy is over
+ gNextMessageViewIndexAfterDelete = -2;
+}
+
+function HandleCompactCompleted(folder)
+{
+ if (folder && folder.server.type != "imap")
+ {
+ let msgFolder = msgWindow.openFolder;
+ if (msgFolder && folder.URI == msgFolder.URI)
+ {
+ // pretend the selection changed, to reselect the current folder+view.
+ gMsgFolderSelected = null;
+ msgWindow.openFolder = null;
+ FolderPaneSelectionChange();
+ LoadCurrentlyDisplayedMessage();
+ }
+ }
+}
+
+function LoadCurrentlyDisplayedMessage()
+{
+ var scrolled = (gCurrentlyDisplayedMessage != nsMsgViewIndex_None);
+ if (scrolled)
+ {
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ var treeSelection = treeView.selection;
+ treeSelection.select(gCurrentlyDisplayedMessage);
+ if (treeView)
+ treeView.selectionChanged();
+ EnsureRowInThreadTreeIsVisible(gCurrentlyDisplayedMessage);
+ SetFocusThreadPane();
+ gCurrentlyDisplayedMessage = nsMsgViewIndex_None; //reset
+ }
+ return scrolled;
+}
+
+function IsCurrentLoadedFolder(aFolder)
+{
+ let msgFolderUri = aFolder.QueryInterface(Ci.nsIMsgFolder)
+ .URI;
+ let currentLoadedFolder = GetThreadPaneFolder();
+
+ // If the currently loaded folder is virtual,
+ // check if aFolder is one of its searched folders.
+ if (currentLoadedFolder.flags & Ci.nsMsgFolderFlags.Virtual)
+ {
+ return currentLoadedFolder.msgDatabase.dBFolderInfo
+ .getCharProperty("searchFolderUri").split("|")
+ .includes(msgFolderUri);
+ }
+
+ // Is aFolder the currently loaded folder?
+ return currentLoadedFolder.URI == msgFolderUri;
+}
+
+function ServerContainsFolder(server, folder)
+{
+ if (!folder || !server)
+ return false;
+
+ return server.equals(folder.server);
+}
+
+function SelectServer(server)
+{
+ gFolderTreeView.selectFolder(server.rootFolder);
+}
+
+// we have this incoming server listener in case we need to
+// alter the folder pane selection when a server is removed
+// or changed (currently, when the real username or real hostname change)
+var gThreePaneIncomingServerListener = {
+ onServerLoaded: function(server) {},
+ onServerUnloaded: function(server) {
+ var selectedFolders = GetSelectedMsgFolders();
+ for (var i = 0; i < selectedFolders.length; i++) {
+ if (ServerContainsFolder(server, selectedFolders[i])) {
+ if (accountManager.defaultAccount)
+ SelectServer(accountManager.defaultAccount.incomingServer);
+ // we've made a new selection, we're done
+ return;
+ }
+ }
+
+ // if nothing is selected at this point, better go select the default
+ // this could happen if nothing was selected when the server was removed
+ selectedFolders = GetSelectedMsgFolders();
+ if (selectedFolders.length == 0) {
+ if (accountManager.defaultAccount)
+ SelectServer(accountManager.defaultAccount.incomingServer);
+ }
+ },
+ onServerChanged: function(server) {
+ // if the current selected folder is on the server that changed
+ // and that server is an imap or news server,
+ // we need to update the selection.
+ // on those server types, we'll be reconnecting to the server
+ // and our currently selected folder will need to be reloaded
+ // or worse, be invalid.
+ if (server.type != "imap" && server.type !="nntp")
+ return;
+
+ var selectedFolders = GetSelectedMsgFolders();
+ for (var i = 0; i < selectedFolders.length; i++) {
+ // if the selected item is a server, we don't have to update
+ // the selection
+ if (!(selectedFolders[i].isServer) && ServerContainsFolder(server, selectedFolders[i])) {
+ SelectServer(server);
+ // we've made a new selection, we're done
+ return;
+ }
+ }
+ }
+}
+
+function UpdateMailPaneConfig() {
+ const dynamicIds = ["messagesBox", "mailContent", "messengerBox"];
+ var desiredId = dynamicIds[Services.prefs.getIntPref("mail.pane_config.dynamic")];
+ var messagePane = GetMessagePane();
+ if (messagePane.parentNode.id != desiredId) {
+ ClearAttachmentList();
+ var messagePaneSplitter = GetThreadAndMessagePaneSplitter();
+ var desiredParent = document.getElementById(desiredId);
+ // See Bug 381992. The ctor for the browser element will fire again when we
+ // re-insert the messagePaneBox back into the document.
+ // But the dtor doesn't fire when the element is removed from the document.
+ // Manually call destroy here to avoid a nasty leak.
+ getMessageBrowser().destroy();
+ desiredParent.appendChild(messagePaneSplitter);
+ desiredParent.appendChild(messagePane);
+ messagePaneSplitter.orient = desiredParent.orient;
+ // Reroot message display
+ InvalidateTabDBs();
+ let tabmail = GetTabMail();
+ tabmail.currentTabInfo = null;
+ tabmail.updateCurrentTab();
+ }
+}
+
+var MailPrefObserver = {
+ observe: function observe(subject, topic, prefName) {
+ if (topic == "nsPref:changed") {
+ if (prefName == "mail.pane_config.dynamic") {
+ UpdateMailPaneConfig();
+ } else if (prefName == "mail.showCondensedAddresses") {
+ let currentDisplayNameVersion =
+ Services.prefs.getIntPref("mail.displayname.version");
+ Services.prefs.setIntPref("mail.displayname.version",
+ ++currentDisplayNameVersion);
+
+ // Refresh the thread pane.
+ GetThreadTree().treeBoxObject.invalid();
+ }
+ }
+ }
+};
+
+/* Functions related to startup */
+function OnLoadMessenger()
+{
+ AddMailOfflineObserver();
+ CreateMailWindowGlobals();
+ Services.prefs.addObserver("mail.pane_config.dynamic", MailPrefObserver);
+ Services.prefs.addObserver("mail.showCondensedAddresses", MailPrefObserver);
+ UpdateMailPaneConfig();
+ Create3PaneGlobals();
+ verifyAccounts(null, false);
+ msgDBCacheManager.init();
+
+ // set the messenger default window size relative to the screen size
+ // initial default dimensions are 2/3 and 1/2 of the screen dimensions
+ if (!document.documentElement.hasAttribute("width")) {
+ let screenHeight = window.screen.availHeight;
+ let screenWidth = window.screen.availWidth;
+ let defaultHeight = Math.floor(screenHeight * 2 / 3);
+ let defaultWidth = Math.floor(screenWidth / 2);
+
+ // minimum dimensions are 1024x768 less padding unless restrained by screen
+ const minHeight = 768;
+ const minWidth = 1024;
+
+ if (defaultHeight < minHeight)
+ defaultHeight = Math.min(minHeight, screenHeight);
+ if (defaultWidth < minWidth)
+ defaultWidth = Math.min(minWidth, screenWidth);
+
+ // keep some distance to the borders, accounting for window decoration
+ document.documentElement.setAttribute("height", defaultHeight - 48);
+ document.documentElement.setAttribute("width", defaultWidth - 24);
+ }
+
+ // initialize tabmail system - see tabmail.js and tabmail.xml for details
+ let tabmail = GetTabMail();
+ tabmail.registerTabType(gMailNewsTabsType);
+ tabmail.openFirstTab();
+ Services.obs.addObserver(MailWindowIsClosing,
+ "quit-application-requested");
+
+ InitMsgWindow();
+ messenger.setWindow(window, msgWindow);
+
+ InitPanes();
+
+ MigrateJunkMailSettings();
+
+ accountManager.setSpecialFolders();
+ accountManager.loadVirtualFolders();
+ accountManager.addIncomingServerListener(gThreePaneIncomingServerListener);
+
+ AddToSession();
+
+ var startFolderUri = null;
+ //need to add to session before trying to load start folder otherwise listeners aren't
+ //set up correctly.
+ // argument[0] --> folder uri
+ // argument[1] --> optional message key
+ // argument[2] --> optional email address; // Will come from aim; needs to show msgs from buddy's email address.
+ if ("arguments" in window)
+ {
+ var args = window.arguments;
+ // filter our any feed urls that came in as arguments to the new window...
+ if (args.length && /^feed:/i.test(args[0]))
+ {
+ var feedHandler =
+ Cc["@mozilla.org/newsblog-feed-downloader;1"]
+ .getService(Ci.nsINewsBlogFeedDownloader);
+ if (feedHandler)
+ feedHandler.subscribeToFeed(args[0], null, msgWindow);
+ }
+ else
+ {
+ startFolderUri = (args.length > 0) ? args[0] : null;
+ }
+ gStartMsgKey = (args.length > 1) ? args[1] : nsMsgKey_None;
+ gSearchEmailAddress = (args.length > 2) ? args[2] : null;
+ }
+
+ window.setTimeout(loadStartFolder, 0, startFolderUri);
+
+ Services.obs.notifyObservers(window, "mail-startup-done");
+
+ // FIX ME - later we will be able to use onload from the overlay
+ OnLoadMsgHeaderPane();
+
+ gHaveLoadedMessage = false;
+
+ //Set focus to the Thread Pane the first time the window is opened.
+ SetFocusThreadPane();
+
+ // Before and after callbacks for the customizeToolbar code
+ var mailToolbox = getMailToolbox();
+ mailToolbox.customizeInit = MailToolboxCustomizeInit;
+ mailToolbox.customizeDone = MailToolboxCustomizeDone;
+ mailToolbox.customizeChange = MailToolboxCustomizeChange;
+
+ // initialize the sync UI
+ // gSyncUI.init();
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ // Load the periodic filter timer.
+ PeriodicFilterManager.setupFiltering();
+}
+
+function HandleAppCommandEvent(evt)
+{
+ evt.stopPropagation();
+ switch (evt.command)
+ {
+ case "Back":
+ goDoCommand('cmd_goBack');
+ break;
+ case "Forward":
+ goDoCommand('cmd_goForward');
+ break;
+ case "Stop":
+ goDoCommand('cmd_stop');
+ break;
+ case "Search":
+ goDoCommand('cmd_search');
+ break;
+ case "Bookmarks":
+ toAddressBook();
+ break;
+ case "Reload":
+ goDoCommand('cmd_reload');
+ break;
+ case "Home":
+ goDoCommand('cmd_goStartPage');
+ break;
+ default:
+ break;
+ }
+}
+
+function OnUnloadMessenger()
+{
+ Services.prefs.removeObserver("mail.pane_config.dynamic", MailPrefObserver, false);
+ Services.prefs.removeObserver("mail.showCondensedAddresses", MailPrefObserver, false);
+ window.removeEventListener("AppCommand", HandleAppCommandEvent, true);
+ Services.obs.removeObserver(MailWindowIsClosing,
+ "quit-application-requested");
+
+ OnLeavingFolder(gMsgFolderSelected); // mark all read in current folder
+ accountManager.removeIncomingServerListener(gThreePaneIncomingServerListener);
+ GetTabMail().closeTabs();
+
+ // FIX ME - later we will be able to use onload from the overlay
+ OnUnloadMsgHeaderPane();
+ UnloadPanes();
+ OnMailWindowUnload();
+}
+
+// we probably want to warn if more than one tab is closed
+function MailWindowIsClosing(aCancelQuit, aTopic, aData)
+{
+ if (aTopic == "quit-application-requested" &&
+ aCancelQuit instanceof Ci.nsISupportsPRBool &&
+ aCancelQuit.data)
+ return false;
+
+ let tabmail = GetTabMail();
+ let reallyClose = tabmail.warnAboutClosingTabs(tabmail.closingTabsEnum.ALL);
+
+ if (!reallyClose && aTopic == "quit-application-requested")
+ aCancelQuit.data = true;
+
+ return reallyClose;
+}
+
+function Create3PaneGlobals()
+{
+ // Update <mailWindow.js> global variables.
+ accountCentralBox = document.getElementById("accountCentralBox");
+ gDisableViewsSearch = document.getElementById("mailDisableViewsSearch");
+
+ GetMessagePane().collapsed = true;
+}
+
+function loadStartFolder(initialUri)
+{
+ var defaultServer = null;
+ var startFolder;
+ var isLoginAtStartUpEnabled = false;
+
+ //First get default account
+ if (initialUri) {
+ startFolder = MailUtils.getFolderForURI(initialUri);
+ } else {
+ var defaultAccount = accountManager.defaultAccount;
+ if (defaultAccount) {
+ defaultServer = defaultAccount.incomingServer;
+ var rootMsgFolder = defaultServer.rootMsgFolder;
+
+ startFolder = rootMsgFolder;
+ // Enable check new mail once by turning checkmail pref 'on' to bring
+ // all users to one plane. This allows all users to go to Inbox. User can
+ // always go to server settings panel and turn off "Check for new mail at startup"
+ if (!Services.prefs.getBoolPref(kMailCheckOncePrefName))
+ {
+ Services.prefs.setBoolPref(kMailCheckOncePrefName, true);
+ defaultServer.loginAtStartUp = true;
+ }
+
+ // Get the user pref to see if the login at startup is enabled for default account
+ isLoginAtStartUpEnabled = defaultServer.loginAtStartUp;
+
+ // Get Inbox only if login at startup is enabled.
+ if (isLoginAtStartUpEnabled)
+ {
+ //now find Inbox
+ const kInboxFlag = Ci.nsMsgFolderFlags.Inbox;
+ var inboxFolder = rootMsgFolder.getFolderWithFlags(kInboxFlag);
+ if (inboxFolder)
+ startFolder = inboxFolder;
+ }
+ } else {
+ // If no default account then show account central page.
+ ShowAccountCentral();
+ }
+
+ }
+
+ if (startFolder) {
+ try {
+ gFolderTreeView.selectFolder(startFolder);
+ } catch(ex) {
+ // This means we tried to select a folder that isn't in the current
+ // view. Just select the first one in the view then.
+ if (gFolderTreeView._rowMap.length)
+ gFolderTreeView.selectFolder(gFolderTreeView._rowMap[0]._folder);
+ }
+
+ // Perform biff on the server to check for new mail, if:
+ // the login at startup is enabled, and
+ // this feature is not exceptionally overridden, and
+ // the account is not deferred-to or deferred.
+ if (isLoginAtStartUpEnabled &&
+ gLoadStartFolder &&
+ !defaultServer.isDeferredTo &&
+ defaultServer.rootFolder == defaultServer.rootMsgFolder)
+ defaultServer.performBiff(msgWindow);
+ }
+
+ MsgGetMessagesForAllServers(defaultServer);
+
+ if (CheckForUnsentMessages() && !Services.io.offline)
+ {
+ InitPrompts();
+ InitServices();
+
+ var sendUnsentWhenGoingOnlinePref = Services.prefs.getIntPref("offline.send.unsent_messages");
+ if (sendUnsentWhenGoingOnlinePref == 0) // pref is "ask"
+ {
+ var buttonPressed = 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:0});
+ if (buttonPressed == 0)
+ SendUnsentMessages();
+ }
+ else if(sendUnsentWhenGoingOnlinePref == 1) // pref is "yes"
+ SendUnsentMessages();
+ }
+}
+
+function AddToSession()
+{
+ var nsIFolderListener = Ci.nsIFolderListener;
+ var notifyFlags = nsIFolderListener.intPropertyChanged |
+ nsIFolderListener.event;
+ MailServices.mailSession.AddFolderListener(folderListener, notifyFlags);
+}
+
+function InitPanes()
+{
+ gFolderTreeView.load(document.getElementById("folderTree"),
+ "folderTree.json");
+ var folderTree = document.getElementById("folderTree");
+ folderTree.addEventListener("click", FolderPaneOnClick, true);
+ folderTree.addEventListener("mousedown", TreeOnMouseDown, true);
+
+ OnLoadThreadPane();
+ SetupCommandUpdateHandlers();
+}
+
+function UnloadPanes()
+{
+ var folderTree = document.getElementById("folderTree");
+ folderTree.removeEventListener("click", FolderPaneOnClick, true);
+ folderTree.removeEventListener("mousedown", TreeOnMouseDown, true);
+ gFolderTreeView.unload("folderTree.json");
+ UnloadCommandUpdateHandlers();
+}
+
+function AddMutationObserver(callback)
+{
+ new MutationObserver(callback).observe(callback(), {attributes: true, attributeFilter: ["hidden"]});
+}
+
+function OnLoadThreadPane()
+{
+ AddMutationObserver(UpdateAttachmentCol);
+}
+
+function UpdateAttachmentCol()
+{
+ var attachmentCol = document.getElementById("attachmentCol");
+ var threadTree = GetThreadTree();
+ threadTree.setAttribute("noattachcol", attachmentCol.getAttribute("hidden"));
+ threadTree.treeBoxObject.clearStyleAndImageCaches();
+ return attachmentCol;
+}
+
+function GetSearchInput()
+{
+ if (!gSearchInput)
+ gSearchInput = document.getElementById("searchInput");
+ return gSearchInput;
+}
+
+function GetMessagePaneFrame()
+{
+ return window.content;
+}
+
+function FindInSidebar(currentWindow, id)
+{
+ var item = currentWindow.document.getElementById(id);
+ if (item)
+ return item;
+
+ for (var i = 0; i < currentWindow.frames.length; ++i)
+ {
+ var frameItem = FindInSidebar(currentWindow.frames[i], id);
+ if (frameItem)
+ return frameItem;
+ }
+
+ return null;
+}
+
+function GetUnreadCountElement()
+{
+ if (!gUnreadCount)
+ gUnreadCount = document.getElementById('unreadMessageCount');
+ return gUnreadCount;
+}
+
+function GetTotalCountElement()
+{
+ if (!gTotalCount)
+ gTotalCount = document.getElementById('totalMessageCount');
+ return gTotalCount;
+}
+
+function ClearThreadPaneSelection()
+{
+ try {
+ if (gDBView) {
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ var treeSelection = treeView.selection;
+ if (treeSelection)
+ treeSelection.clearSelection();
+ }
+ }
+ catch (ex) {
+ dump("ClearThreadPaneSelection: ex = " + ex + "\n");
+ }
+}
+
+function ClearMessagePane()
+{
+ if (gHaveLoadedMessage)
+ {
+ gHaveLoadedMessage = false;
+ gCurrentDisplayedMessage = null;
+ if (GetMessagePaneFrame().location.href != "about:blank")
+ GetMessagePaneFrame().location.href = "about:blank";
+
+ // hide the message header view AND the message pane...
+ HideMessageHeaderPane();
+ gMessageNotificationBar.clearMsgNotifications();
+ ClearPendingReadTimer();
+ }
+}
+
+// Function to change the highlighted row to where the mouse was clicked
+// without loading the contents of the selected row.
+// It will also keep the outline/dotted line in the original row.
+function ChangeSelectionWithoutContentLoad(event, tree)
+{
+ // usually, we're only interested in tree content clicks, not scrollbars etc.
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var treeBoxObj = tree.treeBoxObject;
+ var treeSelection = tree.view.selection;
+
+ var row = treeBoxObj.getRowAt(event.clientX, event.clientY);
+ // make sure that row.value is valid so that it doesn't mess up
+ // the call to ensureRowIsVisible().
+ if ((row >= 0) && !treeSelection.isSelected(row))
+ {
+ var saveCurrentIndex = treeSelection.currentIndex;
+ treeSelection.selectEventsSuppressed = true;
+ treeSelection.select(row);
+ treeSelection.currentIndex = saveCurrentIndex;
+ treeBoxObj.ensureRowIsVisible(row);
+ treeSelection.selectEventsSuppressed = false;
+
+ // Keep track of which row in the thread pane is currently selected.
+ if (tree.id == "threadTree")
+ gThreadPaneCurrentSelectedIndex = row;
+ }
+ event.stopPropagation();
+}
+
+function TreeOnMouseDown(event)
+{
+ // Detect right mouse click and change the highlight to the row
+ // where the click happened without loading the message headers in
+ // the Folder or Thread Pane.
+ // Same for middle click, which will open the folder/message in a tab.
+ gRightMouseButtonDown = event.button == kMouseButtonRight;
+ if (!gRightMouseButtonDown)
+ gRightMouseButtonDown = AllowOpenTabOnMiddleClick() &&
+ event.button == kMouseButtonMiddle;
+ if (gRightMouseButtonDown)
+ ChangeSelectionWithoutContentLoad(event, event.target.parentNode);
+}
+
+function FolderPaneContextMenuNewTab(event) {
+ var bgLoad = Services.prefs.getBoolPref("mail.tabs.loadInBackground");
+ if (event.shiftKey)
+ bgLoad = !bgLoad;
+ MsgOpenNewTabForFolder(bgLoad);
+}
+
+function FolderPaneOnClick(event)
+{
+ // usually, we're only interested in tree content clicks, not scrollbars etc.
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var folderTree = document.getElementById("folderTree");
+
+ // we may want to open the folder in a new tab on middle click
+ if (event.button == kMouseButtonMiddle)
+ {
+ if (AllowOpenTabOnMiddleClick())
+ {
+ FolderPaneContextMenuNewTab(event);
+ RestoreSelectionWithoutContentLoad(folderTree);
+ return;
+ }
+ }
+
+ // otherwise, we only care about left click events
+ if (event.button != kMouseButtonLeft)
+ return;
+
+ var cell = folderTree.treeBoxObject.getCellAt(event.clientX, event.clientY);
+ if (cell.row == -1)
+ {
+ if (event.originalTarget.localName == "treecol")
+ {
+ // clicking on the name column in the folder pane should not sort
+ event.stopPropagation();
+ }
+ }
+}
+
+function OpenMessageInNewTab(event) {
+ var bgLoad = Services.prefs.getBoolPref("mail.tabs.loadInBackground");
+ if (event.shiftKey)
+ bgLoad = !bgLoad;
+
+ MsgOpenNewTabForMessage(bgLoad);
+}
+
+function GetSelectedMsgFolders()
+{
+ return gFolderTreeView.getSelectedFolders();
+}
+
+function GetSelectedIndices(dbView)
+{
+ try {
+ return dbView.getIndicesForSelection();
+ }
+ catch (ex) {
+ dump("ex = " + ex + "\n");
+ return null;
+ }
+}
+
+function GetLoadedMsgFolder()
+{
+ if (!gDBView) return null;
+ return gDBView.msgFolder;
+}
+
+function GetLoadedMessage()
+{
+ try {
+ return gDBView.URIForFirstSelectedMessage;
+ }
+ catch (ex) {
+ return null;
+ }
+}
+
+//Clear everything related to the current message. called after load start page.
+function ClearMessageSelection()
+{
+ ClearThreadPaneSelection();
+}
+
+// Figures out how many messages are selected (hilighted - does not necessarily
+// have the dotted outline) above a given index row value in the thread pane.
+function NumberOfSelectedMessagesAboveCurrentIndex(index)
+{
+ var numberOfMessages = 0;
+ var indicies = GetSelectedIndices(gDBView);
+
+ if (indicies && indicies.length)
+ {
+ for (var i = 0; i < indicies.length; i++)
+ {
+ if (indicies[i] < index)
+ ++numberOfMessages;
+ else
+ break;
+ }
+ }
+ return numberOfMessages;
+}
+
+function SetNextMessageAfterDelete()
+{
+ var treeSelection = GetThreadTree().view.selection;
+
+ if (treeSelection.isSelected(treeSelection.currentIndex))
+ gNextMessageViewIndexAfterDelete = gDBView.msgToSelectAfterDelete;
+ else if(gDBView.removeRowOnMoveOrDelete)
+ {
+ // Only set gThreadPaneDeleteOrMoveOccurred to true if the message was
+ // truly moved to the trash or deleted, as opposed to an IMAP delete
+ // (where it is only "marked as deleted". This will prevent bug 142065.
+ //
+ // If it's an IMAP delete, then just set gNextMessageViewIndexAfterDelete
+ // to treeSelection.currentIndex (where the outline is at) because nothing
+ // was moved or deleted from the folder.
+ gThreadPaneDeleteOrMoveOccurred = true;
+ gNextMessageViewIndexAfterDelete = treeSelection.currentIndex - NumberOfSelectedMessagesAboveCurrentIndex(treeSelection.currentIndex);
+ }
+ else
+ gNextMessageViewIndexAfterDelete = treeSelection.currentIndex;
+}
+
+function EnsureFolderIndex(treeView, msgFolder) {
+ // Try to get the index of the folder in the tree.
+ let index = treeView.getIndexOfFolder(msgFolder);
+ if (!index) {
+ // If we couldn't find the folder, open the parents.
+ let folder = msgFolder;
+ while (!index && folder) {
+ folder = folder.parent;
+ index = EnsureFolderIndex(treeView, folder);
+ }
+ if (index) {
+ treeView.toggleOpenState(index);
+ index = treeView.getIndexOfFolder(msgFolder);
+ }
+ }
+ return index;
+}
+
+function SelectMsgFolder(msgFolder) {
+ gFolderTreeView.selectFolder(msgFolder);
+}
+
+function SelectMessage(messageUri)
+{
+ var msgHdr = messenger.msgHdrFromURI(messageUri);
+ if (msgHdr)
+ gDBView.selectMsgByKey(msgHdr.messageKey);
+}
+
+function ReloadMessage()
+{
+ gDBView.reloadMessage();
+}
+
+function SetBusyCursor(window, enable)
+{
+ // setCursor() is only available for chrome windows.
+ // However one of our frames is the start page which
+ // is a non-chrome window, so check if this window has a
+ // setCursor method
+ if ("setCursor" in window) {
+ if (enable)
+ window.setCursor("progress");
+ else
+ window.setCursor("auto");
+ }
+
+ var numFrames = window.frames.length;
+ for(var i = 0; i < numFrames; i++)
+ SetBusyCursor(window.frames[i], enable);
+}
+
+function GetDBView()
+{
+ return gDBView;
+}
+
+// Some of the per account junk mail settings have been
+// converted to global prefs. Let's try to migrate some
+// of those settings from the default account.
+function MigrateJunkMailSettings()
+{
+ var junkMailSettingsVersion = Services.prefs.getIntPref("mail.spam.version");
+ if (!junkMailSettingsVersion)
+ {
+ // Get the default account, check to see if we have values for our
+ // globally migrated prefs.
+ var defaultAccount = accountManager.defaultAccount;
+ if (defaultAccount)
+ {
+ // we only care about
+ var prefix = "mail.server." + defaultAccount.incomingServer.key + ".";
+ if (Services.prefs.prefHasUserValue(prefix + "manualMark"))
+ Services.prefs.setBoolPref("mail.spam.manualMark", Services.prefs.getBoolPref(prefix + "manualMark"));
+ if (Services.prefs.prefHasUserValue(prefix + "manualMarkMode"))
+ Services.prefs.setIntPref("mail.spam.manualMarkMode", Services.prefs.getIntPref(prefix + "manualMarkMode"));
+ if (Services.prefs.prefHasUserValue(prefix + "spamLoggingEnabled"))
+ Services.prefs.setBoolPref("mail.spam.logging.enabled", Services.prefs.getBoolPref(prefix + "spamLoggingEnabled"));
+ if (Services.prefs.prefHasUserValue(prefix + "markAsReadOnSpam"))
+ Services.prefs.setBoolPref("mail.spam.markAsReadOnSpam", Services.prefs.getBoolPref(prefix + "markAsReadOnSpam"));
+ }
+ // bump the version so we don't bother doing this again.
+ Services.prefs.setIntPref("mail.spam.version", 1);
+ }
+}
diff --git a/comm/suite/mailnews/content/msgViewNavigation.js b/comm/suite/mailnews/content/msgViewNavigation.js
new file mode 100644
index 0000000000..a7d0496210
--- /dev/null
+++ b/comm/suite/mailnews/content/msgViewNavigation.js
@@ -0,0 +1,243 @@
+/* -*- 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/. */
+
+/* This file contains the js functions necessary to implement view navigation within the 3 pane. */
+
+const {FolderUtils} = ChromeUtils.import("resource:///modules/FolderUtils.jsm");
+
+//NOTE: gMessengerBundle must be defined and set or this Overlay won't work
+
+function GetSubFoldersInFolderPaneOrder(folder)
+{
+ var msgFolders = folder.subFolders;
+
+ function compareFolderSortKey(folder1, folder2) {
+ return folder1.compareSortKeys(folder2);
+ }
+
+ // sort the subfolders
+ msgFolders.sort(compareFolderSortKey);
+ return msgFolders;
+}
+
+function FindNextChildFolder(aParent, aAfter)
+{
+ // Search the child folders of aParent for unread messages
+ // but in the case that we are working up from the current folder
+ // we need to skip up to and including the current folder
+ // we skip the current folder in case a mail view is hiding unread messages
+ if (aParent.getNumUnread(true) > 0) {
+ var subFolders = GetSubFoldersInFolderPaneOrder(aParent);
+ var i = 0;
+ var folder = null;
+
+ // Skip folders until after the specified child
+ while (folder != aAfter)
+ folder = subFolders[i++];
+
+ let ignoreFlags = Ci.nsMsgFolderFlags.Trash | Ci.nsMsgFolderFlags.SentMail |
+ Ci.nsMsgFolderFlags.Drafts | Ci.nsMsgFolderFlags.Queue |
+ Ci.nsMsgFolderFlags.Templates | Ci.nsMsgFolderFlags.Junk;
+ while (i < subFolders.length) {
+ folder = subFolders[i++];
+ // if there is unread mail in the trash, sent, drafts, unsent messages
+ // templates or junk special folder,
+ // we ignore it when doing cross folder "next" navigation
+ if (!folder.isSpecialFolder(ignoreFlags, true)) {
+ if (folder.getNumUnread(false) > 0)
+ return folder;
+
+ folder = FindNextChildFolder(folder, null);
+ if (folder)
+ return folder;
+ }
+ }
+ }
+
+ return null;
+}
+
+function FindNextFolder()
+{
+ // look for the next folder, this will only look on the current account
+ // and below us, in the folder pane
+ // note use of gDBView restricts this function to message folders
+ // otherwise you could go next unread from a server
+ var folder = FindNextChildFolder(gDBView.msgFolder, null);
+ if (folder)
+ return folder;
+
+ // didn't find folder in children
+ // go up to the parent, and start at the folder after the current one
+ // unless we are at a server, in which case bail out.
+ for (folder = gDBView.msgFolder; !folder.isServer; ) {
+
+ var parent = folder.parent;
+ folder = FindNextChildFolder(parent, folder);
+ if (folder)
+ return folder;
+
+ // none at this level after the current folder. go up.
+ folder = parent;
+ }
+
+ // nothing in the current account, start with the next account (below)
+ // and try until we hit the bottom of the folder pane
+
+ // start at the account after the current account
+ var rootFolders = GetRootFoldersInFolderPaneOrder();
+ for (var i = 0; i < rootFolders.length; i++) {
+ if (rootFolders[i].URI == gDBView.msgFolder.server.serverURI)
+ break;
+ }
+
+ for (var j = i + 1; j < rootFolders.length; j++) {
+ folder = FindNextChildFolder(rootFolders[j], null);
+ if (folder)
+ return folder;
+ }
+
+ // if nothing from the current account down to the bottom
+ // (of the folder pane), start again at the top.
+ for (j = 0; j <= i; j++) {
+ folder = FindNextChildFolder(rootFolders[j], null);
+ if (folder)
+ return folder;
+ }
+ return null;
+}
+
+function GetRootFoldersInFolderPaneOrder()
+{
+ var accounts = FolderUtils.allAccountsSorted(false);
+
+ var serversMsgFolders = [];
+ for (var account of accounts)
+ serversMsgFolders.push(account.incomingServer.rootMsgFolder);
+
+ return serversMsgFolders;
+}
+
+function CrossFolderNavigation(type)
+{
+ // do cross folder navigation for next unread message/thread and message history
+ if (type != nsMsgNavigationType.nextUnreadMessage &&
+ type != nsMsgNavigationType.nextUnreadThread &&
+ type != nsMsgNavigationType.forward &&
+ type != nsMsgNavigationType.back)
+ return;
+
+ if (type == nsMsgNavigationType.nextUnreadMessage ||
+ type == nsMsgNavigationType.nextUnreadThread)
+ {
+
+ var nextMode = Services.prefs.getIntPref("mailnews.nav_crosses_folders");
+ // 0: "next" goes to the next folder, without prompting
+ // 1: "next" goes to the next folder, and prompts (the default)
+ // 2: "next" does nothing when there are no unread messages
+
+ // not crossing folders, don't find next
+ if (nextMode == 2)
+ return;
+
+ var folder = FindNextFolder();
+ if (folder && (gDBView.msgFolder.URI != folder.URI))
+ {
+ switch (nextMode)
+ {
+ case 0:
+ // do this unconditionally
+ gNextMessageAfterLoad = type;
+ SelectMsgFolder(folder);
+ break;
+ case 1:
+ default:
+ var promptText = gMessengerBundle.getFormattedString("advanceNextPrompt", [ folder.name ], 1);
+ if (Services.prompt.confirmEx(window, null, promptText,
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null, null, null, null, {}) == 0)
+ {
+ gNextMessageAfterLoad = type;
+ SelectMsgFolder(folder);
+ }
+ break;
+ }
+ }
+ }
+ else
+ {
+ // if no message is loaded, relPos should be 0, to
+ // go back to the previously loaded message
+ var relPos = (type == nsMsgNavigationType.forward)
+ ? 1 : ((GetLoadedMessage()) ? -1 : 0);
+ var folderUri = messenger.getFolderUriAtNavigatePos(relPos);
+ var msgHdr = messenger.msgHdrFromURI(messenger.getMsgUriAtNavigatePos(relPos));
+ gStartMsgKey = msgHdr.messageKey;
+ var curPos = messenger.navigatePos;
+ curPos += relPos;
+ messenger.navigatePos = curPos;
+ SelectMsgFolder(MailUtils.getFolderForURI(folderUri));
+ }
+}
+
+
+function ScrollToMessage(type, wrap, selectMessage)
+{
+ try {
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ var treeSelection = treeView.selection;
+ var currentIndex = treeSelection.currentIndex;
+
+ var resultId = new Object;
+ var resultIndex = new Object;
+ var threadIndex = new Object;
+
+ let elidedFlag = Ci.nsMsgMessageFlags.Elided;
+ let summarizeSelection =
+ Services.prefs.getBoolPref("mail.operate_on_msgs_in_collapsed_threads");
+
+ // if we're doing next unread, and a collapsed thread is selected, and
+ // the top level message is unread, just set the result manually to
+ // the top level message, without using gDBView.viewNavigate.
+ if (summarizeSelection && type == nsMsgNavigationType.nextUnreadMessage &&
+ currentIndex != -1 &&
+ gDBView.getFlagsAt(currentIndex) & elidedFlag &&
+ gDBView.isContainer(currentIndex) &&
+ ! (gDBView.getFlagsAt(currentIndex) &
+ Ci.nsMsgMessageFlags.Read)) {
+ resultIndex.value = currentIndex;
+ resultId.value = gDBView.getKeyAt(currentIndex);
+ } else {
+ gDBView.viewNavigate(type, resultId, resultIndex, threadIndex, true /* wrap */);
+ }
+
+ // only scroll and select if we found something
+ if ((resultId.value != nsMsgViewIndex_None) && (resultIndex.value != nsMsgViewIndex_None)) {
+ if (gDBView.getFlagsAt(resultIndex.value) & elidedFlag &&
+ summarizeSelection)
+ gDBView.toggleOpenState(resultIndex.value);
+
+ if (selectMessage){
+ treeSelection.select(resultIndex.value);
+ }
+ EnsureRowInThreadTreeIsVisible(resultIndex.value);
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ catch (ex) {
+ return false;
+ }
+}
+
+function GoNextMessage(type, startFromBeginning)
+{
+ if (!ScrollToMessage(type, startFromBeginning, true))
+ CrossFolderNavigation(type);
+
+ SetFocusThreadPaneIfNotOnMessagePane();
+}
diff --git a/comm/suite/mailnews/content/msgViewPickerOverlay.js b/comm/suite/mailnews/content/msgViewPickerOverlay.js
new file mode 100644
index 0000000000..39b3286b5d
--- /dev/null
+++ b/comm/suite/mailnews/content/msgViewPickerOverlay.js
@@ -0,0 +1,413 @@
+/* -*- 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/. */
+
+// menuitem value constants
+// tag views have kViewTagMarker + their key as value
+const kViewItemAll = 0;
+const kViewItemUnread = 1;
+const kViewItemTags = 2; // former labels used values 2-6
+const kViewItemNotDeleted = 3;
+const kViewItemVirtual = 7;
+const kViewItemCustomize = 8;
+const kViewItemFirstCustom = 9;
+
+const kViewCurrent = "current-view";
+const kViewCurrentTag = "current-view-tag";
+const kViewTagMarker = ":";
+
+var gMailViewList = null;
+var gCurrentViewValue = kViewItemAll;
+var gCurrentViewLabel = "";
+var gSaveDefaultSVTerms;
+
+var nsMsgSearchScope = Ci.nsMsgSearchScope;
+var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+var nsMsgSearchOp = Ci.nsMsgSearchOp;
+
+
+// perform the view/action requested by the aValue string
+// and set the view picker label to the aLabel string
+function ViewChange(aValue, aLabel)
+{
+ if (aValue == kViewItemCustomize || aValue == kViewItemVirtual)
+ {
+ // restore to the previous view value, in case they cancel
+ UpdateViewPicker(gCurrentViewValue, gCurrentViewLabel);
+ if (aValue == kViewItemCustomize)
+ LaunchCustomizeDialog();
+ else
+ {
+ gFolderTreeController.newVirtualFolder(gCurrentViewLabel,
+ gSaveDefaultSVTerms);
+ }
+ return;
+ }
+
+ // persist the view
+ gCurrentViewValue = aValue;
+ gCurrentViewLabel = aLabel;
+ SetMailViewForFolder(GetFirstSelectedMsgFolder(), gCurrentViewValue)
+ UpdateViewPicker(gCurrentViewValue, gCurrentViewLabel);
+
+ // tag menuitem values are of the form :<keyword>
+ if (isNaN(aValue))
+ {
+ // split off the tag key
+ var tagkey = aValue.substr(kViewTagMarker.length);
+ ViewTagKeyword(tagkey);
+ }
+ else
+ {
+ var numval = Number(aValue);
+ switch (numval)
+ {
+ case kViewItemAll: // View All
+ gDefaultSearchViewTerms = null;
+ break;
+ case kViewItemUnread: // Unread
+ ViewNewMail();
+ break;
+ case kViewItemNotDeleted: // Not deleted
+ ViewNotDeletedMail();
+ break;
+ default:
+ // for legacy reasons, custom views start at index 9
+ LoadCustomMailView(numval - kViewItemFirstCustom);
+ break;
+ }
+ }
+ gSaveDefaultSVTerms = gDefaultSearchViewTerms;
+ onEnterInSearchBar();
+ gQSViewIsDirty = true;
+}
+
+
+function ViewChangeByMenuitem(aMenuitem)
+{
+ // Mac View menu menuitems don't have XBL bindings
+ ViewChange(aMenuitem.getAttribute("value"), aMenuitem.getAttribute("label"));
+}
+
+
+function ViewChangeByValue(aValue)
+{
+ ViewChange(aValue, GetLabelForValue(aValue));
+}
+
+function ViewChangeByFolder(aFolder)
+{
+ var result = GetMailViewForFolder(aFolder);
+ ViewChangeByValue(result);
+}
+
+function GetLabelForValue(aValue)
+{
+ var label = "";
+ var viewPickerPopup = document.getElementById("viewPickerPopup");
+ if (viewPickerPopup)
+ {
+ // grab the label for the menulist from one of its menuitems
+ var selectedItems = viewPickerPopup.getElementsByAttribute("value", aValue);
+ if (!selectedItems || !selectedItems.length)
+ {
+ // we may have a new item
+ RefreshAllViewPopups(viewPickerPopup);
+ selectedItems = viewPickerPopup.getElementsByAttribute("value", aValue);
+ }
+ label = selectedItems && selectedItems.length && selectedItems.item(0).getAttribute("label");
+ }
+ return label;
+}
+
+function UpdateViewPickerByValue(aValue)
+{
+ UpdateViewPicker(aValue, GetLabelForValue(aValue));
+}
+
+function UpdateViewPicker(aValue, aLabel)
+{
+ var viewPicker = document.getElementById("viewPicker");
+ if (viewPicker)
+ {
+ viewPicker.value = aValue;
+ viewPicker.setAttribute("label", aLabel);
+ }
+}
+
+function GetFolderInfo(aFolder)
+{
+ try
+ {
+ var db = aFolder.msgDatabase;
+ if (db)
+ return db.dBFolderInfo;
+ }
+ catch (ex) {}
+ return null;
+}
+
+
+function GetMailViewForFolder(aFolder)
+{
+ var val = "";
+ var folderInfo = GetFolderInfo(aFolder);
+ if (folderInfo)
+ {
+ val = folderInfo.getCharProperty(kViewCurrentTag);
+ if (!val)
+ {
+ // no new view value, thus using the old
+ var numval = folderInfo.getUint32Property(kViewCurrent, kViewItemAll);
+ // and migrate it, if it's a former label view (label views used values 2-6)
+ if ((kViewItemTags <= numval) && (numval < kViewItemVirtual))
+ val = kViewTagMarker + "$label" + (val - 1);
+ else
+ val = numval;
+ }
+ }
+ return val;
+}
+
+
+function SetMailViewForFolder(aFolder, aValue)
+{
+ var folderInfo = GetFolderInfo(aFolder);
+ if (folderInfo)
+ {
+ // we can't map tags back to labels in general,
+ // so set view to all for backwards compatibility in this case
+ folderInfo.setUint32Property (kViewCurrent, isNaN(aValue) ? kViewItemAll : aValue);
+ folderInfo.setCharProperty(kViewCurrentTag, aValue);
+ }
+}
+
+
+function LaunchCustomizeDialog()
+{
+ OpenOrFocusWindow({}, "mailnews:mailviewlist", "chrome://messenger/content/mailViewList.xul");
+}
+
+
+function LoadCustomMailView(index)
+{
+ PrepareForViewChange();
+ var searchTermsArrayForQS = CreateGroupedSearchTerms(gMailViewList.getMailViewAt(index).searchTerms);
+ createSearchTermsWithList(searchTermsArrayForQS);
+ AddVirtualFolderTerms(searchTermsArrayForQS);
+ gDefaultSearchViewTerms = searchTermsArrayForQS;
+}
+
+
+function ViewTagKeyword(keyword)
+{
+ PrepareForViewChange();
+
+ // create an i supports array to store our search terms
+ var searchTermsArray = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ var term = gSearchSession.createTerm();
+ var value = term.value;
+
+ value.str = keyword;
+ value.attrib = nsMsgSearchAttrib.Keywords;
+ term.value = value;
+ term.attrib = nsMsgSearchAttrib.Keywords;
+ term.op = nsMsgSearchOp.Contains;
+ term.booleanAnd = true;
+
+ searchTermsArray.appendElement(term);
+ AddVirtualFolderTerms(searchTermsArray);
+ createSearchTermsWithList(searchTermsArray);
+ gDefaultSearchViewTerms = searchTermsArray;
+}
+
+
+function ViewNewMail()
+{
+ PrepareForViewChange();
+
+ // create an i supports array to store our search terms
+ var searchTermsArray = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ var term = gSearchSession.createTerm();
+ var value = term.value;
+
+ value.status = 1;
+ value.attrib = nsMsgSearchAttrib.MsgStatus;
+ term.value = value;
+ term.attrib = nsMsgSearchAttrib.MsgStatus;
+ term.op = nsMsgSearchOp.Isnt;
+ term.booleanAnd = true;
+ searchTermsArray.appendElement(term);
+
+ AddVirtualFolderTerms(searchTermsArray);
+
+ createSearchTermsWithList(searchTermsArray);
+ // not quite right - these want to be just the view terms...but it might not matter.
+ gDefaultSearchViewTerms = searchTermsArray;
+}
+
+
+function ViewNotDeletedMail()
+{
+ PrepareForViewChange();
+
+ // create an i supports array to store our search terms
+ var searchTermsArray = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ var term = gSearchSession.createTerm();
+ var value = term.value;
+
+ value.status = 0x00200000;
+ value.attrib = nsMsgSearchAttrib.MsgStatus;
+ term.value = value;
+ term.attrib = nsMsgSearchAttrib.MsgStatus;
+ term.op = nsMsgSearchOp.Isnt;
+ term.booleanAnd = true;
+ searchTermsArray.appendElement(term);
+
+ AddVirtualFolderTerms(searchTermsArray);
+
+ createSearchTermsWithList(searchTermsArray);
+ // not quite right - these want to be just the view terms...but it might not matter.
+ gDefaultSearchViewTerms = searchTermsArray;
+}
+
+
+function AddVirtualFolderTerms(searchTermsArray)
+{
+ // add in any virtual folder terms
+ var virtualFolderSearchTerms = (gVirtualFolderTerms || gXFVirtualFolderTerms);
+ if (virtualFolderSearchTerms)
+ {
+ for (let virtualFolderSearchTerm of virtualFolderSearchTerms)
+ {
+ searchTermsArray.appendElement(virtualFolderSearchTerm);
+ }
+ }
+}
+
+
+function PrepareForViewChange()
+{
+ // this is a problem - it saves the current view in gPreQuickSearchView
+ // then we eventually call onEnterInSearchBar, and we think we need to restore the pre search view!
+ initializeSearchBar();
+ ClearThreadPaneSelection();
+ ClearMessagePane();
+}
+
+
+// refresh view popup and its subpopups
+function RefreshAllViewPopups(aViewPopup)
+{
+ var menupopups = aViewPopup.getElementsByTagName("menupopup");
+ if (menupopups.length > 1)
+ {
+ // when we have menupopups, we assume both tags and custom views are there
+ RefreshTagsPopup(menupopups[0]);
+ RefreshCustomViewsPopup(menupopups[1]);
+ }
+}
+
+
+function RefreshViewPopup(aViewPopup)
+{
+ // mark default views if selected
+ let viewAll = aViewPopup.getElementsByAttribute("value", kViewItemAll)[0];
+ viewAll.setAttribute("checked", gCurrentViewValue == kViewItemAll);
+ let viewUnread =
+ aViewPopup.getElementsByAttribute("value", kViewItemUnread)[0];
+ viewUnread.setAttribute("checked", gCurrentViewValue == kViewItemUnread);
+
+ let viewNotDeleted =
+ aViewPopup.getElementsByAttribute("value", kViewItemNotDeleted)[0];
+ var folderArray = GetSelectedMsgFolders();
+ if (folderArray.length == 0)
+ return;
+
+ // Only show the "Not Deleted" item for IMAP servers
+ // that are using the IMAP delete model.
+ viewNotDeleted.setAttribute("hidden", true);
+ var msgFolder = folderArray[0];
+ var server = msgFolder.server;
+ if (server.type == "imap")
+ {
+ let imapServer =
+ server.QueryInterface(Ci.nsIImapIncomingServer);
+ if (imapServer.deleteModel == Ci.nsMsgImapDeleteModels.IMAPDelete)
+ {
+ viewNotDeleted.setAttribute("hidden", false);
+ viewNotDeleted.setAttribute("checked",
+ gCurrentViewValue == kViewItemNotDeleted);
+ }
+ }
+}
+
+
+function RefreshCustomViewsPopup(aMenupopup)
+{
+ // for each mail view in the msg view list, add an entry in our combo box
+ if (!gMailViewList)
+ gMailViewList = Cc["@mozilla.org/messenger/mailviewlist;1"]
+ .getService(Ci.nsIMsgMailViewList);
+ // remove all menuitems
+ while (aMenupopup.hasChildNodes())
+ aMenupopup.lastChild.remove();
+
+ // now rebuild the list
+ var currentView = isNaN(gCurrentViewValue) ? kViewItemAll : Number(gCurrentViewValue);
+ var numItems = gMailViewList.mailViewCount;
+ for (var i = 0; i < numItems; ++i)
+ {
+ var viewInfo = gMailViewList.getMailViewAt(i);
+ var menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", viewInfo.prettyName);
+ menuitem.setAttribute("value", kViewItemFirstCustom + i);
+ menuitem.setAttribute("name", "viewmessages");
+ menuitem.setAttribute("type", "radio");
+ if (kViewItemFirstCustom + i == currentView)
+ menuitem.setAttribute("checked", true);
+ aMenupopup.appendChild(menuitem);
+ }
+}
+
+
+function RefreshTagsPopup(aMenupopup)
+{
+ // remove all menuitems
+ while (aMenupopup.hasChildNodes())
+ aMenupopup.lastChild.remove();
+
+ // create tag menuitems
+ var currentTagKey = isNaN(gCurrentViewValue) ? gCurrentViewValue.substr(kViewTagMarker.length) : "";
+ var tagArray = MailServices.tags.getAllTags();
+ for (var i = 0; i < tagArray.length; ++i)
+ {
+ var tagInfo = tagArray[i];
+ var menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", tagInfo.tag);
+ menuitem.setAttribute("value", kViewTagMarker + tagInfo.key);
+ menuitem.setAttribute("name", "viewmessages");
+ menuitem.setAttribute("type", "radio");
+ if (tagInfo.key == currentTagKey)
+ menuitem.setAttribute("checked", true);
+ var color = tagInfo.color;
+ if (color)
+ menuitem.setAttribute("class", "lc-" + color.substr(1));
+ aMenupopup.appendChild(menuitem);
+ }
+}
+
+
+function ViewPickerOnLoad()
+{
+ var viewPickerPopup = document.getElementById("viewPickerPopup");
+ if (viewPickerPopup)
+ RefreshAllViewPopups(viewPickerPopup);
+}
+
+
+window.addEventListener("load", ViewPickerOnLoad);
diff --git a/comm/suite/mailnews/content/nsDragAndDrop.js b/comm/suite/mailnews/content/nsDragAndDrop.js
new file mode 100644
index 0000000000..8808e5ecd0
--- /dev/null
+++ b/comm/suite/mailnews/content/nsDragAndDrop.js
@@ -0,0 +1,595 @@
+/* -*- 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/. */
+
+////////////////////////////////////////////////////////////////////////
+//
+// USE OF THIS API FOR DRAG AND DROP IS DEPRECATED!
+// Do not use this file for new code.
+//
+// For documentation about what to use instead, see:
+// http://developer.mozilla.org/En/DragDrop/Drag_and_Drop
+//
+////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * nsTransferable - a wrapper for nsITransferable that simplifies
+ * javascript clipboard and drag&drop. for use in
+ * these situations you should use the nsClipboard
+ * and nsDragAndDrop wrappers for more convenience
+ **/
+
+var nsTransferable = {
+ /**
+ * nsITransferable set (TransferData aTransferData) ;
+ *
+ * Creates a transferable with data for a list of supported types ("flavours")
+ *
+ * @param TransferData aTransferData
+ * a javascript object in the format described above
+ **/
+ set: function (aTransferDataSet)
+ {
+ var trans = this.createTransferable();
+ for (var i = 0; i < aTransferDataSet.dataList.length; ++i)
+ {
+ var currData = aTransferDataSet.dataList[i];
+ var currFlavour = currData.flavour.contentType;
+ trans.addDataFlavor(currFlavour);
+ var supports = null; // nsISupports data
+ var length = 0;
+ if (currData.flavour.dataIIDKey == "nsISupportsString")
+ {
+ supports = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+
+ supports.data = currData.supports;
+ length = supports.data.length;
+ }
+ else
+ {
+ // non-string data.
+ supports = currData.supports;
+ length = 0; // kFlavorHasDataProvider
+ }
+ trans.setTransferData(currFlavour, supports, length * 2);
+ }
+ return trans;
+ },
+
+ /**
+ * TransferData/TransferDataSet get (FlavourSet aFlavourSet,
+ * Function aRetrievalFunc, Boolean aAnyFlag) ;
+ *
+ * Retrieves data from the transferable provided in aRetrievalFunc, formatted
+ * for more convenient access.
+ *
+ * @param FlavourSet aFlavourSet
+ * a FlavourSet object that contains a list of supported flavours.
+ * @param Function aRetrievalFunc
+ * a reference to a function that returns a nsIArray of nsITransferables
+ * for each item from the specified source (clipboard/drag&drop etc)
+ * @param Boolean aAnyFlag
+ * a flag specifying whether or not a specific flavour is requested. If false,
+ * data of the type of the first flavour in the flavourlist parameter is returned,
+ * otherwise the best flavour supported will be returned.
+ **/
+ get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
+ {
+ if (!aRetrievalFunc)
+ throw "No data retrieval handler provided!";
+
+ var array = aRetrievalFunc(aFlavourSet);
+ var dataArray = [];
+
+ // Iterate over the number of items returned from aRetrievalFunc. For
+ // clipboard operations, this is 1, for drag and drop (where multiple
+ // items may have been dragged) this could be >1.
+ for (let i = 0; i < array.length; i++)
+ {
+ let trans = array.queryElementAt(i, Ci.nsITransferable);
+ if (!trans)
+ continue;
+
+ var data = { };
+ var length = { };
+
+ var currData = null;
+ if (aAnyFlag)
+ {
+ var flavour = { };
+ trans.getAnyTransferData(flavour, data, length);
+ if (data && flavour)
+ {
+ var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
+ if (selectedFlavour)
+ dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
+ }
+ }
+ else
+ {
+ var firstFlavour = aFlavourSet.flavours[0];
+ trans.getTransferData(firstFlavour, data, length);
+ if (data && firstFlavour)
+ dataArray[i] = FlavourToXfer(data.value, length.value, firstFlavour);
+ }
+ }
+ return new TransferDataSet(dataArray);
+ },
+
+ /**
+ * nsITransferable createTransferable (void) ;
+ *
+ * Creates and returns a transferable object.
+ **/
+ createTransferable: function ()
+ {
+ const kXferableContractID = "@mozilla.org/widget/transferable;1";
+ const kXferableIID = Ci.nsITransferable;
+ var trans = Cc[kXferableContractID].createInstance(kXferableIID);
+ trans.init(null);
+ return trans;
+ }
+};
+
+/**
+ * A FlavourSet is a simple type that represents a collection of Flavour objects.
+ * FlavourSet is constructed from an array of Flavours, and stores this list as
+ * an array and a hashtable. The rationale for the dual storage is as follows:
+ *
+ * Array: Ordering is important when adding data flavours to a transferable.
+ * Flavours added first are deemed to be 'preferred' by the client.
+ * Hash: Convenient lookup of flavour data using the content type (MIME type)
+ * of data as a key.
+ */
+function FlavourSet(aFlavourList)
+{
+ this.flavours = aFlavourList || [];
+ this.flavourTable = { };
+
+ this._XferID = "FlavourSet";
+
+ for (var i = 0; i < this.flavours.length; ++i)
+ this.flavourTable[this.flavours[i].contentType] = this.flavours[i];
+}
+
+FlavourSet.prototype = {
+ appendFlavour: function (aFlavour, aFlavourIIDKey)
+ {
+ var flavour = new Flavour (aFlavour, aFlavourIIDKey);
+ this.flavours.push(flavour);
+ this.flavourTable[flavour.contentType] = flavour;
+ }
+};
+
+/**
+ * A Flavour is a simple type that represents a data type that can be handled.
+ * It takes a content type (MIME type) which is used when storing data on the
+ * system clipboard/drag and drop, and an IIDKey (string interface name
+ * which is used to QI data to an appropriate form. The default interface is
+ * assumed to be wide-string.
+ */
+function Flavour(aContentType, aDataIIDKey)
+{
+ this.contentType = aContentType;
+ this.dataIIDKey = aDataIIDKey || "nsISupportsString";
+
+ this._XferID = "Flavour";
+}
+
+function TransferDataBase() {}
+TransferDataBase.prototype = {
+ push: function (aItems)
+ {
+ this.dataList.push(aItems);
+ },
+
+ get first ()
+ {
+ return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
+ }
+};
+
+/**
+ * TransferDataSet is a list (array) of TransferData objects, which represents
+ * data dragged from one or more elements.
+ */
+function TransferDataSet(aTransferDataList)
+{
+ this.dataList = aTransferDataList || [];
+
+ this._XferID = "TransferDataSet";
+}
+TransferDataSet.prototype = TransferDataBase.prototype;
+
+/**
+ * TransferData is a list (array) of FlavourData for all the applicable content
+ * types associated with a drag from a single item.
+ */
+function TransferData(aFlavourDataList)
+{
+ this.dataList = aFlavourDataList || [];
+
+ this._XferID = "TransferData";
+}
+TransferData.prototype = {
+ __proto__: TransferDataBase.prototype,
+
+ addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
+ {
+ this.dataList.push(new FlavourData(aData, aLength,
+ new Flavour(aFlavourString, aDataIIDKey)));
+ }
+};
+
+/**
+ * FlavourData is a type that represents data retrieved from the system
+ * clipboard or drag and drop. It is constructed internally by the Transferable
+ * using the raw (nsISupports) data from the clipboard, the length of the data,
+ * and an object of type Flavour representing the type. Clients implementing
+ * IDragDropObserver receive an object of this type in their implementation of
+ * onDrop. They access the 'data' property to retrieve data, which is either data
+ * QI'ed to a usable form, or unicode string.
+ */
+function FlavourData(aData, aLength, aFlavour)
+{
+ this.supports = aData;
+ this.contentLength = aLength;
+ this.flavour = aFlavour || null;
+
+ this._XferID = "FlavourData";
+}
+
+FlavourData.prototype = {
+ get data ()
+ {
+ if (this.flavour &&
+ this.flavour.dataIIDKey != "nsISupportsString")
+ return this.supports.QueryInterface(Ci[this.flavour.dataIIDKey]);
+
+ var supports = this.supports;
+ if (supports instanceof Ci.nsISupportsString)
+ return supports.data.substring(0, this.contentLength/2);
+
+ return supports;
+ }
+}
+
+/**
+ * Create a TransferData object with a single FlavourData entry. Used when
+ * unwrapping data of a specific flavour from the drag service.
+ */
+function FlavourToXfer(aData, aLength, aFlavour)
+{
+ return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
+}
+
+var transferUtils = {
+
+ retrieveURLFromData: function (aData, flavour)
+ {
+ switch (flavour) {
+ case "text/unicode":
+ case "text/plain":
+ case "text/x-moz-text-internal":
+ return aData.replace(/^\s+|\s+$/g, "");
+ case "text/x-moz-url":
+ return ((aData instanceof Ci.nsISupportsString) ? aData.toString() : aData).split("\n")[0];
+ case "application/x-moz-file":
+ var fileHandler = Services.io.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler);
+ return fileHandler.getURLSpecFromFile(aData);
+ }
+ return null;
+ }
+
+}
+
+/**
+ * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
+ * and nsIDragService/nsIDragSession.
+ *
+ * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
+ * 'ondragdrop' event handlers on your XML element, e.g.
+ * <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"
+ * ondragover="nsDragAndDrop.dragOver(event, observer);"
+ * ondragdrop="nsDragAndDrop.drop(event, observer);"/>
+ *
+ * You need to create an observer js object with the following member
+ * functions:
+ * Object onDragStart (event) // called when drag initiated,
+ * // returns flavour list with data
+ * // to stuff into transferable
+ * void onDragOver (Object flavour) // called when element is dragged
+ * // over, so that it can perform
+ * // any drag-over feedback for provided
+ * // flavour
+ * void onDrop (Object data) // formatted data object dropped.
+ * Object getSupportedFlavours () // returns a flavour list so that
+ * // nsTransferable can determine
+ * // whether or not to accept drop.
+ **/
+
+var nsDragAndDrop = {
+
+ _mDS: null,
+ get mDragService()
+ {
+ if (!this._mDS)
+ {
+ const kDSContractID = "@mozilla.org/widget/dragservice;1";
+ const kDSIID = Ci.nsIDragService;
+ this._mDS = Cc[kDSContractID].getService(kDSIID);
+ }
+ return this._mDS;
+ },
+
+ /**
+ * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
+ *
+ * called when a drag on an element is started.
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by the drag init
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ **/
+ startDrag: function (aEvent, aDragDropObserver)
+ {
+ if (!("onDragStart" in aDragDropObserver))
+ return;
+
+ const kDSIID = Ci.nsIDragService;
+ var dragAction = { action: kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_MOVE + kDSIID.DRAGDROP_ACTION_LINK };
+
+ var transferData = { data: null };
+ try
+ {
+ aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
+ }
+ catch (e)
+ {
+ return; // not a draggable item, bail!
+ }
+
+ if (!transferData.data) return;
+ transferData = transferData.data;
+
+ var dt = aEvent.dataTransfer;
+ var count = 0;
+ do {
+ var tds = transferData._XferID == "TransferData"
+ ? transferData
+ : transferData.dataList[count]
+ for (var i = 0; i < tds.dataList.length; ++i)
+ {
+ var currData = tds.dataList[i];
+ var currFlavour = currData.flavour.contentType;
+ var value = currData.supports;
+ if (value instanceof Ci.nsISupportsString)
+ value = value.toString();
+ dt.mozSetDataAt(currFlavour, value, count);
+ }
+
+ count++;
+ }
+ while (transferData._XferID == "TransferDataSet" &&
+ count < transferData.dataList.length);
+
+ dt.effectAllowed = "all";
+ // a drag targeted at a tree should instead use the treechildren so that
+ // the current selection is used as the drag feedback
+ dt.addElement(aEvent.originalTarget.localName == "treechildren" ?
+ aEvent.originalTarget : aEvent.target);
+ aEvent.stopPropagation();
+ },
+
+ /**
+ * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
+ *
+ * called when a drag passes over this element
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by passing over the element
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ **/
+ dragOver: function (aEvent, aDragDropObserver)
+ {
+ if (!("onDragOver" in aDragDropObserver))
+ return;
+ if (!this.checkCanDrop(aEvent, aDragDropObserver))
+ return;
+ var flavourSet = aDragDropObserver.getSupportedFlavours();
+ for (var flavour in flavourSet.flavourTable)
+ {
+ if (this.mDragSession.isDataFlavorSupported(flavour))
+ {
+ aDragDropObserver.onDragOver(aEvent,
+ flavourSet.flavourTable[flavour],
+ this.mDragSession);
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ break;
+ }
+ }
+ },
+
+ mDragSession: null,
+
+ /**
+ * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
+ *
+ * called when the user drops on the element
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by the drop
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ **/
+ drop: function (aEvent, aDragDropObserver)
+ {
+ if (!("onDrop" in aDragDropObserver))
+ return;
+ if (!this.checkCanDrop(aEvent, aDragDropObserver))
+ return;
+
+ var flavourSet = aDragDropObserver.getSupportedFlavours();
+
+ var dt = aEvent.dataTransfer;
+ var dataArray = [];
+ var count = dt.mozItemCount;
+ for (var i = 0; i < count; ++i) {
+ var types = dt.mozTypesAt(i);
+ for (var j = 0; j < flavourSet.flavours.length; j++) {
+ var type = flavourSet.flavours[j].contentType;
+ // dataTransfer uses text/plain but older code used text/unicode, so
+ // switch this for compatibility
+ var modtype = (type == "text/unicode") ? "text/plain" : type;
+ if (Array.from(types).includes(modtype)) {
+ var data = dt.mozGetDataAt(modtype, i);
+ if (data) {
+ // Non-strings need some non-zero value used for their data length.
+ const kNonStringDataLength = 4;
+
+ var length = (typeof data == "string") ? data.length : kNonStringDataLength;
+ dataArray[i] = FlavourToXfer(data, length, flavourSet.flavourTable[type]);
+ break;
+ }
+ }
+ }
+ }
+
+ var transferData = new TransferDataSet(dataArray)
+
+ // hand over to the client to respond to dropped data
+ var multiple = "canHandleMultipleItems" in aDragDropObserver && aDragDropObserver.canHandleMultipleItems;
+ var dropData = multiple ? transferData : transferData.first.first;
+ aDragDropObserver.onDrop(aEvent, dropData, this.mDragSession);
+ aEvent.stopPropagation();
+ },
+
+ /**
+ * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
+ *
+ * called when a drag leaves this element
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by leaving the element
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ **/
+ dragExit: function (aEvent, aDragDropObserver)
+ {
+ if (!this.checkCanDrop(aEvent, aDragDropObserver))
+ return;
+ if ("onDragExit" in aDragDropObserver)
+ aDragDropObserver.onDragExit(aEvent, this.mDragSession);
+ },
+
+ /**
+ * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
+ *
+ * called when a drag enters in this element
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by entering in the element
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ **/
+ dragEnter: function (aEvent, aDragDropObserver)
+ {
+ if (!this.checkCanDrop(aEvent, aDragDropObserver))
+ return;
+ if ("onDragEnter" in aDragDropObserver)
+ aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
+ },
+
+ /**
+ * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
+ *
+ * Sets the canDrop attribute for the drag session.
+ * returns false if there is no current drag session.
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by the drop
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ **/
+ checkCanDrop: function (aEvent, aDragDropObserver)
+ {
+ if (!this.mDragSession)
+ this.mDragSession = this.mDragService.getCurrentSession();
+ if (!this.mDragSession)
+ return false;
+ this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
+ if ("canDrop" in aDragDropObserver)
+ this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
+ return true;
+ },
+
+ /**
+ * Do a security check for drag n' drop. Make sure the source document
+ * can load the dragged link.
+ *
+ * @param DOMEvent aEvent
+ * the DOM event fired by leaving the element
+ * @param Object aDragDropObserver
+ * javascript object of format described above that specifies
+ * the way in which the element responds to drag events.
+ * @param String aDraggedText
+ * the text being dragged
+ **/
+ dragDropSecurityCheck: function (aEvent, aDragSession, aDraggedText)
+ {
+ // Strip leading and trailing whitespace, then try to create a
+ // URI from the dropped string. If that succeeds, we're
+ // dropping a URI and we need to do a security check to make
+ // sure the source document can load the dropped URI. We don't
+ // so much care about creating the real URI here
+ // (i.e. encoding differences etc don't matter), we just want
+ // to know if aDraggedText really is a URI.
+
+ aDraggedText = aDraggedText.replace(/^\s*|\s*$/g, '');
+
+ var uri;
+ try {
+ uri = Services.io.newURI(aDraggedText);
+ } catch (e) {
+ }
+
+ if (!uri)
+ return;
+
+ // aDraggedText is a URI, do the security check.
+ let secMan = Services.scriptSecurityManager;
+
+ if (!aDragSession)
+ aDragSession = this.mDragService.getCurrentSession();
+
+ var sourceDoc = aDragSession.sourceDocument;
+ // Use "file:///" as the default sourceURI so that drops of file:// URIs
+ // are always allowed.
+ var principal = sourceDoc ? sourceDoc.nodePrincipal
+ : secMan.createCodebasePrincipal(Services.io.newURI("file:///"), {});
+
+ try {
+ secMan.checkLoadURIStrWithPrincipal(principal, aDraggedText,
+ Ci.nsIScriptSecurityManager.STANDARD);
+ } catch (e) {
+ // Stop event propagation right here.
+ aEvent.stopPropagation();
+
+ throw "Drop of " + aDraggedText + " denied.";
+ }
+ }
+};
+
diff --git a/comm/suite/mailnews/content/phishingDetector.js b/comm/suite/mailnews/content/phishingDetector.js
new file mode 100644
index 0000000000..04d2910753
--- /dev/null
+++ b/comm/suite/mailnews/content/phishingDetector.js
@@ -0,0 +1,173 @@
+/* -*- 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/. */
+
+// Dependencies:
+// gBrandBundle, gMessengerBundle should already be defined
+// gatherTextUnder from utilityOverlay.js
+
+ChromeUtils.import("resource:///modules/hostnameUtils.jsm");
+
+const kPhishingNotSuspicious = 0;
+const kPhishingWithIPAddress = 1;
+const kPhishingWithMismatchedHosts = 2;
+
+//////////////////////////////////////////////////////////////////////////////
+// isEmailScam --> examines the message currently loaded in the message pane
+// and returns true if we think that message is an e-mail scam.
+// Assumes the message has been completely loaded in the message pane (i.e. OnMsgParsed has fired)
+// aUrl: nsIURI object for the msg we want to examine...
+//////////////////////////////////////////////////////////////////////////////
+function isMsgEmailScam(aUrl)
+{
+ var isEmailScam = false;
+ if (!aUrl || !Services.prefs.getBoolPref("mail.phishing.detection.enabled"))
+ return isEmailScam;
+
+ try {
+ // nsIMsgMailNewsUrl.folder can throw an NS_ERROR_FAILURE, especially if
+ // we are opening an .eml file.
+ var folder = aUrl.folder;
+
+ // Ignore NNTP and RSS messages.
+ if (folder.server.type == 'nntp' || folder.server.type == 'rss')
+ return isEmailScam;
+
+ // Also ignore messages in Sent/Drafts/Templates/Outbox.
+ let outgoingFlags = Ci.nsMsgFolderFlags.SentMail |
+ Ci.nsMsgFolderFlags.Drafts |
+ Ci.nsMsgFolderFlags.Templates |
+ Ci.nsMsgFolderFlags.Queue;
+ if (folder.isSpecialFolder(outgoingFlags, true))
+ return isEmailScam;
+
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_FAILURE)
+ throw ex;
+ }
+
+ // loop through all of the link nodes in the message's DOM, looking for phishing URLs...
+ var msgDocument = document.getElementById('messagepane').contentDocument;
+ var index;
+
+ // examine all links...
+ var linkNodes = msgDocument.links;
+ for (index = 0; index < linkNodes.length && !isEmailScam; index++)
+ isEmailScam = isPhishingURL(linkNodes[index], true);
+
+ // if an e-mail contains a non-addressbook form element, then assume the message is
+ // a phishing attack. Legitimate sites should not be using forms inside of e-mail
+ if (!isEmailScam)
+ {
+ var forms = msgDocument.getElementsByTagName("form");
+ for (index = 0; index < forms.length && !isEmailScam; index++)
+ isEmailScam = forms[index].action != "" && !/^addbook:/.test(forms[index].action);
+ }
+
+ // we'll add more checks here as our detector matures....
+ return isEmailScam;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// isPhishingURL --> examines the passed in linkNode and returns true if we think
+// the URL is an email scam.
+// aLinkNode: the link node to examine
+// aSilentMode: don't prompt the user to confirm
+// aHref: optional href for XLinks
+//////////////////////////////////////////////////////////////////////////////
+
+function isPhishingURL(aLinkNode, aSilentMode, aHref)
+{
+ if (!Services.prefs.getBoolPref("mail.phishing.detection.enabled"))
+ return false;
+
+ var phishingType = kPhishingNotSuspicious;
+ var aLinkText = gatherTextUnder(aLinkNode);
+ var href = aHref || aLinkNode.href;
+ if (!href)
+ return false;
+
+ var linkTextURL = {};
+ var isPhishingURL = false;
+
+ var hrefURL;
+ // Make sure relative link urls don't make us bail out.
+ try {
+ hrefURL = Services.io.newURI(href);
+ } catch(ex) { return false; }
+
+ // only check for phishing urls if the url is an http or https link.
+ // this prevents us from flagging imap and other internally handled urls
+ if (hrefURL.schemeIs('http') || hrefURL.schemeIs('https'))
+ {
+
+ if (aLinkText)
+ aLinkText = aLinkText.replace(/^<(.+)>$|^"(.+)"$/, "$1$2");
+ if (aLinkText != aLinkNode.href &&
+ aLinkText.replace(/\/+$/, "") != aLinkNode.href.replace(/\/+$/, ""))
+ {
+ let ipAddress = isLegalIPAddress(hrefURL.host, true);
+ if (ipAddress && !isLegalLocalIPAddress(ipAddress))
+ phishingType = kPhishingWithIPAddress;
+ else if (misMatchedHostWithLinkText(aLinkNode, hrefURL))
+ phishingType = kPhishingWithMismatchedHosts;
+
+ isPhishingURL = phishingType != kPhishingNotSuspicious;
+
+ if (!aSilentMode && isPhishingURL) // allow the user to override the decision
+ isPhishingURL = confirmSuspiciousURL(phishingType, hrefURL.host);
+ }
+ }
+
+ return isPhishingURL;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// helper methods in support of isPhishingURL
+//////////////////////////////////////////////////////////////////////////////
+
+function misMatchedHostWithLinkText(aLinkNode, aHrefURL)
+{
+ var linkNodeText = gatherTextUnder(aLinkNode);
+
+ // gatherTextUnder puts a space between each piece of text it gathers,
+ // so strip the spaces out (see bug 326082 for details).
+ linkNodeText = linkNodeText.replace(/ /g, "");
+
+ // only worry about http and https urls
+ if (linkNodeText)
+ {
+ // does the link text look like a http url?
+ if (linkNodeText.search(/(^http:|^https:)/) != -1)
+ {
+ var linkURI = Services.io.newURI(linkNodeText);
+ // compare hosts, but ignore possible www. prefix
+ return !(aHrefURL.host.replace(/^www\./, "") == linkURI.host.replace(/^www\./, ""));
+ }
+ }
+
+ return false;
+}
+
+// returns true if the user confirms the URL is a scam
+function confirmSuspiciousURL(aPhishingType, aSuspiciousHostName)
+{
+ var brandShortName = gBrandBundle.getString("brandShortName");
+ var titleMsg = gMessengerBundle.getString("confirmPhishingTitle");
+ var dialogMsg;
+
+ switch (aPhishingType)
+ {
+ case kPhishingWithIPAddress:
+ case kPhishingWithMismatchedHosts:
+ dialogMsg = gMessengerBundle.getFormattedString("confirmPhishingUrl" + aPhishingType, [brandShortName, aSuspiciousHostName], 2);
+ break;
+ default:
+ return false;
+ }
+
+ var buttons = Services.prompt.STD_YES_NO_BUTTONS +
+ Services.prompt.BUTTON_POS_1_DEFAULT;
+ return Services.prompt.confirmEx(window, titleMsg, dialogMsg, buttons, "", "", "", "", {}); /* the yes button is in position 0 */
+} \ No newline at end of file
diff --git a/comm/suite/mailnews/content/searchBar.js b/comm/suite/mailnews/content/searchBar.js
new file mode 100644
index 0000000000..2c23b395a3
--- /dev/null
+++ b/comm/suite/mailnews/content/searchBar.js
@@ -0,0 +1,432 @@
+/* -*- 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/. */
+
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+
+var gSearchSession = null;
+var gPreQuickSearchView = null;
+var gSearchTimer = null;
+var gViewSearchListener;
+var gSearchBundle;
+var gProgressMeter = null;
+var gSearchInProgress = false;
+var gClearButton = null;
+var gDefaultSearchViewTerms = null;
+var gQSViewIsDirty = false;
+var gNumTotalMessages;
+var gNumUnreadMessages;
+
+function SetQSStatusText(aNumHits)
+{
+ var statusMsg;
+ // if there are no hits, it means no matches were found in the search.
+ if (aNumHits == 0)
+ {
+ statusMsg = gSearchBundle.getString("noMatchesFound");
+ }
+ else
+ {
+ statusMsg = PluralForm.get(aNumHits,
+ gSearchBundle.getString("matchesFound"));
+ statusMsg = statusMsg.replace("#1", aNumHits);
+ }
+ statusFeedback.showStatusString(statusMsg);
+}
+
+// nsIMsgSearchNotify object
+var gSearchNotificationListener =
+{
+ onSearchHit: function(header, folder)
+ {
+ gNumTotalMessages++;
+ if (!header.isRead)
+ gNumUnreadMessages++;
+ // XXX todo
+ // update status text?
+ },
+
+ onSearchDone: function(status)
+ {
+ SetQSStatusText(gDBView.QueryInterface(Ci.nsITreeView).rowCount)
+ statusFeedback.showProgress(0);
+ gProgressMeter.setAttribute("mode", "normal");
+ gSearchInProgress = false;
+
+ // ### TODO need to find out if there's quick search within a virtual folder.
+ if (gCurrentVirtualFolderUri &&
+ (!gSearchInput || gSearchInput.value == ""))
+ {
+ var vFolder = MailUtils.getFolderForURI(gCurrentVirtualFolderUri, false);
+ var dbFolderInfo = vFolder.msgDatabase.dBFolderInfo;
+ dbFolderInfo.numUnreadMessages = gNumUnreadMessages;
+ dbFolderInfo.numMessages = gNumTotalMessages;
+ vFolder.updateSummaryTotals(true); // force update from db.
+ var msgdb = vFolder.msgDatabase;
+ msgdb.Commit(Ci.nsMsgDBCommitType.kLargeCommit);
+ // now that we have finished loading a virtual folder,
+ // scroll to the correct message if there is at least one.
+ if (vFolder.getTotalMessages(false) > 0)
+ ScrollToMessageAfterFolderLoad(vFolder);
+ }
+ },
+
+ onNewSearch: function()
+ {
+ statusFeedback.showProgress(0);
+ statusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
+ gProgressMeter.setAttribute("mode", "undetermined");
+ gSearchInProgress = true;
+ gNumTotalMessages = 0;
+ gNumUnreadMessages = 0;
+ }
+}
+
+function getDocumentElements()
+{
+ gSearchBundle = document.getElementById("bundle_search");
+ gProgressMeter = document.getElementById('statusbar-icon');
+ gClearButton = document.getElementById('clearButton');
+ GetSearchInput();
+}
+
+function addListeners()
+{
+ gViewSearchListener = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
+ gSearchSession.registerListener(gViewSearchListener);
+}
+
+function removeListeners()
+{
+ gSearchSession.unregisterListener(gViewSearchListener);
+}
+
+function removeGlobalListeners()
+{
+ removeListeners();
+ gSearchSession.unregisterListener(gSearchNotificationListener);
+}
+
+function initializeGlobalListeners()
+{
+ // Setup the javascript object as a listener on the search results
+ gSearchSession.registerListener(gSearchNotificationListener);
+}
+
+function createQuickSearchView()
+{
+ //if not already in quick search view
+ if (gDBView.viewType != nsMsgViewType.eShowQuickSearchResults)
+ {
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView); //clear selection
+ if (treeView && treeView.selection)
+ treeView.selection.clearSelection();
+ gPreQuickSearchView = gDBView;
+ if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
+ {
+ // remove the view as a listener on the search results
+ var saveViewSearchListener = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
+ gSearchSession.unregisterListener(saveViewSearchListener);
+ }
+ CreateDBView(gDBView.msgFolder, (gXFVirtualFolderTerms) ? nsMsgViewType.eShowVirtualFolderResults : nsMsgViewType.eShowQuickSearchResults, gDBView.viewFlags, gDBView.sortType, gDBView.sortOrder);
+ }
+}
+
+function initializeSearchBar()
+{
+ createQuickSearchView();
+ if (!gSearchSession)
+ {
+ var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
+ gSearchSession = Cc[searchSessionContractID].createInstance(Ci.nsIMsgSearchSession);
+ initializeGlobalListeners();
+ }
+ else
+ {
+ if (gSearchInProgress)
+ {
+ onSearchStop();
+ gSearchInProgress = false;
+ }
+ removeListeners();
+ }
+ addListeners();
+}
+
+function onEnterInSearchBar()
+{
+ if (!gSearchBundle)
+ getDocumentElements();
+ if (gSearchInput.value == "")
+ {
+ let viewType = gDBView && gDBView.viewType;
+ if (viewType == nsMsgViewType.eShowQuickSearchResults ||
+ viewType == nsMsgViewType.eShowVirtualFolderResults)
+ {
+ statusFeedback.showStatusString("");
+ disableQuickSearchClearButton();
+
+ viewDebug ("onEnterInSearchBar gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = "
+ + gVirtualFolderTerms + "gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
+ var addTerms = gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms;
+ if (addTerms)
+ {
+ viewDebug ("addTerms = " + addTerms + " count = " + addTerms.length + "\n");
+ initializeSearchBar();
+ onSearch(addTerms);
+ }
+ else
+ restorePreSearchView();
+ }
+ else if (gPreQuickSearchView && !gDefaultSearchViewTerms)// may be a quick search from a cross-folder virtual folder
+ restorePreSearchView();
+
+ gQSViewIsDirty = false;
+ return;
+ }
+
+ initializeSearchBar();
+
+ if (gClearButton)
+ gClearButton.setAttribute("disabled", false); //coming into search enable clear button
+
+ ClearThreadPaneSelection();
+ ClearMessagePane();
+
+ onSearch(null);
+ gQSViewIsDirty = false;
+}
+
+function restorePreSearchView()
+{
+ var selectedHdr = null;
+ //save selection
+ try
+ {
+ selectedHdr = gDBView.hdrForFirstSelectedMessage;
+ }
+ catch (ex)
+ {}
+
+ //we might have to sort the view coming out of quick search
+ var sortType = gDBView.sortType;
+ var sortOrder = gDBView.sortOrder;
+ var viewFlags = gDBView.viewFlags;
+ var folder = gDBView.msgFolder;
+
+ gDBView.close();
+ gDBView = null;
+
+ if (gPreQuickSearchView)
+ {
+ gDBView = gPreQuickSearchView;
+ if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
+ {
+ // readd the view as a listener on the search results
+ var saveViewSearchListener = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
+ if (gSearchSession)
+ gSearchSession.registerListener(saveViewSearchListener);
+ }
+// dump ("view type = " + gDBView.viewType + "\n");
+
+ if (sortType != gDBView.sortType || sortOrder != gDBView.sortOrder)
+ {
+ gDBView.sort(sortType, sortOrder);
+ }
+ UpdateSortIndicators(sortType, sortOrder);
+
+ gPreQuickSearchView = null;
+ }
+ else //create default view type
+ CreateDBView(folder, nsMsgViewType.eShowAllThreads, viewFlags, sortType, sortOrder);
+
+ RerootThreadPane();
+
+ var scrolled = false;
+
+ // now restore selection
+ if (selectedHdr)
+ {
+ gDBView.selectMsgByKey(selectedHdr.messageKey);
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ var selectedIndex = treeView.selection.currentIndex;
+ if (selectedIndex >= 0)
+ {
+ // scroll
+ EnsureRowInThreadTreeIsVisible(selectedIndex);
+ scrolled = true;
+ }
+ else
+ ClearMessagePane();
+ }
+ if (!scrolled)
+ ScrollToMessageAfterFolderLoad(null);
+}
+
+function onSearch(aSearchTerms)
+{
+ viewDebug("in OnSearch, searchTerms = " + aSearchTerms + "\n");
+ RerootThreadPane();
+
+ if (aSearchTerms)
+ createSearchTermsWithList(aSearchTerms);
+ else
+ createSearchTerms();
+
+ gDBView.searchSession = gSearchSession;
+ try
+ {
+ gSearchSession.search(msgWindow);
+ }
+ catch(ex)
+ {
+ dump("Search Exception\n");
+ }
+}
+
+function createSearchTermsWithList(aTermsArray)
+{
+ var nsMsgSearchScope = Ci.nsMsgSearchScope;
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ var nsMsgSearchOp = Ci.nsMsgSearchOp;
+
+ gSearchSession.searchTerms.clear();
+ gSearchSession.clearScopes();
+
+ var i;
+ var selectedFolder = GetThreadPaneFolder();
+ if (gXFVirtualFolderTerms)
+ {
+ var msgDatabase = selectedFolder.msgDatabase;
+ if (msgDatabase)
+ {
+ var dbFolderInfo = msgDatabase.dBFolderInfo;
+ var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
+ viewDebug("createSearchTermsWithList xf vf scope = " + srchFolderUri + "\n");
+ var srchFolderUriArray = srchFolderUri.split('|');
+ for (i in srchFolderUriArray)
+ {
+ let realFolder = MailUtils.getFolderForURI(srchFolderUriArray[i]);
+ if (!realFolder.isServer)
+ gSearchSession.addScopeTerm(nsMsgSearchScope.offlineMail, realFolder);
+ }
+ }
+ }
+ else
+ {
+ viewDebug ("in createSearchTermsWithList, adding scope term for selected folder\n");
+ gSearchSession.addScopeTerm(nsMsgSearchScope.offlineMail, selectedFolder);
+ }
+
+ // Add each item in aTermsArray to the search session.
+ for (let term of aTermsArray) {
+ gSearchSession.appendTerm(term);
+ }
+}
+
+function createSearchTerms()
+{
+ var nsMsgSearchScope = Ci.nsMsgSearchScope;
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ var nsMsgSearchOp = Ci.nsMsgSearchOp;
+
+ // create an nsIMutableArray to store our search terms
+ var searchTermsArray = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ var selectedFolder = GetThreadPaneFolder();
+
+ // implement | for QS
+ // does this break if the user types "foo|bar" expecting to see subjects with that string?
+ // I claim no, since "foo|bar" will be a hit for "foo" || "bar"
+ // they just might get more false positives
+ var termList = gSearchInput.value.split("|");
+ for (var i = 0; i < termList.length; i ++)
+ {
+ // if the term is empty, skip it
+ if (termList[i] == "")
+ continue;
+
+ // create, fill, and append the subject term
+ var term = gSearchSession.createTerm();
+ var value = term.value;
+ value.str = termList[i];
+ term.value = value;
+ term.attrib = nsMsgSearchAttrib.Subject;
+ term.op = nsMsgSearchOp.Contains;
+ term.booleanAnd = false;
+ searchTermsArray.appendElement(term);
+
+ // create, fill, and append the AllAddresses term
+ term = gSearchSession.createTerm();
+ value = term.value;
+ value.str = termList[i];
+ term.value = value;
+ term.attrib = nsMsgSearchAttrib.AllAddresses;
+ term.op = nsMsgSearchOp.Contains;
+ term.booleanAnd = false;
+ searchTermsArray.appendElement(term);
+ }
+
+ // now append the default view or virtual folder criteria to the quick search
+ // so we don't lose any default view information
+ viewDebug("gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = " + gVirtualFolderTerms +
+ "gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
+ var defaultSearchTerms = (gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms);
+ if (defaultSearchTerms)
+ {
+ for (let searchTerm of defaultSearchTerms)
+ {
+ searchTermsArray.appendElement(searchTerm);
+ }
+ }
+
+ createSearchTermsWithList(searchTermsArray);
+
+ // now that we've added the terms, clear out our input array
+ searchTermsArray.clear();
+}
+
+function onSearchStop()
+{
+ gSearchSession.interruptSearch();
+}
+
+function onClearSearch()
+{
+ // Use the last focused element so that focus can be restored
+ // if it does not exist, try and get the thread tree instead
+ var focusedElement = gLastFocusedElement || GetThreadTree();
+ Search("");
+ focusedElement.focus();
+}
+
+function disableQuickSearchClearButton()
+{
+ if (gClearButton)
+ gClearButton.setAttribute("disabled", true); //going out of search disable clear button
+}
+
+function ClearQSIfNecessary()
+{
+ GetSearchInput();
+
+ if (gSearchInput.value == "")
+ return;
+
+ Search("");
+}
+
+function Search(str)
+{
+ GetSearchInput();
+
+ if (str != gSearchInput.value)
+ {
+ gQSViewIsDirty = true;
+ viewDebug("in Search(), setting gQSViewIsDirty true\n");
+ }
+
+ gSearchInput.value = str; //on input does not get fired for some reason
+ onEnterInSearchBar();
+}
diff --git a/comm/suite/mailnews/content/searchTermOverlay.xul b/comm/suite/mailnews/content/searchTermOverlay.xul
new file mode 100644
index 0000000000..cd3b1df635
--- /dev/null
+++ b/comm/suite/mailnews/content/searchTermOverlay.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/searchTermOverlay.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://messenger/content/searchTerm.js"/>
+ <script src="chrome://messenger/content/dateFormat.js"/>
+
+ <vbox id="searchTermListBox">
+
+ <radiogroup id="booleanAndGroup" orient="horizontal" value="and"
+ oncommand="booleanChanged(event);">
+ <radio value="and" label="&matchAll.label;"
+ accesskey="&matchAll.accesskey;"/>
+ <radio value="or" label="&matchAny.label;"
+ accesskey="&matchAny.accesskey;"/>
+ <radio value="matchAll" id="matchAllItem" label="&matchAllMsgs.label;"
+ accesskey="&matchAllMsgs.accesskey;"/>
+ </radiogroup>
+
+ <hbox flex="1">
+ <hbox id="searchterms"/>
+ <listbox flex="1" id="searchTermList" rows="4" minheight="35%">
+ <listcols>
+ <listcol flex="&searchTermListAttributesFlexValue;"/>
+ <listcol flex="&searchTermListOperatorsFlexValue;"/>
+ <listcol flex="&searchTermListValueFlexValue;"/>
+ <listcol class="filler"/>
+ </listcols>
+
+ <!-- this is what the listitems will look like:
+ <listitem id="searchListItem">
+ <listcell allowevents="true">
+ <searchattribute id="searchAttr1" for="searchOp1,searchValue1" flex="1"/>
+ </listcell>
+ <listcell allowevents="true">
+ <searchoperator id="searchOp1" opfor="searchValue1" flex="1"/>
+ </listcell>
+ <listcell allowevents="true" >
+ <searchvalue id="searchValue1" flex="1"/>
+ </listcell>
+ <listcell>
+ <button label="add"/>
+ <button label="remove"/>
+ </listcell>
+ </listitem>
+ <listitem>
+ <listcell label="the.."/>
+ <listcell label="contains.."/>
+ <listcell label="text here"/>
+ <listcell label="+/-"/>
+ </listitem>
+ -->
+ </listbox>
+
+ </hbox>
+ </vbox>
+
+</overlay>
diff --git a/comm/suite/mailnews/content/start.xhtml b/comm/suite/mailnews/content/start.xhtml
new file mode 100644
index 0000000000..d9aaf6b790
--- /dev/null
+++ b/comm/suite/mailnews/content/start.xhtml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/start.css" type="text/css"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % startDTD SYSTEM "chrome://messenger/locale/start.dtd" >
+%startDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&startpage.title;</title>
+</head>
+
+<body>
+<h1>&headline.label;</h1>
+
+<div id="main">
+<p>&description.label;</p>
+<h2>&features.title;</h2>
+<ul>
+ <li>&feat_multiacc.label;</li>
+ <li>&feat_junk.label;</li>
+ <li>&feat_feeds.label;</li>
+ <li>&feat_filters.label;</li>
+ <li>&feat_htmlmsg.label;</li>
+ <li>&feat_abook.label;</li>
+ <li>&feat_tags.label;</li>
+ <li>&feat_integration.label;</li>
+</ul>
+<h2>&dict.title;</h2>
+<p>&dict_intro.label;</p>
+<p>&dict_info.label2;</p>
+<h2>&info.title;</h2>
+<p>&info_bugs.label2;</p>
+</div>
+
+<script>
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ // get vendor, dictionaries and release notes URLs from prefs
+ let formatter = Services.urlFormatter;
+ var vendorURL = formatter.formatURLPref("app.vendorURL");
+
+ if (vendorURL != "about:blank") {
+ var vendor = document.getElementById("vendorURL");
+ if (vendor)
+ vendor.setAttribute("href", vendorURL);
+ }
+
+ var dictURL = formatter.formatURLPref("spellchecker.dictionaries.download.url");
+ var dictionaries = document.getElementById("dictURL");
+ if (dictionaries)
+ dictionaries.setAttribute("href", dictURL);
+
+ var releaseNotesURL = formatter.formatURLPref("app.releaseNotesURL");
+ var relnotes = document.getElementById("releaseNotesURL");
+ if (relnotes)
+ relnotes.setAttribute("href", releaseNotesURL);
+</script>
+
+</body>
+</html>
diff --git a/comm/suite/mailnews/content/tabmail.js b/comm/suite/mailnews/content/tabmail.js
new file mode 100644
index 0000000000..694941ce92
--- /dev/null
+++ b/comm/suite/mailnews/content/tabmail.js
@@ -0,0 +1,969 @@
+/* -*- 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/. */
+
+// Traditionally, mailnews tabs come in two flavours: "folder" and
+// "message" tabs. But these modes are just mere default settings on tab
+// creation, defining layout, URI to load, etc.
+// The user can turn a "message" tab into a "folder" tab just by unhiding
+// the folder pane (F9) and hiding the message pane (F8), and vice versa.
+// Tab title and icon will change accordingly.
+// Both flavours are just instances of the basic "3pane" mode, triggered by
+// a bitwise or combination of these possible pane values:
+const kTabShowNoPane = 0;
+const kTabShowFolderPane = 1 << 0;
+const kTabShowMessagePane = 1 << 1;
+const kTabShowThreadPane = 1 << 2;
+const kTabShowAcctCentral = 1 << 3;
+// predefined mode masks
+const kTabMaskDisplayDeck = kTabShowThreadPane | kTabShowAcctCentral;
+// predefined traditional flavours
+const kTabModeFolder = kTabShowFolderPane | kTabShowThreadPane | kTabShowMessagePane;
+const kTabModeMessage = kTabShowMessagePane; // message tab
+
+
+// global mailnews tab definition object
+var gMailNewsTabsType =
+{
+ name: "mailnews",
+ panelId: "mailContent",
+
+ modes:
+ {
+ "3pane":
+ {
+ isDefault: true,
+ type: "3pane",
+
+ // aTabInfo belongs to the newly created tab,
+ // aArgs can contain:
+ // * modeBits is a combination of kTabShow* layout bits (or null),
+ // * folderURI designates the folder to select (or null)
+ // * msgHdr designates the message to select (or null)
+ openTab: function(aTabInfo, {modeBits: aModeBits, folderURI: aFolderURI,
+ msgHdr: aMsgHdr}) {
+ // clone the current 3pane state before overriding parts of it
+ this.saveTabState(aTabInfo);
+
+ // aModeBits must have at least one bit set
+ // if not, we just copy the current state
+ let cloneMode = !aModeBits;
+ if (cloneMode)
+ aModeBits = this.getCurrentModeBits() || kTabModeFolder;
+ aTabInfo.modeBits = aModeBits;
+ // Currently, we only check for kTabModeMessage vs. kTabModeFolder,
+ // but in theory we could distinguish in much more detail!
+ let messageId = null;
+ if (aModeBits == kTabModeMessage || cloneMode)
+ {
+ if (!aMsgHdr && gDBView)
+ {
+ try
+ {
+ // duplicate current message tab if nothing else is specified
+ aMsgHdr = gDBView.hdrForFirstSelectedMessage;
+ // Use the header's folder - this will open a msg in a virtual folder view
+ // in its real folder, which is needed if the msg wouldn't be in a new
+ // view with the same terms - e.g., it's read and the view is unread only.
+ // If we cloned the view, we wouldn't have to do this.
+ if (aTabInfo.switchToNewTab)
+ {
+ // Fix it so we won't try to load the previously loaded message.
+ aMsgHdr.folder.lastMessageLoaded = nsMsgKey_None;
+ }
+ aFolderURI = aMsgHdr.folder.URI;
+ }
+ catch (ex) {}
+ }
+ if (aMsgHdr)
+ messageId = aMsgHdr.messageId;
+ aTabInfo.clearSplitter = true;
+ }
+
+ if (!messageId)
+ {
+ // only sanitize the URL, if possible
+ let clearSplitter = aModeBits == kTabModeFolder;
+ if (!aFolderURI)
+ {
+ // Use GetSelectedMsgFolders() to find out which folder to open
+ // instead of GetLoadedMsgFolder().URI. 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.
+ let msgFolder = GetSelectedMsgFolders()[0];
+ aFolderURI = msgFolder.URI;
+ // don't kill the splitter settings for account central
+ clearSplitter &= !msgFolder.isServer;
+ }
+ aMsgHdr = null;
+ aTabInfo.clearSplitter = clearSplitter;
+ }
+ aTabInfo.uriToOpen = aFolderURI;
+ aTabInfo.hdr = aMsgHdr;
+ aTabInfo.selectedMsgId = messageId;
+
+ // call superclass logic
+ this.openTab(aTabInfo);
+ },
+
+ // We can close all mailnews tabs - but one.
+ // Closing the last mailnews tab would destroy our mailnews functionality.
+ canCloseTab: function(aTabInfo)
+ {
+ return aTabInfo.mode.tabs.length > 1;
+ }
+ }
+ },
+
+ // combines the current pane visibility states into a mode bit mask
+ getCurrentModeBits: function()
+ {
+ let modeBits = kTabShowNoPane;
+ if (!IsFolderPaneCollapsed())
+ modeBits |= kTabShowFolderPane;
+ if (!IsDisplayDeckCollapsed())
+ {
+ // currently, the display deck has only two panes
+ if (gAccountCentralLoaded)
+ modeBits |= kTabShowAcctCentral;
+ else
+ modeBits |= kTabShowThreadPane;
+ }
+ if (!IsMessagePaneCollapsed())
+ modeBits |= kTabShowMessagePane;
+ return modeBits;
+ },
+
+ _updatePaneLayout: function(aTabInfo)
+ {
+ // first show all needed panes, then hide all unwanted ones
+ // (we have to keep this order to avoid hiding all panes!)
+ let showFolderPane = aTabInfo.modeBits & kTabShowFolderPane;
+ let showMessagePane = aTabInfo.modeBits & kTabShowMessagePane;
+ let showDisplayDeck = aTabInfo.modeBits & (kTabShowThreadPane | kTabShowAcctCentral);
+ if (showMessagePane && IsMessagePaneCollapsed())
+ MsgToggleMessagePane(true); // show message pane
+ if (showDisplayDeck && IsDisplayDeckCollapsed())
+ MsgToggleThreadPane(); // show thread pane
+ if (showFolderPane && IsFolderPaneCollapsed())
+ MsgToggleFolderPane(true); // show folder pane
+ if (!showMessagePane && !IsMessagePaneCollapsed())
+ MsgToggleMessagePane(true); // hide message pane
+ if (!showDisplayDeck && !IsDisplayDeckCollapsed())
+ MsgToggleThreadPane(); // hide thread pane
+ if (!showFolderPane && !IsFolderPaneCollapsed())
+ MsgToggleFolderPane(true); // hide folder pane
+ UpdateLayoutVisibility();
+ },
+
+ /**
+ * Create the new tab's state, which engenders some side effects.
+ * Part of our contract is that we leave the tab in the selected state.
+ */
+ openTab: function(aTabInfo)
+ {
+ // each tab gets its own messenger instance
+ // for undo/redo, backwards/forwards, etc.
+ messenger = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger);
+ messenger.setWindow(window, msgWindow);
+ aTabInfo.messenger = messenger;
+
+ // remember the currently selected folder
+ aTabInfo.msgSelectedFolder = gMsgFolderSelected;
+
+ // show tab if permitted
+ if (aTabInfo.switchToNewTab)
+ this.showTab(aTabInfo);
+ },
+
+ showTab: function(aTabInfo)
+ {
+ // don't allow saveTabState while restoring a tab
+ aTabInfo.lock = true;
+ // set the messagepane as the primary browser for content
+ var messageBrowser = getMessageBrowser();
+ messageBrowser.setAttribute("type", "content");
+ messageBrowser.setAttribute("primary", "true");
+
+ if (aTabInfo.uriToOpen)
+ {
+ // HACK: Since we've switched away from the tab, we need to bring
+ // back the real selection before selecting the folder, so do that
+ RestoreSelectionWithoutContentLoad(document.getElementById("folderTree"));
+
+ // Clear selection, because context clicking on a folder and opening in a
+ // new tab needs to have selectFolder think the selection has changed.
+ gFolderTreeView.selection.clearSelection();
+ gFolderTreeView.selection.currentIndex = -1;
+ gMsgFolderSelected = null;
+ msgWindow.openFolder = null;
+
+ // clear gDBView so we won't try to close it
+ gDBView = null;
+
+ // reroot the message sink (we might have switched layout)
+ messenger.setWindow(null, null);
+ messenger.setWindow(window, msgWindow);
+
+ // Clear thread pane selection - otherwise, the tree tries to impose the
+ // the current selection on the new view.
+ let msgHdr = aTabInfo.hdr;
+ let msgId = aTabInfo.selectedMsgId;
+ aTabInfo.hdr = null;
+ aTabInfo.selectedMsgId = null;
+ aTabInfo.dbView = null;
+ let folder = MailUtils.getFolderForURI(aTabInfo.uriToOpen);
+ gFolderTreeView.selectFolder(folder);
+ gCurrentFolderToReroot = null;
+ delete aTabInfo.uriToOpen; // destroy after use!
+ // Store the folder that is being opened.
+ aTabInfo.msgSelectedFolder = folder;
+
+ // restore our message data
+ aTabInfo.hdr = msgHdr;
+ aTabInfo.selectedMsgId = msgId;
+
+ aTabInfo.dbView = gDBView;
+ UpdateMailToolbar("new tab");
+ }
+
+ // Do not bother with Thread and Message panes if at server level.
+ if (!aTabInfo.msgSelectedFolder.isServer) {
+ // Restore the layout if present.
+ ShowThreadPane();
+ // Some modes (e.g. new message tabs) need to initially hide the
+ // splitters, this is marked by aTabInfo.clearSplitter=true.
+ let clearSplitter = "clearSplitter" in aTabInfo && aTabInfo.clearSplitter;
+ if (clearSplitter) {
+ aTabInfo.messageSplitter.collapsible = true;
+ aTabInfo.folderSplitter.collapsible = true;
+ delete aTabInfo.clearSplitter;
+ }
+ SetSplitterState(GetThreadAndMessagePaneSplitter(),
+ aTabInfo.messageSplitter);
+ SetSplitterState(GetFolderPaneSplitter(),
+ aTabInfo.folderSplitter);
+ this._updatePaneLayout(aTabInfo);
+ ClearMessagePane();
+ // Force the header pane twisty state restoration by toggling from the
+ // opposite.
+ if (gCollapsedHeaderViewMode != aTabInfo.headerViewMode)
+ ToggleHeaderView();
+ }
+
+ // restore globals
+ messenger = aTabInfo.messenger;
+ gDBView = aTabInfo.dbView;
+ gSearchSession = aTabInfo.searchSession;
+ let folderToSelect = aTabInfo.msgSelectedFolder || gDBView && gDBView.msgFolder;
+
+ // restore view state if we had one
+ let row = gFolderTreeView.getIndexOfFolder(folderToSelect);
+ let treeBoxObj = document.getElementById("folderTree").treeBoxObject;
+ let folderTreeSelection = gFolderTreeView.selection;
+
+ // make sure that row.value is valid so that it doesn't mess up
+ // the call to ensureRowIsVisible()
+ if ((row >= 0) && !folderTreeSelection.isSelected(row))
+ {
+ gMsgFolderSelected = folderToSelect;
+ msgWindow.openFolder = folderToSelect;
+ folderTreeSelection.select(row);
+ treeBoxObj.ensureRowIsVisible(row);
+ }
+
+ if (gDBView)
+ {
+ // This sets the thread pane tree's view to the gDBView view.
+ UpdateSortIndicators(gDBView.sortType, gDBView.sortOrder);
+ RerootThreadPane();
+
+ // We don't want to reapply the mailview (threadpane changes by switching
+ // tabs alone would be rather surprising), just update the viewpicker
+ // and resave the new view.
+ UpdateViewPickerByValue(aTabInfo.mailView);
+ SetMailViewForFolder(folderToSelect, aTabInfo.mailView);
+
+ // restore quick search
+ GetSearchInput().value = aTabInfo.searchInput;
+
+ // We need to restore the selection to what it was when we switched away
+ // from this tab. We need to remember the selected keys, instead of the
+ // selected indices, since the view might have changed. But maybe the
+ // selectedIndices adjust as items are added/removed from the (hidden)
+ // view.
+ try
+ {
+ if (aTabInfo.selectedMsgId && aTabInfo.msgSelectedFolder)
+ {
+ // We clear the selection in order to generate an event when we
+ // re-select our message. This destroys aTabInfo.selectedMsgId.
+ let selectedMsgId = aTabInfo.selectedMsgId;
+ ClearThreadPaneSelection();
+ aTabInfo.selectedMsgId = selectedMsgId;
+ let msgDB = aTabInfo.msgSelectedFolder.msgDatabase;
+ let msgHdr = msgDB.getMsgHdrForMessageID(aTabInfo.selectedMsgId);
+ setTimeout(gDBView.selectFolderMsgByKey,
+ 0,
+ aTabInfo.msgSelectedFolder,
+ msgHdr.messageKey);
+ }
+ // We do not clear the selection if there was more than one message
+ // displayed. this leaves our selection intact. there was originally
+ // some claim that the selection might lose synchronization with the
+ // view, but this is unsubstantiated. said comment came from the
+ // original code that stored information on the selected rows, but
+ // then failed to do anything with it, probably because there is no
+ // existing API call that accomplishes it.
+ }
+ catch (ex)
+ {
+ dump(ex);
+ }
+ GetThreadTree().treeBoxObject.scrollToRow(aTabInfo.firstVisibleRow);
+ }
+ else if (gMsgFolderSelected.isServer)
+ {
+ // Load AccountCentral page here.
+ ShowAccountCentral(gMsgFolderSelected);
+ }
+ SetUpToolbarButtons(gMsgFolderSelected.URI);
+ UpdateMailToolbar("tab changed");
+ delete aTabInfo.lock;
+ },
+
+ closeTab: function(aTabInfo)
+ {
+ // If the tab has never been opened, we must not clean up the view,
+ // because it still belongs to a different tab.
+ if (aTabInfo.uriToOpen)
+ return;
+
+ if (aTabInfo.dbView)
+ aTabInfo.dbView.close();
+ if (aTabInfo.messenger)
+ aTabInfo.messenger.setWindow(null, null);
+ },
+
+ // called when switching away from aTabInfo
+ saveTabState: function(aTabInfo)
+ {
+ if (aTabInfo.lock)
+ return;
+
+ // save message db data and view filters
+ aTabInfo.messenger = messenger;
+ aTabInfo.dbView = gDBView;
+ aTabInfo.searchSession = gSearchSession;
+ aTabInfo.msgSelectedFolder = gMsgFolderSelected;
+ aTabInfo.selectedMsgId = null;
+ if (gDBView)
+ {
+ // save thread pane scroll position
+ aTabInfo.firstVisibleRow = GetThreadTree().treeBoxObject.getFirstVisibleRow();
+
+ let curMsgViewIndex = gDBView.currentlyDisplayedMessage;
+ if (curMsgViewIndex != nsMsgViewIndex_None)
+ {
+ try // there may not be a selected message.
+ {
+ // the currentlyDisplayedMessage is not always the first selected
+ // message, e.g. on a right click for the context menu
+ let curMsgHdr = gDBView.getMsgHdrAt(curMsgViewIndex);
+ aTabInfo.selectedMsgId = curMsgHdr.messageId;
+ }
+ catch (ex) {}
+ }
+ if (!aTabInfo.selectedMsgId)
+ aTabInfo.msgSelectedFolder = gDBView.msgFolder;
+ }
+ aTabInfo.mailView = GetMailViewForFolder(aTabInfo.msgSelectedFolder);
+
+ // remember layout
+ aTabInfo.modeBits = this.getCurrentModeBits();
+ aTabInfo.messageSplitter = GetSplitterState(GetThreadAndMessagePaneSplitter());
+ aTabInfo.folderSplitter = GetSplitterState(GetFolderPaneSplitter());
+
+ // header pane twisty state
+ aTabInfo.headerViewMode = gCollapsedHeaderViewMode;
+
+ // quick search
+ aTabInfo.searchInput = GetSearchInput().value;
+ },
+
+ onTitleChanged: function(aTabInfo, aTabNode)
+ {
+ // If we have an account, we also always have a "Local Folders" account,
+ let multipleRealAccounts = MailServices.accounts.accounts.length > 2;
+
+ // clear out specific tab data now, because we might need to return early
+ aTabNode.removeAttribute("SpecialFolder");
+ aTabNode.removeAttribute("ServerType");
+ aTabNode.removeAttribute("IsServer");
+ aTabNode.removeAttribute("IsSecure");
+ aTabNode.removeAttribute("NewMessages");
+ aTabNode.removeAttribute("ImapShared");
+ aTabNode.removeAttribute("BiffState");
+ aTabNode.removeAttribute("MessageType");
+ aTabNode.removeAttribute("Offline");
+ aTabNode.removeAttribute("Attachment");
+ aTabNode.removeAttribute("IMAPDeleted");
+
+ // aTabInfo.msgSelectedFolder may contain the base folder of saved search
+ let folder = null;
+ if (aTabInfo.uriToOpen)
+ {
+ // select folder for the backgound tab without changing the current one
+ // (stolen from SelectFolder)
+ folder = MailUtils.getFolderForURI(aTabInfo.uriToOpen);
+ }
+ else
+ {
+ folder = (aTabInfo.dbView && aTabInfo.dbView.viewFolder) ||
+ (aTabInfo.dbView && aTabInfo.dbView.msgFolder) ||
+ aTabInfo.msgSelectedFolder || gMsgFolderSelected;
+ }
+
+ // update the message header only if we're the current tab
+ if (aTabNode.selected)
+ {
+ try
+ {
+ aTabInfo.hdr = aTabInfo.dbView && aTabInfo.dbView.hdrForFirstSelectedMessage;
+ }
+ catch (e)
+ {
+ aTabInfo.hdr = null;
+ }
+ }
+
+ // update tab title and icon state
+ aTabInfo.title = "";
+ if (IsMessagePaneCollapsed() || !aTabInfo.hdr)
+ {
+ // Folder Tab
+ aTabNode.setAttribute("type", "folder"); // override "3pane"
+ if (!folder)
+ {
+ // nothing to do
+ return;
+ }
+ else
+ {
+ aTabInfo.title = folder.prettyName;
+ if (!folder.isServer && multipleRealAccounts)
+ aTabInfo.title += " - " + folder.server.prettyName;
+ }
+
+ // The user may have changed folders, triggering our onTitleChanged callback.
+ // Update the appropriate attributes on the tab.
+ aTabNode.setAttribute("SpecialFolder", FolderUtils.getSpecialFolderString(folder));
+ aTabNode.setAttribute("ServerType", folder.server.type);
+ aTabNode.setAttribute("IsServer", folder.isServer);
+ aTabNode.setAttribute("IsSecure", folder.server.isSecure);
+ aTabNode.setAttribute("NewMessages", folder.hasNewMessages);
+ aTabNode.setAttribute("ImapShared", folder.imapShared);
+
+ let biffState = "UnknownMail";
+ switch (folder.biffState)
+ {
+ case Ci.nsIMsgFolder.nsMsgBiffState_NewMail:
+ biffState = "NewMail";
+ break;
+ case Ci.nsIMsgFolder.nsMsgBiffState_NoMail:
+ biffState = "NoMail";
+ break;
+ }
+ aTabNode.setAttribute("BiffState", biffState);
+ }
+ else
+ {
+ // Message Tab
+ aTabNode.setAttribute("type", "message"); // override "3pane"
+ if (aTabInfo.hdr.flags & Ci.nsMsgMessageFlags.HasRe)
+ aTabInfo.title = "Re: ";
+ if (aTabInfo.hdr.mime2DecodedSubject)
+ aTabInfo.title += aTabInfo.hdr.mime2DecodedSubject;
+ aTabInfo.title += " - " + aTabInfo.hdr.folder.prettyName;
+ if (multipleRealAccounts)
+ aTabInfo.title += " - " + aTabInfo.hdr.folder.server.prettyName;
+
+ // message specific tab data
+ let flags = aTabInfo.hdr.flags;
+ aTabNode.setAttribute("MessageType", folder.server.type);
+ aTabNode.setAttribute("Offline",
+ Boolean(flags & Ci.nsMsgMessageFlags.Offline));
+ aTabNode.setAttribute("Attachment",
+ Boolean(flags & Ci.nsMsgMessageFlags.Attachment));
+ aTabNode.setAttribute("IMAPDeleted",
+ Boolean(flags & Ci.nsMsgMessageFlags.IMAPDeleted));
+ }
+ },
+
+ getBrowser: function(aTabInfo)
+ {
+ // we currently use the messagepane element for all 3pane tab types
+ return getMessageBrowser();
+ },
+
+ //
+ // nsIController implementation
+ //
+ // We ignore the aTabInfo parameter sent by tabmail when calling nsIController
+ // stuff and just delegate the call to the DefaultController by using it as
+ // our proto chain.
+ // XXX remove the MessageWindowController stuff once we kill messageWindow.xul
+ __proto__: "DefaultController" in window && window.DefaultController ||
+ "MessageWindowController" in window && window.MessageWindowController
+};
+
+
+
+//
+// tabmail support methods
+//
+
+function GetTabMail()
+{
+ return document.getElementById("tabmail");
+}
+
+function MsgOpenNewTab(aType, aModeBits, aBackground) {
+ // duplicate the current tab
+ var tabmail = GetTabMail();
+ if (tabmail)
+ tabmail.openTab(aType, {modeBits: aModeBits, background: aBackground});
+}
+
+function MsgOpenNewTabForFolder(aBackground) {
+ // open current folder in full 3pane tab
+ MsgOpenNewTab("3pane", kTabModeFolder, aBackground);
+}
+
+function MsgOpenNewTabForMessage(aBackground) {
+ // open current message in message tab
+ MsgOpenNewTab("3pane", kTabModeMessage, aBackground);
+}
+
+// A Thunderbird compatibility function called from e.g. newsblog.
+// We ignore aHandlerRegExp as it is not needed by SeaMonkey.
+function openContentTab(aUrl, aWhere, aHandlerRegExp)
+{
+ openUILinkIn(aUrl, aWhere);
+}
+
+function AllowOpenTabOnMiddleClick()
+{
+ return Services.prefs.getBoolPref("mail.tabs.opentabfor.middleclick");
+}
+
+function AllowOpenTabOnDoubleClick()
+{
+ return Services.prefs.getBoolPref("mail.tabs.opentabfor.doubleclick");
+}
+
+//
+// pane management
+// (maybe we should cache these items in a global object?)
+//
+
+function GetFolderPane()
+{
+ return document.getElementById("folderPaneBox");
+}
+
+function GetThreadPane()
+{
+ return document.getElementById("threadPaneBox");
+}
+
+function GetDisplayDeck()
+{
+ return document.getElementById("displayDeck");
+}
+
+function GetMessagePane()
+{
+ return document.getElementById("messagepanebox");
+}
+
+function GetHeaderPane()
+{
+ return document.getElementById("msgHeaderView");
+}
+
+function GetFolderPaneSplitter()
+{
+ return document.getElementById("folderpane-splitter");
+}
+
+function GetThreadAndMessagePaneSplitter()
+{
+ return document.getElementById("threadpane-splitter");
+}
+
+
+
+//
+// pane visibility management
+//
+// - collapsing the folderpane by clicking its splitter doesn't need
+// additional processing
+// - collapsing the messagepane by clicking its splitter needs some special
+// treatment of attachments, gDBView, etc.
+// - the threadpane has no splitter assigned to it
+// - collapsing the messagepane, threadpane or folderpane by <key> needs to
+// pay attention to the other panes' (and splitters') visibility
+
+function IsMessagePaneCollapsed()
+{
+ return GetMessagePane().collapsed;
+}
+
+function IsDisplayDeckCollapsed()
+{
+ // regard display deck as collapsed in the standalone message window
+ var displayDeck = GetDisplayDeck();
+ return !displayDeck || displayDeck.collapsed;
+}
+
+function IsFolderPaneCollapsed()
+{
+ // regard folderpane as collapsed in the standalone message window
+ var folderPane = GetFolderPane();
+ return !folderPane || folderPane.collapsed;
+}
+
+// Which state is the splitter in? Is it collapsed?
+// How wide/high is the associated pane?
+function GetSplitterState(aSplitter)
+{
+ var next = aSplitter.getAttribute("collapse") == "after";
+ var pane = next ? aSplitter.nextSibling : aSplitter.previousSibling;
+ var vertical = aSplitter.orient == "vertical";
+ var rv =
+ {
+ state: aSplitter.getAttribute("state"),
+ collapsed: aSplitter.collapsed,
+ // <splitter>s are <hbox>es,
+ // thus the "orient" attribute is usually either unset or "vertical"
+ size: vertical ? pane.height : pane.width,
+ collapsible: "collapsible" in aSplitter && aSplitter.collapsible
+ };
+ return rv;
+}
+
+function SetSplitterState(aSplitter, aState)
+{
+ // all settings in aState are optional
+ if (!aState)
+ return;
+ if ("state" in aState)
+ aSplitter.setAttribute("state", aState.state);
+ if ("collapsed" in aState)
+ aSplitter.collapsed = aState.collapsed;
+ if ("size" in aState)
+ {
+ let next = aSplitter.getAttribute("collapse") == "after";
+ let pane = next ? aSplitter.nextSibling : aSplitter.previousSibling;
+ let vertical = aSplitter.orient == "vertical";
+ if (vertical)
+ {
+ // vertical splitter orientation
+ pane.height = aState.size;
+ }
+ else
+ {
+ // horizontal splitter orientation
+ pane.width = aState.size;
+ }
+ }
+ if ("collapsible" in aState)
+ aSplitter.collapsible = aState.collapsible;
+}
+
+// If we hit one of the pane splitter <key>s or choose the respective menuitem,
+// we show/hide both the pane *and* the splitter, just like we do for the
+// browser sidebar. Clicking a splitter's grippy, though, will hide the pane
+// but not the splitter.
+function MsgToggleSplitter(aSplitter)
+{
+ var state = aSplitter.getAttribute("state");
+ if (state == "collapsed")
+ {
+ // removing the attribute would hurt persistency
+ aSplitter.setAttribute("state", "open");
+ aSplitter.collapsed = false; // always show splitter when open
+ }
+ else
+ {
+ aSplitter.setAttribute("state", "collapsed");
+ aSplitter.collapsed = true; // hide splitter
+ }
+}
+
+function MsgCollapseSplitter(aSplitter, aCollapse)
+{
+ if (!("collapsible" in aSplitter))
+ aSplitter.collapsible = true;
+ aSplitter.collapsed = aCollapse && aSplitter.collapsible;
+}
+
+// helper function for UpdateLayoutVisibility
+function UpdateFolderPaneFlex(aTuneLayout)
+{
+ var folderBox = GetFolderPane();
+ var messagesBox = document.getElementById("messagesBox");
+ if (aTuneLayout)
+ {
+ // tune folderpane layout
+ folderBox.setAttribute("flex", "1");
+ messagesBox.removeAttribute("flex");
+ }
+ else
+ {
+ // restore old layout
+ folderBox.removeAttribute("flex");
+ messagesBox.setAttribute("flex", "1");
+ }
+}
+
+// we need to finetune the pane and splitter layout in certain circumstances
+function UpdateLayoutVisibility()
+{
+ var modeBits = gMailNewsTabsType.getCurrentModeBits();
+ var folderPaneVisible = modeBits & kTabShowFolderPane;
+ var messagePaneVisible = modeBits & kTabShowMessagePane;
+ var threadPaneVisible = modeBits & kTabShowThreadPane;
+ var displayDeckVisible = modeBits & kTabMaskDisplayDeck;
+ var onlyFolderPane = modeBits == kTabShowFolderPane;
+ var onlyMessagePane = modeBits == kTabShowMessagePane;
+ var onlyDisplayDeck = modeBits == kTabShowThreadPane ||
+ modeBits == kTabShowAcctCentral;
+ var onlyOnePane = onlyFolderPane || onlyMessagePane || onlyDisplayDeck;
+ var showFolderSplitter = false;
+ var showMessageSplitter = false;
+ switch (Services.prefs.getIntPref("mail.pane_config.dynamic"))
+ {
+ case kClassicMailLayout:
+ // if only the folderpane is visible it has to flex,
+ // while the messagesbox must not
+ UpdateFolderPaneFlex(onlyFolderPane);
+ if (!onlyOnePane)
+ {
+ showFolderSplitter = folderPaneVisible;
+ showMessageSplitter = threadPaneVisible && messagePaneVisible;
+ }
+ break;
+
+ case kWideMailLayout:
+ // if only the messagepane is visible, collapse the rest
+ let messengerBox = document.getElementById("messengerBox");
+ messengerBox.collapsed = onlyMessagePane;
+ // a hidden displaydeck must not flex, while the folderpane has to
+ if (!onlyMessagePane)
+ UpdateFolderPaneFlex(!displayDeckVisible);
+ if (!onlyOnePane)
+ {
+ showFolderSplitter = folderPaneVisible && displayDeckVisible;
+ showMessageSplitter = messagePaneVisible;
+ }
+ break;
+
+ case kVerticalMailLayout:
+ // if the threadpane is hidden, we need to hide its outer box as well
+ let messagesBox = document.getElementById("messagesBox");
+ messagesBox.collapsed = !displayDeckVisible;
+ // if only the folderpane is visible, it needs to flex
+ UpdateFolderPaneFlex(onlyFolderPane);
+ if (!onlyOnePane)
+ {
+ showFolderSplitter = folderPaneVisible;
+ showMessageSplitter = messagePaneVisible;
+ }
+ break;
+ }
+
+ // set splitter visibility
+ // if the pane was hidden by clicking the splitter grippy,
+ // the splitter must not hide
+ MsgCollapseSplitter(GetFolderPaneSplitter(), !showFolderSplitter);
+ MsgCollapseSplitter(GetThreadAndMessagePaneSplitter(), !showMessageSplitter);
+
+ // disable location bar if only message pane is visible
+ document.getElementById("locationFolders").disabled = onlyMessagePane;
+ // disable mailviews and search if threadpane is invisible
+ if (!threadPaneVisible)
+ gDisableViewsSearch.setAttribute("disabled", true);
+ else
+ gDisableViewsSearch.removeAttribute("disabled");
+}
+
+function ChangeMessagePaneVisibility()
+{
+ var hidden = IsMessagePaneCollapsed();
+ // We also have to disable the Message/Attachments menuitem.
+ // It will be enabled when loading a message with attachments
+ // (see messageHeaderSink.handleAttachment).
+ if (hidden)
+ {
+ let node = document.getElementById("msgAttachmentMenu");
+ if (node)
+ node.setAttribute("disabled", "true");
+ }
+
+ if (gDBView)
+ {
+ // clear the subject, collapsing won't automatically do this
+ setTitleFromFolder(GetThreadPaneFolder(), null);
+ // the collapsed state is the state after we released the mouse
+ // so we take it as it is
+ gDBView.suppressMsgDisplay = hidden;
+ // set the subject, uncollapsing won't automatically do this
+ gDBView.loadMessageByUrl("about:blank");
+ gDBView.selectionChanged();
+ }
+
+ var event = new Event( "messagepane-"+ (hidden ? "hide" : "unhide"),
+ { bubbles: false, cancelable: true });
+ document.getElementById("messengerWindow").dispatchEvent(event);
+}
+
+function MsgToggleMessagePane(aToggleManually)
+{
+ // don't hide all three panes at once
+ if (IsDisplayDeckCollapsed() && IsFolderPaneCollapsed())
+ return;
+ // toggle the splitter manually if it wasn't clicked and remember that
+ var splitter = GetThreadAndMessagePaneSplitter();
+ if (aToggleManually)
+ MsgToggleSplitter(splitter);
+ splitter.collapsible = aToggleManually;
+ ChangeMessagePaneVisibility();
+ UpdateLayoutVisibility();
+}
+
+function MsgToggleFolderPane(aToggleManually)
+{
+ // don't hide all three panes at once
+ if (IsDisplayDeckCollapsed() && IsMessagePaneCollapsed())
+ return;
+ // toggle the splitter manually if it wasn't clicked and remember that
+ var splitter = GetFolderPaneSplitter();
+ if (aToggleManually)
+ MsgToggleSplitter(splitter);
+ splitter.collapsible = aToggleManually;
+ UpdateLayoutVisibility();
+}
+
+function MsgToggleThreadPane()
+{
+ // don't hide all three panes at once
+ if (IsFolderPaneCollapsed() && IsMessagePaneCollapsed())
+ return;
+ var threadPane = GetDisplayDeck();
+ threadPane.collapsed = !threadPane.collapsed;
+ // we only get here by hitting a key, so always hide border splitters
+ UpdateLayoutVisibility();
+}
+
+// When the ThreadPane is hidden via the displayDeck, we should collapse the
+// elements that are only meaningful to the thread pane. When AccountCentral is
+// shown via the displayDeck, we need to switch the displayDeck to show the
+// accountCentralBox and load the iframe in the AccountCentral box with the
+// corresponding page.
+function ShowAccountCentral(displayedFolder)
+{
+ GetDisplayDeck().selectedPanel = accountCentralBox;
+ let acctCentralPage = GetLocalizedStringPref("mailnews.account_central_page.url");
+ if (acctCentralPage) {
+ let loadURL =
+ acctCentralPage +
+ (displayedFolder ? "?folderURI=" + displayedFolder : "");
+ if (window.frames["accountCentralPane"].location.href != loadURL) {
+ window.frames["accountCentralPane"].location.href = loadURL;
+ }
+ } else {
+ dump("Error loading AccountCentral page\n");
+ }
+}
+
+function ShowThreadPane()
+{
+ GetDisplayDeck().selectedPanel = GetThreadPane();
+}
+
+function ShowingThreadPane()
+{
+ gDisableViewsSearch.removeAttribute("disabled");
+ var threadPaneSplitter = GetThreadAndMessagePaneSplitter();
+ threadPaneSplitter.collapsed = false;
+ if (!threadPaneSplitter.hidden && threadPaneSplitter.getAttribute("state") != "collapsed")
+ {
+ GetMessagePane().collapsed = false;
+ // XXX We need to force the tree to refresh its new height
+ // so that it will correctly scroll to the newest message
+ GetThreadTree().boxObject.height;
+ }
+ document.getElementById("key_toggleThreadPane").removeAttribute("disabled");
+ document.getElementById("key_toggleMessagePane").removeAttribute("disabled");
+}
+
+function HidingThreadPane()
+{
+ ClearThreadPane();
+ GetUnreadCountElement().hidden = true;
+ GetTotalCountElement().hidden = true;
+ GetMessagePane().collapsed = true;
+ GetThreadAndMessagePaneSplitter().collapsed = true;
+ gDisableViewsSearch.setAttribute("disabled", true);
+ document.getElementById("key_toggleThreadPane").setAttribute("disabled", "true");
+ document.getElementById("key_toggleMessagePane").setAttribute("disabled", "true");
+}
+
+var gCurrentDisplayDeckId = "";
+function ObserveDisplayDeckChange(aEvent)
+{
+ var selectedPanel = GetDisplayDeck().selectedPanel;
+ var nowSelected = selectedPanel ? selectedPanel.id : "";
+ // onselect fires for every mouse click inside the deck, so ObserveDisplayDeckChange
+ // is getting called every time we click on a message in the thread pane.
+ // Only show/hide elements if the selected deck is actually changing.
+ if (nowSelected != gCurrentDisplayDeckId)
+ {
+ if (nowSelected == "threadPaneBox")
+ ShowingThreadPane();
+ else
+ HidingThreadPane();
+
+ if (nowSelected == "accountCentralBox") {
+ if (!document.getElementById("folderPaneBox").collapsed)
+ document.getElementById("folderTree").focus();
+ gAccountCentralLoaded = true;
+ } else {
+ gAccountCentralLoaded = false;
+ }
+ gCurrentDisplayDeckId = nowSelected;
+ }
+}
+
+function InvalidateTabDBs()
+{
+ // enforce reloading the tab's dbView
+ var tabInfos = GetTabMail().tabInfo;
+ for (let i = 0; i < tabInfos.length; ++i)
+ {
+ let tabInfo = tabInfos[i];
+ // only reroot 3pane tabs
+ if (tabInfo.mode.type == "3pane")
+ {
+ // don't change URI if already set -
+ // we might try to read from an invalid msgSelectedFolder
+ if (!("uriToOpen" in tabInfo))
+ tabInfo.uriToOpen = tabInfo.msgSelectedFolder.URI;
+ }
+ }
+}
diff --git a/comm/suite/mailnews/content/tabmail.xml b/comm/suite/mailnews/content/tabmail.xml
new file mode 100644
index 0000000000..613bb3a418
--- /dev/null
+++ b/comm/suite/mailnews/content/tabmail.xml
@@ -0,0 +1,1583 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+ %messengerDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<bindings id="tabmailBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <!-- SeaMonkey's clone of Thunderbird's tab UI mechanism.
+ -
+ - We expect to be instantiated with the following children:
+ - * One "tabpanels" child element whose id must be placed in the
+ - "panelcontainer" attribute on the element we are being bound to. We do
+ - this because it is important to allow overlays to contribute panels.
+ - When we attempted to have the immediate children of the bound element
+ - be propagated through use of the "children" tag, we found that children
+ - contributed by overlays did not propagate.
+ - * Any children you want added to the right side of the tab bar. This is
+ - primarily intended to allow for "open a BLANK tab" buttons, namely
+ - calendar and tasks. For reasons similar to the tabpanels case, we
+ - expect the instantiating element to provide a child hbox for overlays
+ - to contribute buttons to.
+ -
+ - From a javascript perspective, there are three types of code that we
+ - expect to interact with:
+ - 1) Code that wants to open new tabs.
+ - 2) Code that wants to contribute one or more varieties of tabs.
+ - 3) Code that wants to monitor to know when the active tab changes.
+ -
+ - Consumer code should use the following methods:
+ - * openTab(aTabModeName, aArgs): Open a tab of the given "mode",
+ - passing the provided arguments as an object. The tab type author
+ - should tell you the modes they implement and the required/optional
+ - arguments.
+ - One of the arguments you can pass is "background": if this is true,
+ - the tab will be loaded in the background.
+ - * setTabTitle([aOptionalTabInfo]): Tells us that the title of the current
+ - tab (if no argument is provided) or provided tab needs to be updated.
+ - This will result in a call to the tab mode's logic to update the title.
+ - In the event this is not for the current tab, the caller is responsible
+ - for ensuring that the underlying tab mode is capable of providing a tab
+ - title when it is in the background.
+ - * removeCurrentTab(): Close the current tab.
+ - * removeTab(aTabElement): Close the tab whose tabmail-tab bound
+ - element is passed in.
+ - Changing the currently displayed tab is accomplished by changing
+ - tabmail.tabContainer's selectedIndex or selectedItem property.
+ -
+ - Tab contributing code should define a tab type object and register it
+ - with us by calling registerTabType. Each tab type can provide multiple
+ - tab modes. The rationale behind this organization is that Thunderbird
+ - historically/currently uses a single 3-pane view to display both
+ - three-pane folder browsing and single message browsing across multiple
+ - tabs. Each tab type has the ability to use a single tab panel for all
+ - of its display needs. So Thunderbird's "mail" tab type covers both the
+ - "folder" (3-pane folder-based browsing) and "message" (just a single
+ - message) tab modes, while SeaMonkey integrates both flavours into just
+ - one "3pane" mode. Likewise, calendar/lightning currently displays
+ - both its calendar and tasks in the same panel. A tab type can also
+ - create a new tabpanel for each tab as it is created. In that case, the
+ - tab type should probably only have a single mode unless there are a
+ - number of similar modes that can gain from code sharing.
+ - The tab type definition should include the following attributes:
+ - * name: The name of the tab-type, mainly to aid in debugging.
+ - * panelId or perTabPanel: If using a single tab panel, the id of the
+ - panel must be provided in panelId. If using one tab panel per tab,
+ - perTabPanel should be either the XUL element name that should be
+ - created for each tab, or a helper function to create and return the
+ - element.
+ - * modes: An object whose attributes are mode names (which are
+ - automatically propagated to a 'name' attribute for debugging) and
+ - values are objects with the following attributes...
+ - * any of the openTab/closeTab/saveTabState/showTab/onTitleChanged
+ - functions as described on the mode definitions. These will only be
+ - called if the mode does not provide the functions. Note that because
+ - the 'this' variable passed to the functions will always reference the
+ - tab type definition (rather than the mode definition), the mode
+ - functions can defer to the tab type functions by calling
+ - this.functionName(). (This should prove convenient.)
+ - Mode definition attributes:
+ - * type: The "type" attribute to set on the displayed tab for CSS purposes.
+ - Generally, this would be the same as the mode name, but you can do as
+ - you please.
+ - * isDefault: This should only be present and should be true for the tab
+ - mode that is the tab displayed automatically on startup.
+ - * maxTabs: The maximum number of this mode that can be opened at a time.
+ - If this limit is reached, any additional calls to openTab for this
+ - mode will simply result in the first existing tab of this mode being
+ - displayed.
+ - * shouldSwitchTo(aArgs): Optional function. Called when openTab is called
+ - on the top-level tabmail binding. It is used to decide if the openTab
+ - function should switch to an existing tab or actually open a new tab.
+ - If the openTab function should switch to an existing tab, return the
+ - index of that tab; otherwise return -1.
+ - aArgs is a set of named parameters (the ones that are later passed to
+ - openTab).
+ - * openTab(aTabInfo, aArgs): Called when a tab of the given mode is in the
+ - process of being opened. aTabInfo will have its "mode" attribute
+ - set to the mode definition of the tab mode being opened. You should
+ - set the "title" attribute on it, and may set any other attributes
+ - you wish for your own use in subsequent functions. Note that 'this'
+ - points to the tab type definition, not the mode definition as you
+ - might expect. This allows you to place common logic code on the
+ - tab type for use by multiple modes and to defer to it. Any arguments
+ - provided to the caller of tabmail.openTab will be passed to your
+ - function as well, including background.
+ - * canCloseTab(aTabInfo): Optional function.
+ - Return true (false) if the tab is (not) allowed to close.
+ - A tab's default permission is stored in aTabInfo.canClose.
+ - * closeTab(aTabInfo): Called when aTabInfo is being closed. The tab need
+ - not be currently displayed. You are responsible for properly cleaning
+ - up any state you preserved in aTabInfo.
+ - * saveTabState(aTabInfo): Called when aTabInfo is being switched away from
+ - so that you can preserve its state on aTabInfo. This is primarily for
+ - single tab panel implementations; you may not have much state to save
+ - if your tab has its own tab panel.
+ - * showTab(aTabInfo): Called when aTabInfo is being displayed and you
+ - should restore its state (if required).
+ - * onTitleChanged(aTabInfo): Called when someone calls
+ - tabmail.setTabTitle() to hint that the tab's title needs to be
+ - updated. This function should update aTabInfo.title if it can.
+ - * getBrowser(aTabInfo): This function should return the browser element
+ - for your tab if there is one (return null or don't define this
+ - function otherwise). It is used for some toolkit functions that
+ - require a global "getBrowser" function, e.g. ZoomManager.
+ -
+ - Mode definition functions for menu/toolbar commands (see nsIController):
+ - * supportsCommand(aCommand, aTabInfo): Called when a menu or toolbar needs
+ - to be updated. Return true if you support that command in
+ - isCommandEnabled and doCommand, return false otherwise.
+ - * isCommandEnabled(aCommand, aTabInfo): Called when a menu or toolbar
+ - needs to be updated. Return true if the command can be executed at the
+ - current time, false otherwise.
+ - * doCommand(aCommand, aTabInfo): Called when a menu or toolbar command is
+ - to be executed. Perform the action appropriate to the command.
+ - * onEvent(aEvent, aTabInfo): This can be used to handle different events
+ - on the window.
+ -
+ - Tab monitoring code is expected to be used for widgets on the screen
+ - outside of the tab box that need to update themselves as the active tab
+ - changes. This is primarily intended to be used for the ThunderBar; if
+ - you are not the ThunderBar and this sounds appealing to you, please
+ - solicit discussion on your needs on the mozilla.dev.apps.thunderbird
+ - newsgroup.
+ - Tab monitoring code (un)registers itself via (un)registerTabMonitor.
+ - The following functions should be provided on the monitor object:
+ - * onTabTitleChanged(aTabInfo): Called when the tab's title changes.
+ - * onTabSwitched(aTabInfo, aOldTabInfo): Called when a new tab is made
+ - active. If this is the first tab ever, aOldTabInfo will be null,
+ - otherwise aOldTabInfo will be the previously active tab.
+ -->
+ <binding id="tabmail"
+ extends="chrome://navigator/content/tabbrowser.xml#tabbrowser">
+ <resources>
+ <stylesheet src="chrome://navigator/skin/tabbrowser.css"/>
+ </resources>
+ <content>
+ <xul:stringbundle anonid="tmstringbundle" src="chrome://messenger/locale/tabmail.properties"/>
+ <xul:tabbox anonid="tabbox"
+ flex="1"
+ eventnode="document"
+ onselect="if (event.target.localName == 'tabs' &amp;&amp;
+ 'updateCurrentTab' in this.parentNode)
+ this.parentNode.updateCurrentTab();">
+ <xul:hbox class="tab-drop-indicator-bar" collapsed="true">
+ <xul:hbox class="tab-drop-indicator" mousethrough="always"/>
+ </xul:hbox>
+ <xul:hbox class="tabbrowser-strip tabmail-strip"
+ tooltip="_child"
+ context="_child"
+ anonid="strip"
+ ondragstart="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
+ <xul:tooltip onpopupshowing="var tabmail = this.parentNode.parentNode.parentNode;
+ return tabmail.FillTabmailTooltip(document, event);"/>
+ <xul:menupopup anonid="tabContextMenu"
+ onpopupshowing="return document.getBindingParent(this)
+ .onTabContextMenuShowing();">
+ <xul:menuitem label="&closeTabCmd.label;"
+ accesskey="&closeTabCmd.accesskey;"
+ oncommand="var tabmail = document.getBindingParent(this);
+ tabmail.removeTab(tabmail.mContextTab);"/>
+ </xul:menupopup>
+ <xul:tabs class="tabbrowser-tabs tabmail-tabs"
+ flex="1"
+ anonid="tabcontainer"
+ setfocus="false"
+ onclick="this.parentNode.parentNode.parentNode.onTabClick(event);">
+ <xul:tab selected="true"
+ validate="never"
+ type="3pane"
+ maxwidth="250"
+ width="0"
+ minwidth="100"
+ flex="100"
+ class="tabbrowser-tab tabmail-tab icon-holder"
+ crop="end"/>
+ </xul:tabs>
+ <children/>
+ </xul:hbox>
+ <!-- Remember, user of this binding, you need to provide tabpanels! -->
+ <children includes="tabpanels"/>
+ </xul:tabbox>
+ </content>
+
+ <implementation implements="nsIController, nsIObserver">
+ <constructor>
+ <![CDATA[
+ window.controllers.insertControllerAt(0, this);
+ const kAutoHide = "mail.tabs.autoHide";
+ this.mAutoHide = Services.prefs.getBoolPref(kAutoHide);
+ Services.prefs.addObserver(kAutoHide, this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("mail.tabs.autoHide", this);
+ window.controllers.removeController(this);
+ ]]>
+ </destructor>
+
+ <field name="currentTabInfo">
+ null
+ </field>
+
+ <field name="tabTypes" readonly="true">
+ new Object()
+ </field>
+
+ <field name="tabModes" readonly="true">
+ new Object()
+ </field>
+
+ <field name="defaultTabMode">
+ null
+ </field>
+
+ <field name="tabInfo" readonly="true">
+ new Array()
+ </field>
+
+ <field name="tabStrip" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "strip");
+ </field>
+
+ <field name="tabContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
+ </field>
+
+ <field name="panelContainer" readonly="true">
+ document.getElementById(this.getAttribute("panelcontainer"));
+ </field>
+ <field name="tabs" readonly="true">
+ this.tabContainer.childNodes
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tmstringbundle");
+ </field>
+ <field name="mContextTab">
+ null
+ </field>
+
+ <!-- _mAutoHide/mAutoHide reflect the current autoHide pref value -->
+ <field name="_mAutoHide">false</field>
+ <property name="mAutoHide" onget="return this._mAutoHide;">
+ <setter>
+ <![CDATA[
+ if (val != this._mAutoHide)
+ {
+ if (this.tabContainer.childNodes.length == 1)
+ this.mStripVisible = !val;
+ this._mAutoHide = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <!-- mStripVisible reflects the actual XUL autoHide state -->
+ <property name="mStripVisible"
+ onget="return !this.tabStrip.collapsed;"
+ onset="return this.tabStrip.collapsed = !val;"/>
+
+ <method name="registerTabType">
+ <parameter name="aTabType"/>
+ <body>
+ <![CDATA[
+ if (aTabType.name in this.tabTypes)
+ return;
+ this.tabTypes[aTabType.name] = aTabType;
+ for (let [modeName, modeDetails] of Object.entries(aTabType.modes))
+ {
+ modeDetails.name = modeName;
+ modeDetails.tabType = aTabType;
+ modeDetails.tabs = [];
+ this.tabModes[modeName] = modeDetails;
+ if (modeDetails.isDefault)
+ this.defaultTabMode = modeDetails;
+ }
+ aTabType.panel = document.getElementById(aTabType.panelId);
+ ]]>
+ </body>
+ </method>
+
+ <field name="tabMonitors" readonly="true">
+ new Array()
+ </field>
+
+ <method name="registerTabMonitor">
+ <parameter name="aTabMonitor"/>
+ <body>
+ <![CDATA[
+ if (!this.tabMonitors.includes(aTabMonitor))
+ this.tabMonitors.push(aTabMonitor);
+ ]]>
+ </body>
+ </method>
+
+ <method name="unregisterTabMonitor">
+ <parameter name="aTabMonitor"/>
+ <body>
+ <![CDATA[
+ let index = this.tabMonitors.indexOf(aTabMonitor);
+ if (index >= 0)
+ this.tabMonitors.splice(index, 1);
+ ]]>
+ </body>
+ </method>
+
+ <method name="openFirstTab">
+ <body>
+ <![CDATA[
+ // From the moment of creation, our XBL binding already has a
+ // visible tab. We need to create a tab information structure for
+ // this tab. In the process we also generate a synthetic "tab title
+ // changed" event to ensure we have an accurate title.
+ // Note: for mail tabs, the title gets only set later when the
+ // folder or message is loaded, as we don't have a gDBView yet!
+ // We assume the tab contents will set themselves up correctly.
+ if (!this.tabInfo.length)
+ {
+ let firstTabInfo = {mode: this.defaultTabMode, canClose: true};
+ let firstTabNode = this.tabContainer.firstChild;
+ firstTabInfo.mode.tabs.push(firstTabInfo);
+ this.tabInfo[0] = this.currentTabInfo = firstTabInfo;
+ this.setTabTitle(firstTabInfo);
+ if (this.tabMonitors.length)
+ {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabSwitched(firstTabInfo, null);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="openTab">
+ <parameter name="aTabModeName"/>
+ <parameter name="aArgs"/>
+ <body>
+ <![CDATA[
+ if (!aTabModeName)
+ aTabModeName = this.currentTabInfo.mode.type;
+
+ let tabMode = this.tabModes[aTabModeName];
+ // if we are already at our limit for this mode, show an existing one
+ if (tabMode.tabs.length == tabMode.maxTabs)
+ {
+ // show the first tab of this mode
+ this.tabContainer.selectedIndex = this.tabInfo.indexOf(tabMode.tabs[0]);
+ return;
+ }
+
+ // Do this so that we don't generate strict warnings.
+ let background = ("background" in aArgs) && aArgs.background;
+
+ // If the mode wants us to, we should switch to an existing tab
+ // rather than open a new one. We shouldn't switch to the tab if
+ // we're opening it in the background, though.
+ let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
+ tabMode.tabType.shouldSwitchTo;
+
+ if (shouldSwitchToFunc)
+ {
+ let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, [aArgs]);
+ if (tabIndex >= 0)
+ {
+ if (!background)
+ this.selectTabByIndex(tabIndex);
+ return;
+ }
+ }
+
+ if (!background)
+ // we need to save the state before it gets corrupted
+ this.saveCurrentTabState();
+
+ let tabInfo = {mode: tabMode, canClose: true};
+ tabMode.tabs.push(tabInfo);
+
+ let t = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "tab");
+ t.setAttribute("crop", "end");
+ t.maxWidth = this.tabContainer.mTabMaxWidth;
+ t.minWidth = this.tabContainer.mTabMinWidth;
+ t.width = 0;
+ t.setAttribute("flex", "100");
+ t.setAttribute("validate", "never");
+ t.className = "tabbrowser-tab tabmail-tab icon-holder";
+ // for styling purposes, apply the type to the tab
+ // (this attribute may be overwritten by mode functions)
+ t.setAttribute("type", tabInfo.mode.type);
+ this.tabContainer.appendChild(t);
+ if (!this.mStripVisible)
+ {
+ this.mStripVisible = true;
+ this.tabContainer._updateCloseButtons();
+ }
+
+ let oldPanel = this.panelContainer.selectedPanel;
+
+ // Open new tabs in the background?
+ tabInfo.switchToNewTab = !background;
+
+ // the order of the following statements is important
+ let oldTabInfo = this.currentTabInfo;
+ this.tabInfo[this.tabContainer.childNodes.length - 1] = tabInfo;
+
+ if (!background) {
+ this.currentTabInfo = tabInfo;
+ // this has a side effect of calling updateCurrentTab, but our
+ // setting currentTabInfo above will cause it to take no action.
+ this.tabContainer.selectedIndex =
+ this.tabContainer.childNodes.length - 1;
+ }
+ // make sure we are on the right panel
+ let selectedPanel;
+ if (tabInfo.mode.tabType.perTabPanel)
+ {
+ // should we create the element for them, or will they do it?
+ if (typeof(tabInfo.mode.tabType.perTabPanel) == "string")
+ {
+ tabInfo.panel = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ tabInfo.mode.tabType.perTabPanel);
+ }
+ else
+ {
+ tabInfo.panel = tabInfo.mode.tabType.perTabPanel(tabInfo);
+ }
+ this.panelContainer.appendChild(tabInfo.panel);
+ selectedPanel = tabInfo.panel;
+ }
+ else
+ {
+ selectedPanel = tabInfo.mode.tabType.panel;
+ }
+ if (!background)
+ this.panelContainer.selectedPanel = selectedPanel;
+
+ oldPanel.removeAttribute("selected");
+ this.panelContainer.selectedPanel.setAttribute("selected", "true");
+
+ let tabOpenFunc = tabInfo.mode.openTab ||
+ tabInfo.mode.tabType.openTab;
+ if (tabOpenFunc)
+ tabOpenFunc.apply(tabInfo.mode.tabType, [tabInfo, aArgs]);
+
+ if (background) {
+ // if the new tab isn't made current,
+ // its title won't change automatically
+ this.setTabTitle(tabInfo);
+ }
+
+ if (!background && this.tabMonitors.length) {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
+ }
+
+ t.setAttribute("label", tabInfo.title);
+
+ if (!background) {
+ let docTitle = tabInfo.title;
+ if (AppConstants.platform != "macosx") {
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
+ }
+ document.title = docTitle;
+
+ // Update the toolbar status - we don't need to do menus as they
+ // do themselves when we open them.
+ UpdateMailToolbar("tabmail");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectTabByMode">
+ <parameter name="aTabModeName"/>
+ <body>
+ <![CDATA[
+ let tabMode = this.tabModes[aTabModeName];
+ if (tabMode.tabs.length)
+ {
+ let desiredTab = tabMode.tabs[0];
+ this.tabContainer.selectedIndex = this.tabInfo.indexOf(desiredTab);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectTabByIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ // count backwards for aIndex < 0
+ if (aIndex < 0)
+ aIndex += this.tabInfo.length;
+ if (aIndex >= 0 &&
+ aIndex < this.tabInfo.length &&
+ aIndex != this.tabContainer.selectedIndex)
+ {
+ this.tabContainer.selectedIndex = aIndex;
+ }
+
+ if (aEvent)
+ {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="closeTabs">
+ <body>
+ <![CDATA[
+ for (let i = 0; i < this.tabInfo.length; i++)
+ {
+ let tabInfo = this.tabInfo[i];
+ let tabCloseFunc = tabInfo.mode.closeTab ||
+ tabInfo.mode.tabType.closeTab;
+ if (tabCloseFunc)
+ tabCloseFunc.call(tabInfo.mode.tabType, tabInfo);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTab">
+ <parameter name="aTabNode"/>
+ <!-- parameter name="aMoreParameters..."/-->
+ <body>
+ <![CDATA[
+ // Find and locate the tab in our list.
+ let iTab, numTabs = this.tabContainer.childNodes.length;
+ for (iTab = 0; iTab < numTabs; iTab++)
+ if (this.tabContainer.childNodes[iTab] == aTabNode)
+ break;
+ let tabInfo = this.tabInfo[iTab];
+
+ // ask the tab type implementation if we're allowed to close the tab
+ let canClose = tabInfo.canClose;
+ let canCloseFunc = tabInfo.mode.canCloseTab ||
+ tabInfo.mode.tabType.canCloseTab;
+ if (canCloseFunc)
+ canClose = canCloseFunc.call(tabInfo.mode.tabType, tabInfo);
+ if (!canClose)
+ return;
+
+ let closeFunc = tabInfo.mode.closeTab ||
+ tabInfo.mode.tabType.closeTab;
+ if (closeFunc)
+ closeFunc.call(tabInfo.mode.tabType, tabInfo);
+
+ this.tabInfo.splice(iTab, 1);
+ tabInfo.mode.tabs.splice(tabInfo.mode.tabs.indexOf(tabInfo), 1);
+ aTabNode.remove();
+ --numTabs;
+ if (this.tabContainer.selectedIndex == -1)
+ this.tabContainer.selectedIndex = (iTab == numTabs) ? iTab - 1 : iTab;
+ if (this.currentTabInfo == tabInfo)
+ this.updateCurrentTab();
+
+ if (tabInfo.panel)
+ {
+ tabInfo.panel.remove();
+ delete tabInfo.panel;
+ }
+ if (numTabs == 1 && this.mAutoHide)
+ this.mStripVisible = false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <body>
+ <![CDATA[
+ this.removeTab(this.tabContainer.selectedItem);
+ ]]>
+ </body>
+ </method>
+
+ <!-- UpdateCurrentTab - called in response to changing the current tab -->
+ <method name="updateCurrentTab">
+ <body>
+ <![CDATA[
+ if (this.currentTabInfo != this.tabInfo[this.tabContainer.selectedIndex])
+ {
+ if (this.currentTabInfo)
+ this.saveCurrentTabState();
+ let oldTabInfo = this.currentTabInfo;
+ let oldPanel = this.panelContainer.selectedPanel;
+ let tabInfo = this.currentTabInfo = this.tabInfo[this.tabContainer.selectedIndex];
+ this.panelContainer.selectedPanel = tabInfo.panel ||
+ tabInfo.mode.tabType.panel;
+
+ // Update the selected attribute on the current and old tab panel.
+ oldPanel.removeAttribute("selected");
+ this.panelContainer.selectedPanel.setAttribute("selected", "true");
+
+ let showTabFunc = tabInfo.mode.showTab ||
+ tabInfo.mode.tabType.showTab;
+ if (showTabFunc)
+ showTabFunc.call(tabInfo.mode.tabType, tabInfo);
+ if (this.tabMonitors.length)
+ {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
+ }
+
+ let docTitle = tabInfo.title;
+ if (AppConstants.platform != "macosx") {
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
+ }
+ document.title = docTitle;
+
+ // Update the toolbar status - we don't need to do menus as they
+ // do themselves when we open them.
+ UpdateMailToolbar("tabmail");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="saveTabState">
+ <parameter name="aTabInfo"/>
+ <body>
+ <![CDATA[
+ if (!aTabInfo)
+ return;
+ let saveTabFunc = aTabInfo.mode.saveTabState ||
+ aTabInfo.mode.tabType.saveTabState;
+ if (saveTabFunc)
+ saveTabFunc.call(aTabInfo.mode.tabType, aTabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="saveCurrentTabState">
+ <body>
+ <![CDATA[
+ if (!this.currentTabInfo)
+ this.currentTabInfo = this.tabInfo[0];
+ // save the old tab state before we change the current tab
+ this.saveTabState(this.currentTabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTabInfo"/>
+ <body>
+ <![CDATA[
+ // First find the tab and its index.
+ let tabInfo;
+ let index;
+ if (aTabInfo)
+ {
+ tabInfo = aTabInfo;
+ for (index = 0; index < this.tabInfo.length; ++index)
+ {
+ if (tabInfo == this.tabInfo[index])
+ break;
+ }
+ }
+ else
+ {
+ index = this.tabContainer.selectedIndex;
+ tabInfo = this.tabInfo[index];
+ }
+
+ if (tabInfo)
+ {
+ let tabNode = this.tabContainer.childNodes[index];
+ let titleChangeFunc = tabInfo.mode.onTitleChanged ||
+ tabInfo.mode.tabType.onTitleChanged;
+ if (titleChangeFunc)
+ titleChangeFunc.call(tabInfo.mode.tabType, tabInfo, tabNode);
+ if (this.tabMonitors.length)
+ {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabTitleChanged(tabInfo);
+ }
+ tabNode.setAttribute("label", tabInfo.title);
+
+ // Update the window title if we're the displayed tab.
+ if (index == this.tabContainer.selectedIndex)
+ {
+ let docTitle = tabInfo.title;
+ if (AppConstants.platform != "macosx") {
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
+ }
+ document.title = docTitle;
+
+ // Update the toolbar status - we don't need to do menus as they
+ // do themselves when we open them.
+ UpdateMailToolbar("tabmail");
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="FillTabmailTooltip">
+ <parameter name="aDocument"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ aEvent.stopPropagation();
+ let tn = aDocument.tooltipNode;
+ if (tn.localName != "tab")
+ return false; // Not a tab, so cancel the tooltip.
+ if (tn.hasAttribute("label"))
+ {
+ aEvent.target.setAttribute("label", tn.getAttribute("label"));
+ return true;
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="onTabContextMenuShowing">
+ <body>
+ <![CDATA[
+ // The user might right-click on a non-tab area of the tab strip.
+ this.mContextTab = document.popupNode;
+ return this.mContextTab.localName == "tab";
+ ]]>
+ </body>
+ </method>
+
+ <!-- getBrowserForSelectedTab is required as some toolkit functions
+ require a getBrowser() function. -->
+ <method name="getBrowserForSelectedTab">
+ <body>
+ <![CDATA[
+ if (!this.currentTabInfo)
+ this.currentTabInfo = this.tabInfo[0];
+ let tabInfo = this.currentTabInfo;
+ let browserFunc = tabInfo.mode.getBrowser ||
+ tabInfo.mode.tabType.getBrowser;
+ if (!browserFunc)
+ return null;
+ return browserFunc.call(tabInfo.mode.tabType, tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIObserver implementation -->
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ const kAutoHide = "mail.tabs.autoHide";
+ if (aTopic == "nsPref:changed" && aData == kAutoHide)
+ this.mAutoHide = Services.prefs.getBoolPref(kAutoHide);
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIController implementation -->
+
+ <method name="supportsCommand">
+ <parameter name="aCommand"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return false;
+
+ let supportsCommandFunc = tabInfo.mode.supportsCommand ||
+ tabInfo.mode.tabType.supportsCommand;
+ if (!supportsCommandFunc)
+ return false;
+ return supportsCommandFunc.call(tabInfo.mode.tabType,
+ aCommand,
+ tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isCommandEnabled">
+ <parameter name="aCommand"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return false;
+
+ let isCommandEnabledFunc = tabInfo.mode.isCommandEnabled ||
+ tabInfo.mode.tabType.isCommandEnabled;
+ if (!isCommandEnabledFunc)
+ return false;
+ return isCommandEnabledFunc.call(tabInfo.mode.tabType,
+ aCommand,
+ tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="doCommand">
+ <parameter name="aCommand"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return;
+
+ let doCommandFunc = tabInfo.mode.doCommand ||
+ tabInfo.mode.tabType.doCommand;
+ if (!doCommandFunc)
+ return;
+ doCommandFunc.call(tabInfo.mode.tabType,
+ aCommand,
+ tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return;
+
+ let onEventFunc = tabInfo.mode.onEvent ||
+ tabInfo.mode.tabType.onEvent;
+ if (!onEventFunc)
+ return;
+
+ onEventFunc.call(tabInfo.mode.tabType, aCommand, tabInfo);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="tabmail-tab"
+ display="xul:box"
+ extends="chrome://global/content/bindings/tabbox.xml#tab">
+ <content closetabtext="&tabmailClose.label;">
+ <xul:hbox class="tab-middle box-inherit"
+ xbl:inherits="align,dir,pack,orient,selected"
+ flex="1">
+ <xul:image class="tab-icon tab-icon-image" xbl:inherits="validate,src=image"/>
+ <xul:label class="tab-text"
+ xbl:inherits="value=label,accesskey,crop,disabled"
+ flex="1"/>
+ </xul:hbox>
+ <xul:toolbarbutton anonid="close-button"
+ tooltiptext="&tabmailClose.tooltip;"
+ tabindex="-1"
+ class="tabs-closebutton tab-close-button"/>
+ </content>
+
+ <implementation>
+ <field name="mCorrespondingMenuitem">null</field>
+ </implementation>
+ </binding>
+
+ <binding id="tabmail-arrowscrollbox"
+ extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <content>
+ <xul:toolbarbutton class="scrollbutton-up tab-scrollbutton-up"
+ collapsed="true"
+ xbl:inherits="orient"
+ anonid="scrollbutton-up"
+ onmousedown="_startScroll(-1);"
+ onmouseup="_stopScroll();"
+ onmouseout="_stopScroll();"/>
+ <xul:scrollbox xbl:inherits="orient,align,pack,dir"
+ flex="1"
+ anonid="scrollbox">
+ <children/>
+ </xul:scrollbox>
+ <xul:stack align="center" pack="end" class="scrollbutton-down-stack">
+ <xul:hbox flex="1"
+ class="scrollbutton-down-box"
+ collapsed="true"
+ anonid="down-box"/>
+ <xul:hbox flex="1"
+ class="scrollbutton-down-box-animate"
+ collapsed="true"
+ anonid="down-box-animate"/>
+ <xul:toolbarbutton class="scrollbutton-down tab-scrollbutton-down"
+ collapsed="true"
+ xbl:inherits="orient"
+ anonid="scrollbutton-down"
+ onmousedown="_startScroll(1);"
+ onmouseup="_stopScroll();"
+ onmouseout="_stopScroll();"/>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <field name="_scrollButtonDownBox">
+ document.getAnonymousElementByAttribute(this, "anonid", "down-box");
+ </field>
+ <field name="_scrollButtonDownBoxAnimate">
+ document.getAnonymousElementByAttribute(this, "anonid", "down-box-animate");
+ </field>
+ </implementation>
+
+ <handlers>
+ <handler event="underflow" phase="target">
+ <![CDATA[
+ // Ignore vertical events.
+ if (event.detail == 0)
+ return;
+ this._scrollButtonDownBox.collapsed = true;
+ this._scrollButtonDownBoxAnimate.collapsed = true;
+ ]]>
+ </handler>
+
+ <handler event="overflow" phase="target">
+ <![CDATA[
+ // Ignore vertical events.
+ if (event.detail == 0)
+ return;
+ this._scrollButtonDownBox.collapsed = false;
+ this._scrollButtonDownBoxAnimate.collapsed = false;
+ ]]>
+ </handler>
+
+ <handler event="UpdatedScrollButtonsDisabledState">
+ <![CDATA[
+ // filter underflow events which were dispatched on nested scrollboxes
+ if (event.target != this)
+ return;
+
+ // fix for bug #352353
+ // unlike the scrollup button on the tab strip (which is a
+ // simple toolbarbutton) the scrolldown button is
+ // a more complicated stack of boxes and a toolbarbutton
+ // so that we can animate when a tab is opened offscreen.
+ // in order to style the box with the actual background image
+ // we need to manually set the disable state to match the
+ // disable state of the toolbarbutton.
+ this._scrollButtonDownBox
+ .setAttribute("disabled", this._scrollButtonDown.disabled);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabmail-tabs"
+ extends="chrome://global/content/bindings/tabbox.xml#tabs">
+ <content>
+ <xul:stack flex="1" class="tabs-stack">
+ <xul:vbox>
+ <xul:spacer flex="1"/>
+ <xul:hbox class="tabs-bottom" align="center"/>
+ </xul:vbox>
+ <xul:stack>
+ <xul:spacer class="tabs-left tabs-right"/>
+ <xul:hbox>
+ <xul:hbox class="tabs-newbutton-box"
+ pack="start"
+ anonid="tabstrip-newbutton">
+ <xul:toolbarbutton class="new-button tabs-newbutton"
+ tooltiptext="&tabmailNewButton.tooltip;"/>
+ </xul:hbox>
+ <xul:arrowscrollbox anonid="arrowscrollbox"
+ class="tabbrowser-arrowscrollbox tabmail-arrowscrollbox"
+ flex="1"
+ xbl:inherits="smoothscroll"
+ orient="horizontal"
+ style="min-width: 1px;">
+ <children includes="tab"/>
+ </xul:arrowscrollbox>
+ <children/>
+ <xul:hbox class="tabs-closebutton-box"
+ align="center"
+ pack="end"
+ anonid="tabstrip-closebutton">
+ <xul:toolbarbutton class="close-button tabs-closebutton"
+ tooltiptext="&tabmailCloseButton.tooltip;"/>
+ </xul:hbox>
+ <xul:stack align="center" pack="end" class="tabs-alltabs-stack">
+ <xul:hbox flex="1" class="tabs-alltabs-box" anonid="alltabs-box"/>
+ <xul:hbox flex="1"
+ class="tabs-alltabs-box-animate"
+ anonid="alltabs-box-animate"/>
+ <xul:toolbarbutton class="tabs-alltabs-button"
+ type="menu"
+ anonid="alltabs-button"
+ tooltipstring="&tabmailAllTabs.tooltip;">
+ <xul:menupopup class="tabs-alltabs-popup"
+ anonid="alltabs-popup"
+ position="after_end"/>
+ </xul:toolbarbutton>
+ </xul:stack>
+ </xul:hbox>
+ </xul:stack>
+ </xul:stack>
+ </content>
+
+ <implementation implements="nsITimerCallback, nsIDOMEventListener, nsIObserver">
+ <constructor>
+ <![CDATA[
+ this.mTabMinWidth = Services.prefs.getIntPref ("browser.tabs.tabMinWidth");
+ this.mTabMaxWidth = Services.prefs.getIntPref ("browser.tabs.tabMaxWidth");
+ this.mTabClipWidth = Services.prefs.getIntPref ("browser.tabs.tabClipWidth");
+ this.mCloseButtons = Services.prefs.getIntPref ("browser.tabs.closeButtons");
+ this.firstChild.minWidth = this.mTabMinWidth;
+ this.firstChild.maxWidth = this.mTabMaxWidth;
+ this._updateCloseButtons();
+ Services.prefs.addObserver("browser.tabs.", this);
+ window.addEventListener("resize", this);
+
+ // Listen to overflow/underflow events on the tabstrip,
+ // we cannot put these as xbl handlers on the entire binding because
+ // they would also get called for the all-tabs popup scrollbox.
+ // Also, we can't rely on event.target because these are all
+ // anonymous nodes.
+ this.arrowScrollbox.addEventListener("overflow", this);
+ this.arrowScrollbox.addEventListener("underflow", this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("browser.tabs.", this);
+
+ // Release timer to avoid reference cycles.
+ if (this._animateTimer)
+ {
+ this._animateTimer.cancel();
+ this._animateTimer = null;
+ }
+ this.arrowScrollbox.removeEventListener("overflow", this);
+ this.arrowScrollbox.removeEventListener("underflow", this);
+ ]]>
+ </destructor>
+
+ <field name="arrowScrollboxWidth">0</field>
+
+ <field name="arrowScrollbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+
+ <field name="arrowScrollboxClosebutton">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
+ </field>
+
+ <field name="mTabMinWidth">100</field>
+ <field name="mTabMaxWidth">250</field>
+ <field name="mTabClipWidth">140</field>
+ <field name="mCloseButtons">3</field>
+ <method name="_updateCloseButtons">
+ <body>
+ <![CDATA[
+ // modes for tabstrip
+ // 0 - activetab = close button on active tab only
+ // 1 - alltabs = close buttons on all tabs
+ // 2 - noclose = no close buttons at all
+ // 3 - closeatend = close button at the end of the tabstrip
+ switch (this.mCloseButtons)
+ {
+ case 0:
+ this.setAttribute("closebuttons", "activetab");
+ break;
+ case 1:
+ let width = this.firstChild.boxObject.width;
+ // 0 width is an invalid value and indicates
+ // an item without display, so ignore.
+ if (width > this.mTabClipWidth || width == 0)
+ this.setAttribute("closebuttons", "alltabs");
+ else
+ this.setAttribute("closebuttons", "activetab");
+ break;
+ case 2:
+ this.setAttribute("closebuttons", "noclose");
+ break;
+ case 3:
+ this.setAttribute("closebuttons", "closeatend");
+ break;
+ }
+ this.arrowScrollboxClosebutton.collapsed = this.mCloseButtons != 3;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabSelect">
+ <body>
+ <![CDATA[
+ this.arrowScrollbox.ensureElementIsVisible(this.selectedItem);
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type)
+ {
+ case "overflow":
+ this.setAttribute("overflow", "true");
+ this.arrowScrollbox.scrollBoxObject
+ .ensureElementIsVisible(this.selectedItem);
+ break;
+ case "underflow":
+ this.removeAttribute("overflow");
+ break;
+ case "resize":
+ let width = this.arrowScrollbox.boxObject.width;
+ if (width != this.arrowScrollboxWidth)
+ {
+ this._updateCloseButtons();
+ // XXX without this line the tab bar won't budge
+ this.arrowScrollbox.scrollByPixels(1);
+ this._handleTabSelect();
+ this.arrowScrollboxWidth = width;
+ }
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="mAllTabsPopup">
+ document.getAnonymousElementByAttribute(this, "anonid", "alltabs-popup");
+ </field>
+
+ <field name="mAllTabsBoxAnimate">
+ document.getAnonymousElementByAttribute(this, "anonid", "alltabs-box-animate");
+ </field>
+
+ <field name="mDownBoxAnimate">
+ this.arrowScrollbox._scrollButtonDownBoxAnimate;
+ </field>
+
+ <field name="mAllTabsButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "alltabs-button");
+ </field>
+
+ <field name="_animateTimer">null</field>
+ <field name="_animateStep">-1</field>
+ <field name="_animateDelay">25</field>
+ <field name="_animatePercents">
+ [1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
+ 0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
+ 0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
+ 0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
+ 0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
+ 0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
+ </field>
+
+ <method name="_stopAnimation">
+ <body>
+ <![CDATA[
+ if (this._animateStep != -1)
+ {
+ if (this._animateTimer)
+ this._animateTimer.cancel();
+
+ this._animateStep = -1;
+ this.mAllTabsBoxAnimate.style.opacity = 0.0;
+ this.mDownBoxAnimate.style.opacity = 0.0;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_notifyBackgroundTab">
+ <parameter name="aTabNode"/>
+ <body>
+ <![CDATA[
+ let tsbo = this.arrowScrollbox.scrollBoxObject;
+ let tsboStart = tsbo.screenX;
+ let tsboEnd = tsboStart + tsbo.width;
+ let ctbo = aTabNode.boxObject;
+ let ctboStart = ctbo.screenX;
+ let ctboEnd = ctboStart + ctbo.width;
+
+ // only start the flash timer if the new tab (which was loaded in
+ // the background) is not completely visible
+ if (tsboStart > ctboStart || ctboEnd > tsboEnd)
+ {
+ this._animateStep = 0;
+
+ if (!this._animateTimer)
+
+ this._animateTimer =
+ Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ else
+ this._animateTimer.cancel();
+
+ this._animateTimer.initWithCallback(this,
+ this._animateDelay,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="notify">
+ <parameter name="aTimer"/>
+ <body>
+ <![CDATA[
+ if (!document)
+ aTimer.cancel();
+
+ let percent = this._animatePercents[this._animateStep];
+ this.mAllTabsBoxAnimate.style.opacity = percent;
+ this.mDownBoxAnimate.style.opacity = percent;
+
+ if (this._animateStep < (this._animatePercents.length - 1))
+ this._animateStep++;
+ else
+ this._stopAnimation();
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIObserver implementation -->
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ const kCloseButtons = "browser.tabs.closeButtons";
+ if (aTopic == "nsPref:changed" && aData == kCloseButtons)
+ {
+ this.mCloseButtons = Services.prefs.getIntPref(kCloseButtons);
+ this._updateCloseButtons();
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="TabSelect" action="this._handleTabSelect();"/>
+
+ <handler event="mouseover">
+ <![CDATA[
+ if (event.originalTarget == this.mAllTabsButton)
+ {
+ this.mAllTabsButton
+ .setAttribute("tooltiptext",
+ this.mAllTabsButton.getAttribute("tooltipstring"));
+ }
+ else
+ {
+ this.mAllTabsButton.removeAttribute("tooltiptext");
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- alltabs-popup binding
+ This binding relies on the structure of the tabbrowser binding.
+ Therefore it should only be used as a child of the tabs element.
+ This binding is exposed as a pseudo-public-API so themes can customize
+ the tabbar appearance without having to be scriptable
+ (see globalBindings.xml in osx for example).
+ -->
+ <binding id="tabmail-alltabs-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <method name="_tabOnTabClose">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ let menuItem = aEvent.target.mCorrespondingMenuitem;
+ if (menuItem)
+ menuItem.remove();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type)
+ {
+ case "TabClose":
+ this._tabOnTabClose(aEvent);
+ break;
+ case "TabOpen":
+ this._createTabMenuItem(aEvent.originalTarget);
+ break;
+ case "scroll":
+ this._updateTabsVisibilityStatus();
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_updateTabsVisibilityStatus">
+ <body>
+ <![CDATA[
+ let tabContainer = document.getBindingParent(this);
+ let tabstripBO = tabContainer.arrowScrollbox.scrollBoxObject;
+
+ for (let i = 0; i < this.childNodes.length; i++)
+ {
+ let curTabBO = this.childNodes[i].tab.boxObject;
+ if (curTabBO.screenX >= tabstripBO.screenX &&
+ curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+ this.childNodes[i].removeAttribute("tabIsScrolled");
+ else
+ this.childNodes[i].setAttribute("tabIsScrolled", "true");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_createTabMenuItem">
+ <parameter name="aTabNode"/>
+ <body>
+ <![CDATA[
+ let menuItem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+ menuItem.setAttribute("class", "menuitem-iconic alltabs-item icon-holder");
+ menuItem.setAttribute("label", aTabNode.label);
+ menuItem.setAttribute("crop", aTabNode.getAttribute("crop"));
+ menuItem.setAttribute("image", aTabNode.getAttribute("image"));
+
+ let attributes = ["busy", "selected", "type", "NewMessages", "ServerType",
+ "SpecialFolder", "ImapShared", "BiffState", "IsServer",
+ "IsSecure", "Attachment", "IMAPDeleted", "Offline",
+ "MessageType"];
+
+ attributes.forEach(
+ function(attribute)
+ {
+ if (aTabNode.hasAttribute(attribute))
+ {
+ menuItem.setAttribute(attribute, aTabNode.getAttribute(attribute));
+ }
+ }
+ );
+
+ // Keep some attributes of the menuitem in sync with its
+ // corresponding tab (e.g. the tab label)
+ aTabNode.mCorrespondingMenuitem = menuItem;
+ document.addBroadcastListenerFor(aTabNode, menuItem, "label");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "crop");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "image");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "busy");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "selected");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "NewMessages");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "BiffState");
+ aTabNode.addEventListener("TabClose", this);
+ menuItem.tab = aTabNode;
+ menuItem.addEventListener("command", this);
+ this.appendChild(menuItem);
+ return menuItem;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ // set up the menu popup
+ let tabcontainer = document.getBindingParent(this);
+ let tabs = tabcontainer.childNodes;
+
+ // Listen for changes in the tab bar.
+ let tabbrowser = document.getBindingParent(tabcontainer);
+ tabbrowser.addEventListener("TabOpen", this);
+ tabcontainer.arrowScrollbox.addEventListener("scroll", this);
+
+ // if an animation is in progress and the user
+ // clicks on the "all tabs" button, stop the animation
+ tabcontainer._stopAnimation();
+
+ for (let i = 0; i < tabs.length; i++)
+ this._createTabMenuItem(tabs[i]);
+ this._updateTabsVisibilityStatus();
+ ]]>
+ </handler>
+
+ <handler event="popuphiding">
+ <![CDATA[
+ // clear out the menu popup and remove the listeners
+ while (this.hasChildNodes())
+ {
+ let menuItem = this.lastChild;
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "label");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "crop");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "image");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "busy");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "selected");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "NewMessages");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "BiffState");
+ menuItem.removeEventListener("command", this);
+ menuItem.tab.removeEventListener("TabClose", this);
+ menuItem.tab.mCorrespondingMenuitem = null;
+ menuItem.remove();
+ }
+ let tabcontainer = document.getBindingParent(this);
+ tabcontainer.arrowScrollbox.removeEventListener("scroll", this);
+ document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this);
+ ]]>
+ </handler>
+
+ <handler event="command">
+ <![CDATA[
+ let tabcontainer = document.getBindingParent(this);
+ tabcontainer.selectedItem = event.target.tab;
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- new-tab-button/close-tab-button binding
+ These bindings rely on the structure of the tabbrowser binding.
+ Therefore they should only be used as a child of the tab or the tabs
+ element (in both cases, when they are anonymous nodes of <tabbrowser>).
+ These bindings are exposed as pseudo-public-APIs, so themes can customize
+ the tabbar appearance without having to be scriptable
+ (see globalBindings.xml in osx for example).
+ -->
+ <binding id="tabmail-new-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ let bindingParent = document.getBindingParent(this);
+ if (bindingParent)
+ {
+ let tabmail = document.getBindingParent(bindingParent);
+ if (bindingParent.localName == "tabs")
+ {
+ // new-tab-button only appears in the tabstrip
+ // duplicate the current tab
+ tabmail.openTab("", {});
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="dblclick" button="0" phase="capturing">
+ <![CDATA[
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabmail-close-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ let bindingParent = document.getBindingParent(this);
+ if (bindingParent)
+ {
+ let tabmail = document.getBindingParent(bindingParent);
+ if (bindingParent.localName == "tab")
+ {
+ /* The only sequence in which a second click event (i.e. dblclik)
+ * can be dispatched on an in-tab close button is when it is shown
+ * after the first click (i.e. the first click event was dispatched
+ * on the tab). This happens when we show the close button only on
+ * the active tab. (bug 352021)
+ * The only sequence in which a third click event can be dispatched
+ * on an in-tab close button is when the tab was opened with a
+ * double click on the tabbar. (bug 378344)
+ * In both cases, it is most likely that the close button area has
+ * been accidentally clicked, therefore we do not close the tab.
+ */
+ if (event.detail > 1)
+ return;
+
+ tabmail.removeTab(bindingParent);
+ tabmail._blockDblClick = true;
+
+ /* XXXmano hack (see bug 343628):
+ * Since we're removing the event target, if the user
+ * double-clicks this button, the dblclick event will be dispatched
+ * with the tabbar as its event target (and explicit/originalTarget),
+ * which treats that as a mouse gesture for opening a new tab.
+ * In this context, we're manually blocking the dblclick event
+ * (see onTabBarDblClick).
+ */
+ let clickedOnce = false;
+ function enableDblClick(event)
+ {
+ var target = event.originalTarget;
+ if (target.className == "tab-close-button")
+ target._ignoredClick = true;
+ if (!clickedOnce)
+ {
+ clickedOnce = true;
+ return;
+ }
+ tabContainer._blockDblClick = false;
+ tabContainer.removeEventListener("click", enableDblClick, true);
+ }
+ tabContainer.addEventListener("click", enableDblClick, true);
+ }
+ else
+ {
+ // "tabs"
+ tabmail.removeCurrentTab();
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="dblclick" button="0" phase="capturing">
+ <![CDATA[
+ // for the one-close-button case
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/comm/suite/mailnews/content/threadPane.js b/comm/suite/mailnews/content/threadPane.js
new file mode 100644
index 0000000000..ac4943d91f
--- /dev/null
+++ b/comm/suite/mailnews/content/threadPane.js
@@ -0,0 +1,598 @@
+/* -*- 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 { AppConstants } =
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+var gLastMessageUriToLoad = null;
+var gThreadPaneCommandUpdater = null;
+
+function ThreadPaneOnClick(event)
+{
+ // usually, we're only interested in tree content clicks, not scrollbars etc.
+ let t = event.originalTarget;
+
+ // we may want to open the message in a new tab on middle click
+ if (event.button == kMouseButtonMiddle)
+ {
+ if (t.localName == "treechildren" && AllowOpenTabOnMiddleClick())
+ {
+ // we don't allow new tabs in the search dialog
+ if (document.documentElement.id != "searchMailWindow")
+ {
+ OpenMessageInNewTab(event);
+ RestoreSelectionWithoutContentLoad(GetThreadTree());
+ }
+ return;
+ }
+ }
+
+ // otherwise, we only care about left click events
+ if (event.button != kMouseButtonLeft)
+ return;
+
+ // We are already handling marking as read and flagging in nsMsgDBView.cpp,
+ // so all we need to worry about here is double clicks and column header.
+ // We also get in here for clicks on the "treecol" (headers) and the
+ // "scrollbarbutton" (scrollbar buttons), but we don't want those events to
+ // cause a "double click".
+ if (t.localName == "treecol")
+ {
+ HandleColumnClick(t.id);
+ }
+ else if (t.localName == "treechildren")
+ {
+ let tree = GetThreadTree();
+ // figure out what cell the click was in
+ var cell = tree.treeBoxObject.getCellAt(event.clientX, event.clientY);
+ if (cell.row == -1)
+ return;
+
+ // If the cell is in a "cycler" column or if the user double clicked on the
+ // twisty, don't open the message in a new window.
+ if (event.detail == 2 && !cell.col.cycler && (cell.childElt != "twisty"))
+ {
+ ThreadPaneDoubleClick(event);
+ // Double clicking should not toggle the open/close state of the thread.
+ // This will happen if we don't prevent the event from bubbling to the
+ // default handler in tree.xml.
+ event.stopPropagation();
+ }
+ else if (cell.col.id == "junkStatusCol")
+ {
+ MsgJunkMailInfo(true);
+ }
+ else if (cell.col.id == "threadCol" && !event.shiftKey && (event.ctrlKey || event.metaKey))
+ {
+ gDBView.ExpandAndSelectThreadByIndex(cell.row, true);
+ event.stopPropagation();
+ }
+ }
+}
+
+function nsMsgDBViewCommandUpdater()
+{}
+
+nsMsgDBViewCommandUpdater.prototype =
+{
+ updateCommandStatus : function()
+ {
+ // the back end is smart and is only telling us to update command status
+ // when the # of items in the selection has actually changed.
+ UpdateMailToolbar("dbview driven, thread pane");
+ },
+
+ displayMessageChanged : function(aFolder, aSubject, aKeywords)
+ {
+ if (!gDBView.suppressMsgDisplay)
+ setTitleFromFolder(aFolder, aSubject);
+ ClearPendingReadTimer(); // we are loading / selecting a new message so kill the mark as read timer for the currently viewed message
+ gHaveLoadedMessage = true;
+ goUpdateCommand("button_delete");
+ goUpdateCommand("button_junk");
+ },
+
+ updateNextMessageAfterDelete : function()
+ {
+ SetNextMessageAfterDelete();
+ },
+
+ summarizeSelection: function() {return false},
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIMsgDBViewCommandUpdater) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ }
+}
+
+function HandleColumnClick(columnID)
+{
+ const columnMap = {dateCol: 'byDate',
+ receivedCol: 'byReceived',
+ senderCol: 'byAuthor',
+ recipientCol: 'byRecipient',
+ subjectCol: 'bySubject',
+ locationCol: 'byLocation',
+ accountCol: 'byAccount',
+ unreadButtonColHeader: 'byUnread',
+ statusCol: 'byStatus',
+ sizeCol: 'bySize',
+ priorityCol: 'byPriority',
+ flaggedCol: 'byFlagged',
+ threadCol: 'byThread',
+ tagsCol: 'byTags',
+ junkStatusCol: 'byJunkStatus',
+ idCol: 'byId',
+ attachmentCol: 'byAttachments'};
+
+
+ var sortType;
+ if (columnID in columnMap) {
+ sortType = columnMap[columnID];
+ } else {
+ // If the column isn't in the map, check and see if it's a custom column
+ try {
+ // try to grab the columnHandler (an error is thrown if it does not exist)
+ columnHandler = gDBView.getColumnHandler(columnID);
+
+ // it exists - save this column ID in the customSortCol property of
+ // dbFolderInfo for later use (see nsIMsgDBView.cpp)
+ gDBView.db.dBFolderInfo.setProperty('customSortCol', columnID);
+
+ sortType = "byCustom";
+ } catch(err) {
+ dump("unsupported sort column: " + columnID + " - no custom handler installed. (Error was: " + err + ")\n");
+ return; // bail out
+ }
+ }
+
+ var dbview = GetDBView();
+ var simpleColumns = false;
+ try {
+ simpleColumns = !Services.prefs.getBoolPref("mailnews.thread_pane_column_unthreads");
+ }
+ catch (ex) {
+ }
+ if (sortType == "byThread") {
+ if (simpleColumns)
+ MsgToggleThreaded();
+ else if (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)
+ MsgReverseSortThreadPane();
+ else
+ MsgSortByThread();
+ }
+ else {
+ if (!simpleColumns && (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)) {
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kThreadedDisplay;
+ MsgSortThreadPane(sortType);
+ }
+ else if (dbview.sortType == nsMsgViewSortType[sortType]) {
+ MsgReverseSortThreadPane();
+ }
+ else {
+ MsgSortThreadPane(sortType);
+ }
+ }
+}
+
+function ThreadPaneDoubleClick(event) {
+ if (IsSpecialFolderSelected(Ci.nsMsgFolderFlags.Drafts, true))
+ {
+ MsgComposeDraftMessage();
+ }
+ else if (IsSpecialFolderSelected(Ci.nsMsgFolderFlags.Templates, true))
+ {
+ ComposeMsgByType(Ci.nsIMsgCompType.Template, null,
+ Ci.nsIMsgCompFormat.Default);
+ }
+ else if (AllowOpenTabOnDoubleClick() &&
+ document.documentElement.id != "searchMailWindow")
+ { // we don't allow new tabs in the search dialog
+ // open the message in a new tab on double click
+ OpenMessageInNewTab(event);
+ RestoreSelectionWithoutContentLoad(GetThreadTree());
+ }
+ else
+ {
+ MsgOpenSelectedMessages();
+ }
+}
+
+function ThreadPaneKeyPress(event)
+{
+ if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
+ if ((AppConstants.platform == "macosx" ? event.metaKey : event.ctrlKey) &&
+ AllowOpenTabOnMiddleClick()) {
+ OpenMessageInNewTab(event);
+ } else {
+ ThreadPaneDoubleClick(event);
+ }
+ }
+}
+
+function MsgSortByThread()
+{
+ var dbview = GetDBView();
+ dbview.viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ MsgSortThreadPane('byDate');
+}
+
+function MsgSortThreadPane(sortName)
+{
+ var sortType = nsMsgViewSortType[sortName];
+ var dbview = GetDBView();
+
+ // turn off grouping
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+
+ dbview.sort(sortType, nsMsgViewSortOrder.ascending);
+ UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+}
+
+function MsgReverseSortThreadPane()
+{
+ var dbview = GetDBView();
+ if (dbview.sortOrder == nsMsgViewSortOrder.ascending) {
+ MsgSortDescending();
+ }
+ else {
+ MsgSortAscending();
+ }
+}
+
+function MsgToggleThreaded()
+{
+ var dbview = GetDBView();
+ var newViewFlags = dbview.viewFlags ^ nsMsgViewFlagsType.kThreadedDisplay;
+ newViewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ dbview.viewFlags = newViewFlags;
+
+ dbview.sort(dbview.sortType, dbview.sortOrder);
+ UpdateSortIndicators(dbview.sortType, dbview.sortOrder);
+}
+
+function MsgSortThreaded()
+{
+ var dbview = GetDBView();
+ var viewFlags = dbview.viewFlags;
+ let wasGrouped = viewFlags & nsMsgViewFlagsType.kGroupBySort;
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ // if we were grouped, and not a saved search, just rebuild the view
+ if (wasGrouped && !(gMsgFolderSelected.flags &
+ Ci.nsMsgFolderFlags.Virtual))
+ SwitchView("cmd_viewAllMsgs");
+ // Toggle if not already threaded.
+ else if ((viewFlags & nsMsgViewFlagsType.kThreadedDisplay) == 0)
+ MsgToggleThreaded();
+}
+
+function MsgGroupBySort()
+{
+ var dbview = GetDBView();
+ var viewFlags = dbview.viewFlags;
+ var sortOrder = dbview.sortOrder;
+ var sortType = dbview.sortType;
+ var count = new Object;
+ var msgFolder = dbview.msgFolder;
+
+ var sortTypeSupportsGrouping = (sortType == nsMsgViewSortType.byAuthor
+ || sortType == nsMsgViewSortType.byDate || sortType == nsMsgViewSortType.byReceived || sortType == nsMsgViewSortType.byPriority
+ || sortType == nsMsgViewSortType.bySubject || sortType == nsMsgViewSortType.byTags
+ || sortType == nsMsgViewSortType.byStatus || sortType == nsMsgViewSortType.byRecipient
+ || sortType == nsMsgViewSortType.byAccount || sortType == nsMsgViewSortType.byFlagged
+ || sortType == nsMsgViewSortType.byAttachments);
+
+ if (!sortTypeSupportsGrouping)
+ return; // we shouldn't be trying to group something we don't support grouping for...
+
+ viewFlags |= nsMsgViewFlagsType.kThreadedDisplay | nsMsgViewFlagsType.kGroupBySort;
+ if (gDBView &&
+ gMsgFolderSelected.flags & Ci.nsMsgFolderFlags.Virtual)
+ {
+ gDBView.viewFlags = viewFlags;
+ UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+ return;
+ }
+ // null this out, so we don't try sort.
+ if (gDBView) {
+ gDBView.close();
+ gDBView = null;
+ }
+ gDBView = Cc["@mozilla.org/messenger/msgdbview;1?type=group"]
+ .createInstance(Ci.nsIMsgDBView);
+
+ if (!gThreadPaneCommandUpdater)
+ gThreadPaneCommandUpdater = new nsMsgDBViewCommandUpdater();
+
+
+ gDBView.init(messenger, msgWindow, gThreadPaneCommandUpdater);
+ gDBView.open(msgFolder, sortType, sortOrder, viewFlags, count);
+ RerootThreadPane();
+ UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+ Services.obs.notifyObservers(msgFolder, "MsgCreateDBView",
+ Ci.nsMsgViewType.eShowAllThreads + ":" + viewFlags);
+}
+
+function MsgSortUnthreaded()
+{
+ // Toggle if not already unthreaded.
+ if ((GetDBView().viewFlags & nsMsgViewFlagsType.kThreadedDisplay) != 0)
+ MsgToggleThreaded();
+}
+
+function MsgSortAscending()
+{
+ var dbview = GetDBView();
+ dbview.sort(dbview.sortType, nsMsgViewSortOrder.ascending);
+ UpdateSortIndicators(dbview.sortType, nsMsgViewSortOrder.ascending);
+}
+
+function MsgSortDescending()
+{
+ var dbview = GetDBView();
+ dbview.sort(dbview.sortType, nsMsgViewSortOrder.descending);
+ UpdateSortIndicators(dbview.sortType, nsMsgViewSortOrder.descending);
+}
+
+function groupedBySortUsingDummyRow()
+{
+ return (gDBView.viewFlags & nsMsgViewFlagsType.kGroupBySort) &&
+ (gDBView.sortType != nsMsgViewSortType.bySubject);
+}
+
+function UpdateSortIndicators(sortType, sortOrder)
+{
+ // Remove the sort indicator from all the columns
+ var treeColumns = document.getElementById('threadCols').childNodes;
+ for (var i = 0; i < treeColumns.length; i++)
+ treeColumns[i].removeAttribute('sortDirection');
+
+ // show the twisties if the view is threaded
+ var threadCol = document.getElementById("threadCol");
+ var sortedColumn;
+ // set the sort indicator on the column we are sorted by
+ var colID = ConvertSortTypeToColumnID(sortType);
+ if (colID)
+ sortedColumn = document.getElementById(colID);
+
+ var dbview = GetDBView();
+ var currCol = dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort
+ ? sortedColumn : document.getElementById("subjectCol");
+
+ if (dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort)
+ {
+ var threadTree = document.getElementById("threadTree");
+ var subjectCol = document.getElementById("subjectCol");
+
+ if (groupedBySortUsingDummyRow())
+ {
+ currCol.removeAttribute("primary");
+ subjectCol.setAttribute("primary", "true");
+ }
+
+ // hide the threaded column when in grouped view since you can't do
+ // threads inside of a group.
+ document.getElementById("threadCol").collapsed = true;
+ }
+
+ // clear primary attribute from group column if going to a non-grouped view.
+ if (!(dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort))
+ document.getElementById("threadCol").collapsed = false;
+
+ if ((dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay) && !groupedBySortUsingDummyRow()) {
+ threadCol.setAttribute("sortDirection", "ascending");
+ currCol.setAttribute("primary", "true");
+ }
+ else {
+ threadCol.removeAttribute("sortDirection");
+ currCol.removeAttribute("primary");
+ }
+
+ if (sortedColumn) {
+ if (sortOrder == nsMsgViewSortOrder.ascending) {
+ sortedColumn.setAttribute("sortDirection","ascending");
+ }
+ else {
+ sortedColumn.setAttribute("sortDirection","descending");
+ }
+ }
+}
+
+function IsSpecialFolderSelected(flags, checkAncestors)
+{
+ var folder = GetThreadPaneFolder();
+ return folder && folder.isSpecialFolder(flags, checkAncestors);
+}
+
+function GetThreadTree()
+{
+ return document.getElementById("threadTree")
+}
+
+function GetThreadPaneFolder()
+{
+ try {
+ return gDBView.msgFolder;
+ }
+ catch (ex) {
+ return null;
+ }
+}
+
+function EnsureRowInThreadTreeIsVisible(index)
+{
+ if (index < 0)
+ return;
+
+ var tree = GetThreadTree();
+ tree.treeBoxObject.ensureRowIsVisible(index);
+}
+
+function RerootThreadPane()
+{
+ SetNewsFolderColumns();
+
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ if (treeView)
+ {
+ var tree = GetThreadTree();
+ tree.view = treeView;
+ }
+}
+
+function ThreadPaneOnLoad()
+{
+ var tree = GetThreadTree();
+ // We won't have the tree if we're in a message window, so exit silently
+ if (!tree)
+ return;
+
+ tree.addEventListener("click",ThreadPaneOnClick,true);
+
+ // The mousedown event listener below should only be added in the thread
+ // pane of the mailnews 3pane window, not in the advanced search window.
+ if(tree.parentNode.id == "searchResultListBox")
+ return;
+
+ tree.addEventListener("mousedown",TreeOnMouseDown,true);
+ var delay = Services.prefs.getIntPref("mailnews.threadpane_select_delay");
+ document.getElementById("threadTree")._selectDelay = delay;
+}
+
+function ThreadPaneSelectionChanged()
+{
+ UpdateStatusMessageCounts(gMsgFolderSelected);
+ if (!gRightMouseButtonDown)
+ GetThreadTree().view.selectionChanged();
+}
+
+var ThreadPaneDND = {
+ onDragStart(aEvent) {
+ if (aEvent.originalTarget.localName != "treechildren")
+ return;
+
+ let messageUris = gFolderDisplay.selectedMessageUris;
+ if (!messageUris)
+ return;
+
+ // A message can be dragged from one window and dropped on another window.
+ // Therefore we setNextMessageAfterDelete() here since there is no major
+ // disadvantage, even if it is a copy operation.
+ SetNextMessageAfterDelete();
+ let messengerBundle = document.getElementById("bundle_messenger");
+ let noSubject = messengerBundle.getString("defaultSaveMessageAsFileName");
+ if (noSubject.endsWith(".eml")) {
+ noSubject = noSubject.slice(0, -4);
+ }
+ let fileNames = [];
+ let dataTransfer = aEvent.dataTransfer;
+
+ for (let [index, msgUri] of messageUris.entries()) {
+ let msgService = messenger.messageServiceFromURI(msgUri);
+ let msgHdr = msgService.messageURIToMsgHdr(msgUri);
+ let subject = msgHdr.mime2DecodedSubject || noSubject;
+ if (msgHdr.flags & Ci.nsMsgMessageFlags.HasRe) {
+ subject = "Re: " + subject;
+ }
+ let uniqueFileName = suggestUniqueFileName(subject.substr(0, 120), ".eml",
+ fileNames);
+ fileNames[index] = uniqueFileName;
+ let msgUrl = {};
+ msgService.GetUrlForUri(msgUri, msgUrl, null);
+ dataTransfer.mozSetDataAt("text/x-moz-message", msgUri, index);
+ dataTransfer.mozSetDataAt("text/x-moz-url", msgUrl.value.spec, index);
+ dataTransfer.mozSetDataAt("application/x-moz-file-promise-url",
+ msgUrl.value.spec + "?fileName=" +
+ encodeURIComponent(uniqueFileName),
+ index);
+ dataTransfer.mozSetDataAt("application/x-moz-file-promise",
+ new messageFlavorDataProvider(), index);
+ }
+ dataTransfer.effectAllowed = "copyMove";
+ dataTransfer.addElement(aEvent.originalTarget);
+ },
+
+ onDragOver(aEvent) {
+ if (!gMsgFolderSelected.canFileMessages ||
+ gMsgFolderSelected.server.type == "rss")
+ return;
+ let dt = aEvent.dataTransfer;
+ dt.effectAllowed = "copy";
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ if (Array.from(dt.mozTypesAt(i)).includes("application/x-moz-file")) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i);
+ if (!extFile) {
+ return;
+ }
+
+ extFile = extFile.QueryInterface(Ci.nsIFile);
+ if (extFile.isFile() && /\.eml$/i.test(extFile.leafName)) {
+ aEvent.preventDefault();
+ return;
+ }
+ }
+ }
+ },
+
+ onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i);
+ if (!extFile) {
+ continue;
+ }
+
+ extFile = extFile.QueryInterface(Ci.nsIFile);
+ if (extFile.isFile() && /\.eml$/i.test(extFile.leafName))
+ MailServices.copy.CopyFileMessage(extFile, gMsgFolderSelected, null,
+ false, 1, "", null, msgWindow);
+ }
+ },
+}
+
+function messageFlavorDataProvider() {}
+
+messageFlavorDataProvider.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFlavorDataProvider"]),
+
+ getFlavorData(aTransferable, aFlavor, aData, aDataLen) {
+ if (aFlavor !== "application/x-moz-file-promise") {
+ return;
+ }
+ let fileUriPrimitive = {};
+ let dataSize = {};
+ aTransferable.getTransferData("application/x-moz-file-promise-url",
+ fileUriPrimitive, dataSize);
+
+ let fileUriStr = fileUriPrimitive.value
+ .QueryInterface(Ci.nsISupportsString);
+ let fileUri = Services.io.newURI(fileUriStr.data);
+ let fileUrl = fileUri.QueryInterface(Ci.nsIURL);
+ let fileName = fileUrl.fileName;
+
+ let destDirPrimitive = {};
+ aTransferable.getTransferData("application/x-moz-file-promise-dir",
+ destDirPrimitive, dataSize);
+ let destDirectory = destDirPrimitive.value.QueryInterface(Ci.nsIFile);
+ let file = destDirectory.clone();
+ file.append(fileName);
+
+ let messageUriPrimitive = {};
+ aTransferable.getTransferData("text/x-moz-message", messageUriPrimitive,
+ dataSize);
+ let messageUri = messageUriPrimitive.value
+ .QueryInterface(Ci.nsISupportsString);
+
+ messenger.saveAs(messageUri.data, true, null, decodeURIComponent(file.path),
+ true);
+ },
+};
+
+addEventListener("load",ThreadPaneOnLoad,true);
diff --git a/comm/suite/mailnews/content/threadPane.xul b/comm/suite/mailnews/content/threadPane.xul
new file mode 100644
index 0000000000..c012852967
--- /dev/null
+++ b/comm/suite/mailnews/content/threadPane.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/threadPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/threadPaneExtras.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/threadPaneLabels.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/threadpane.dtd">
+
+<overlay
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://messenger/content/threadPane.js"/>
+
+<tree id="threadTree"
+ persist="width lastfoldersent"
+ flex="1"
+ enableColumnDrag="true"
+ _selectDelay="250"
+ class="plain focusring"
+ disableKeyNavigation="true"
+ lastfoldersent="false"
+ noattachcol="true"
+ onkeypress="ThreadPaneKeyPress(event);"
+ onselect="ThreadPaneSelectionChanged();">
+ <treecols id="threadCols" pickertooltiptext="&columnChooser2.tooltip;">
+ <treecol id="threadCol" persist="hidden ordinal" fixed="true" cycler="true" class="treecol-image threadColumnHeader" currentView="unthreaded"
+ label="&threadColumn.label;" tooltiptext="&threadColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="attachmentCol" persist="hidden ordinal" fixed="true" class="treecol-image attachmentColumnHeader" hidden="true"
+ label="&attachmentColumn.label;" tooltiptext="&attachmentColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="subjectCol" persist="hidden ordinal width" flex="7" ignoreincolumnpicker="true"
+ label="&subjectColumn.label;" tooltiptext="&subjectColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="junkStatusCol" persist="hidden ordinal width" fixed="true" cycler="true" class="treecol-image junkStatusHeader"
+ label="&junkStatusColumn.label;" tooltiptext="&junkStatusColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="senderCol" persist="ordinal width hidden swappedhidden" flex="4" hidden="false" swappedhidden="true"
+ label="&fromColumn.label;" tooltiptext="&fromColumn2.tooltip;"/>
+ <treecol id="recipientCol" persist="ordinal width hidden swappedhidden" flex="4" hidden="true" swappedhidden="false"
+ label="&recipientColumn.label;" tooltiptext="&recipientColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="unreadButtonColHeader" persist="hidden ordinal" fixed="true" cycler="true" class="treecol-image readColumnHeader"
+ label="&readColumn.label;" tooltiptext="&readColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="receivedCol" persist="hidden ordinal width temphidden" flex="2" hidden="true" temphidden="false"
+ label="&receivedColumn.label;" tooltiptext="&receivedColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="dateCol" persist="hidden ordinal width" flex="2"
+ label="&dateColumn.label;" tooltiptext="&dateColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="statusCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&statusColumn.label;" tooltiptext="&statusColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="sizeCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&sizeColumn.label;" tooltiptext="&sizeColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="flaggedCol" persist="hidden ordinal" fixed="true" cycler="true" hidden="true" class="treecol-image flagColumnHeader"
+ label="&flagColumn.label;" tooltiptext="&flagColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="tagsCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&tagsColumn.label;" tooltiptext="&tagsColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="accountCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&accountColumn.label;" tooltiptext="&accountColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="priorityCol" persist="hidden ordinal width" flex="1"
+ label="&priorityColumn.label;" tooltiptext="&priorityColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="unreadCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&unreadColumn.label;" tooltiptext="&unreadColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="totalCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&totalColumn.label;" tooltiptext="&totalColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="locationCol" persist="width" flex="1" hidden="true" ignoreincolumnpicker="true"
+ label="&locationColumn.label;" tooltiptext="&locationColumn2.tooltip;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="idCol" persist="hidden ordinal width" flex="1" hidden="true"
+ label="&idColumn.label;" tooltiptext="&idColumn2.tooltip;"/>
+ </treecols>
+ <treechildren ondragstart="ThreadPaneDND.onDragStart(event);"
+ ondragover="ThreadPaneDND.onDragOver(event);"
+ ondrop="ThreadPaneDND.onDrop(event);"/>
+</tree>
+
+</overlay>