summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/components/compose
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/mailnews/components/compose
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/mailnews/components/compose')
-rw-r--r--comm/suite/mailnews/components/compose/content/MsgComposeCommands.js3936
-rw-r--r--comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js1167
-rw-r--r--comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul16
-rw-r--r--comm/suite/mailnews/components/compose/content/messengercompose.xul720
-rw-r--r--comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul23
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js30
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul212
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js151
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul120
-rw-r--r--comm/suite/mailnews/components/compose/jar.mn14
-rw-r--r--comm/suite/mailnews/components/compose/moz.build6
11 files changed, 6395 insertions, 0 deletions
diff --git a/comm/suite/mailnews/components/compose/content/MsgComposeCommands.js b/comm/suite/mailnews/components/compose/content/MsgComposeCommands.js
new file mode 100644
index 0000000000..48f7f31b1a
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/MsgComposeCommands.js
@@ -0,0 +1,3936 @@
+/* -*- 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 {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm");
+const {FolderUtils} = ChromeUtils.import("resource:///modules/FolderUtils.jsm");
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+/**
+ * interfaces
+ */
+var nsIMsgCompDeliverMode = Ci.nsIMsgCompDeliverMode;
+var nsIMsgCompSendFormat = Ci.nsIMsgCompSendFormat;
+var nsIMsgCompConvertible = Ci.nsIMsgCompConvertible;
+var nsIMsgCompType = Ci.nsIMsgCompType;
+var nsIMsgCompFormat = Ci.nsIMsgCompFormat;
+var nsIAbPreferMailFormat = Ci.nsIAbPreferMailFormat;
+var mozISpellCheckingEngine = Ci.mozISpellCheckingEngine;
+
+/**
+ * In order to distinguish clearly globals that are initialized once when js load (static globals) and those that need to be
+ * initialize every time a compose window open (globals), I (ducarroz) have decided to prefix by s... the static one and
+ * by g... the other one. Please try to continue and repect this rule in the future. Thanks.
+ */
+/**
+ * static globals, need to be initialized only once
+ */
+var sComposeMsgsBundle;
+var sBrandBundle;
+
+var sRDF = null;
+var sNameProperty = null;
+var sDictCount = 0;
+
+/**
+ * Global message window object. This is used by mail-offline.js and therefore
+ * should not be renamed. We need to avoid doing this kind of cross file global
+ * stuff in the future and instead pass this object as parameter when needed by
+ * functions in the other js file.
+ */
+var msgWindow;
+
+var gMessenger;
+
+/**
+ * Global variables, need to be re-initialized every time mostly because
+ * we need to release them when the window closes.
+ */
+var gHideMenus;
+var gMsgCompose;
+var gOriginalMsgURI;
+var gWindowLocked;
+var gSendLocked;
+var gContentChanged;
+var gAutoSaving;
+var gCurrentIdentity;
+var defaultSaveOperation;
+var gSendOrSaveOperationInProgress;
+var gCloseWindowAfterSave;
+var gSavedSendNowKey;
+var gSendFormat;
+var gLogComposePerformance;
+
+var gMsgIdentityElement;
+var gMsgAddressingWidgetElement;
+var gMsgSubjectElement;
+var gMsgAttachmentElement;
+var gMsgHeadersToolbarElement;
+var gComposeType;
+var gFormatToolbarHidden = false;
+var gBodyFromArgs;
+
+// i18n globals
+var gCharsetConvertManager;
+
+var gLastWindowToHaveFocus;
+var gReceiptOptionChanged;
+var gDSNOptionChanged;
+var gAttachVCardOptionChanged;
+
+var gAutoSaveInterval;
+var gAutoSaveTimeout;
+var gAutoSaveKickedIn;
+var gEditingDraft;
+
+var kComposeAttachDirPrefName = "mail.compose.attach.dir";
+
+function InitializeGlobalVariables()
+{
+ gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+ gMsgCompose = null;
+ gOriginalMsgURI = null;
+ gWindowLocked = false;
+ gContentChanged = false;
+ gCurrentIdentity = null;
+ defaultSaveOperation = "draft";
+ gSendOrSaveOperationInProgress = false;
+ gAutoSaving = false;
+ gCloseWindowAfterSave = false;
+ gSavedSendNowKey = null;
+ gSendFormat = nsIMsgCompSendFormat.AskUser;
+ gCharsetConvertManager = Cc['@mozilla.org/charset-converter-manager;1'].getService(Ci.nsICharsetConverterManager);
+ gHideMenus = false;
+ // We are storing the value of the bool logComposePerformance inorder to
+ // avoid logging unnecessarily.
+ gLogComposePerformance = MailServices.compose.logComposePerformance;
+
+ gLastWindowToHaveFocus = null;
+ gReceiptOptionChanged = false;
+ gDSNOptionChanged = false;
+ gAttachVCardOptionChanged = false;
+ msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(Ci.nsIMsgWindow);
+ MailServices.mailSession.AddMsgWindow(msgWindow);
+}
+InitializeGlobalVariables();
+
+function ReleaseGlobalVariables()
+{
+ gCurrentIdentity = null;
+ gCharsetConvertManager = null;
+ gMsgCompose = null;
+ gOriginalMsgURI = null;
+ gMessenger = null;
+ sComposeMsgsBundle = null;
+ sBrandBundle = null;
+ MailServices.mailSession.RemoveMsgWindow(msgWindow);
+ msgWindow = null;
+}
+
+function disableEditableFields()
+{
+ gMsgCompose.editor.flags |= Ci.nsIEditor.eEditorReadonlyMask;
+ var disableElements = document.getElementsByAttribute("disableonsend", "true");
+ for (let i = 0; i < disableElements.length; i++)
+ disableElements[i].setAttribute('disabled', 'true');
+
+}
+
+function enableEditableFields()
+{
+ gMsgCompose.editor.flags &= ~Ci.nsIEditor.eEditorReadonlyMask;
+ var enableElements = document.getElementsByAttribute("disableonsend", "true");
+ for (let i = 0; i < enableElements.length; i++)
+ enableElements[i].removeAttribute('disabled');
+
+}
+
+/**
+ * Small helper function to check whether the node passed in is a signature.
+ * Note that a text node is not a DOM element, hence .localName can't be used.
+ */
+function isSignature(aNode) {
+ return ["DIV","PRE"].includes(aNode.nodeName) &&
+ aNode.classList.contains("moz-signature");
+}
+
+var stateListener = {
+ NotifyComposeFieldsReady: function() {
+ ComposeFieldsReady();
+ updateSendCommands(true);
+ },
+
+ NotifyComposeBodyReady: function() {
+ this.useParagraph = gMsgCompose.composeHTML &&
+ Services.prefs.getBoolPref("mail.compose.default_to_paragraph");
+ this.editor = GetCurrentEditor();
+ this.paragraphState = document.getElementById("cmd_paragraphState");
+
+ // Look at the compose types which require action (nsIMsgComposeParams.idl):
+ switch (gComposeType) {
+
+ case Ci.nsIMsgCompType.MailToUrl:
+ gBodyFromArgs = true;
+ case Ci.nsIMsgCompType.New:
+ case Ci.nsIMsgCompType.NewsPost:
+ case Ci.nsIMsgCompType.ForwardAsAttachment:
+ this.NotifyComposeBodyReadyNew();
+ break;
+
+ case Ci.nsIMsgCompType.Reply:
+ case Ci.nsIMsgCompType.ReplyAll:
+ case Ci.nsIMsgCompType.ReplyToSender:
+ case Ci.nsIMsgCompType.ReplyToGroup:
+ case Ci.nsIMsgCompType.ReplyToSenderAndGroup:
+ case Ci.nsIMsgCompType.ReplyWithTemplate:
+ case Ci.nsIMsgCompType.ReplyToList:
+ this.NotifyComposeBodyReadyReply();
+ break;
+
+ case Ci.nsIMsgCompType.ForwardInline:
+ this.NotifyComposeBodyReadyForwardInline();
+ break;
+
+ case Ci.nsIMsgCompType.EditTemplate:
+ defaultSaveOperation = "template";
+ case Ci.nsIMsgCompType.Draft:
+ case Ci.nsIMsgCompType.Template:
+ case Ci.nsIMsgCompType.Redirect:
+ case Ci.nsIMsgCompType.EditAsNew:
+ break;
+
+ default:
+ dump("Unexpected nsIMsgCompType in NotifyComposeBodyReady (" +
+ gComposeType + ")\n");
+ }
+
+ // Set the selected item in the identity list as needed, which will cause
+ // an identity/signature switch. This can only be done once the message
+ // body has already been assembled with the signature we need to switch.
+ if (gMsgCompose.identity != gCurrentIdentity) {
+ // Since switching the signature loses the caret position, we record it
+ // and restore it later.
+ let selection = this.editor.selection;
+ let range = selection.getRangeAt(0);
+ let start = range.startOffset;
+ let startNode = range.startContainer;
+
+ this.editor.enableUndo(false);
+ let identityList = GetMsgIdentityElement();
+ identityList.selectedItem = identityList.getElementsByAttribute(
+ "identitykey", gMsgCompose.identity.key)[0];
+ LoadIdentity(false);
+
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ selection.collapse(startNode, start);
+ }
+
+ if (gMsgCompose.composeHTML)
+ loadHTMLMsgPrefs();
+ AdjustFocus();
+ },
+
+ NotifyComposeBodyReadyNew: function() {
+ let insertParagraph = this.useParagraph;
+
+ let mailDoc = document.getElementById("content-frame").contentDocument;
+ let mailBody = mailDoc.querySelector("body");
+ if (insertParagraph && gBodyFromArgs) {
+ // Check for "empty" body before allowing paragraph to be inserted.
+ // Non-empty bodies in a new message can occur when clicking on a
+ // mailto link or when using the command line option -compose.
+ // An "empty" body can be one of these two cases:
+ // 1) <br> and nothing follows (no next sibling)
+ // 2) <div/pre class="moz-signature">
+ // Note that <br><div/pre class="moz-signature"> doesn't happen in
+ // paragraph mode.
+ let firstChild = mailBody.firstChild;
+ if ((firstChild.nodeName != "BR" || firstChild.nextSibling) &&
+ !isSignature(firstChild))
+ insertParagraph = false;
+ }
+
+ // Control insertion of line breaks.
+ if (insertParagraph) {
+ this.editor.enableUndo(false);
+
+ this.editor.selection.collapse(mailBody, 0);
+ let pElement = this.editor.createElementWithDefaults("p");
+ let brElement = this.editor.createElementWithDefaults("br");
+ pElement.appendChild(brElement);
+ this.editor.insertElementAtSelection(pElement, false);
+
+ this.paragraphState.setAttribute("state", "p");
+
+ this.editor.beginningOfDocument();
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ } else {
+ this.paragraphState.setAttribute("state", "");
+ }
+ },
+
+ NotifyComposeBodyReadyReply: function() {
+ // Control insertion of line breaks.
+ if (this.useParagraph) {
+ let mailDoc = document.getElementById("content-frame").contentDocument;
+ let mailBody = mailDoc.querySelector("body");
+ let selection = this.editor.selection;
+
+ // Make sure the selection isn't inside the signature.
+ if (isSignature(mailBody.firstChild))
+ selection.collapse(mailBody, 0);
+
+ let range = selection.getRangeAt(0);
+ let start = range.startOffset;
+
+ if (start != range.endOffset) {
+ // The selection is not collapsed, most likely due to the
+ // "select the quote" option. In this case we do nothing.
+ return;
+ }
+
+ if (range.startContainer != mailBody) {
+ dump("Unexpected selection in NotifyComposeBodyReadyReply\n");
+ return;
+ }
+
+ this.editor.enableUndo(false);
+
+ let pElement = this.editor.createElementWithDefaults("p");
+ let brElement = this.editor.createElementWithDefaults("br");
+ pElement.appendChild(brElement);
+ this.editor.insertElementAtSelection(pElement, false);
+
+ // Position into the paragraph.
+ selection.collapse(pElement, 0);
+
+ this.paragraphState.setAttribute("state", "p");
+
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ } else {
+ this.paragraphState.setAttribute("state", "");
+ }
+ },
+
+ NotifyComposeBodyReadyForwardInline: function() {
+ let mailDoc = document.getElementById("content-frame").contentDocument;
+ let mailBody = mailDoc.querySelector("body");
+ let selection = this.editor.selection;
+
+ this.editor.enableUndo(false);
+
+ // Control insertion of line breaks.
+ selection.collapse(mailBody, 0);
+ if (this.useParagraph) {
+ let pElement = this.editor.createElementWithDefaults("p");
+ let brElement = this.editor.createElementWithDefaults("br");
+ pElement.appendChild(brElement);
+ this.editor.insertElementAtSelection(pElement, false);
+ this.paragraphState.setAttribute("state", "p");
+ } else {
+ // insertLineBreak() has been observed to insert two <br> elements
+ // instead of one before a <div>, so we'll do it ourselves here.
+ let brElement = this.editor.createElementWithDefaults("br");
+ this.editor.insertElementAtSelection(brElement, false);
+ this.paragraphState.setAttribute("state", "");
+ }
+
+ this.editor.beginningOfDocument();
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ },
+
+ ComposeProcessDone: function(aResult) {
+ gWindowLocked = false;
+ enableEditableFields();
+ updateComposeItems();
+
+ if (aResult== Cr.NS_OK)
+ {
+ if (!gAutoSaving)
+ SetContentAndBodyAsUnmodified();
+
+ if (gCloseWindowAfterSave)
+ {
+ // Notify the SendListener that Send has been aborted and Stopped
+ if (gMsgCompose)
+ gMsgCompose.onSendNotPerformed(null, Cr.NS_ERROR_ABORT);
+
+ MsgComposeCloseWindow();
+ }
+ }
+ // else if we failed to save, and we're autosaving, need to re-mark the editor
+ // as changed, so that we won't lose the changes.
+ else if (gAutoSaving)
+ {
+ gMsgCompose.bodyModified = true;
+ gContentChanged = true;
+ }
+
+ gAutoSaving = false;
+ gCloseWindowAfterSave = false;
+ },
+
+ SaveInFolderDone: function(folderURI) {
+ DisplaySaveFolderDlg(folderURI);
+ }
+};
+
+// all progress notifications are done through the nsIWebProgressListener implementation...
+var progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START)
+ {
+ document.getElementById('navigator-throbber').setAttribute("busy", "true");
+ document.getElementById('compose-progressmeter').setAttribute( "mode", "undetermined" );
+ document.getElementById("statusbar-progresspanel").collapsed = false;
+ }
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)
+ {
+ gSendOrSaveOperationInProgress = false;
+ document.getElementById('navigator-throbber').removeAttribute("busy");
+ document.getElementById('compose-progressmeter').setAttribute( "mode", "normal" );
+ document.getElementById('compose-progressmeter').setAttribute( "value", 0 );
+ document.getElementById("statusbar-progresspanel").collapsed = true;
+ document.getElementById('statusText').setAttribute('label', '');
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ // Calculate percentage.
+ var percent;
+ if ( aMaxTotalProgress > 0 )
+ {
+ percent = Math.round( (aCurTotalProgress*100)/aMaxTotalProgress );
+ if ( percent > 100 )
+ percent = 100;
+
+ document.getElementById('compose-progressmeter').removeAttribute("mode");
+
+ // Advance progress meter.
+ document.getElementById('compose-progressmeter').setAttribute( "value", percent );
+ }
+ else
+ {
+ // Progress meter should be barber-pole in this case.
+ document.getElementById('compose-progressmeter').setAttribute( "mode", "undetermined" );
+ }
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ // we can ignore this notification
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ // Looks like it's possible that we get call while the document has been already delete!
+ // therefore we need to protect ourself by using try/catch
+ try {
+ let statusText = document.getElementById("statusText");
+ if (statusText)
+ statusText.setAttribute("label", aMessage);
+ } catch (ex) {}
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ // we can ignore this notification
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIWebProgressListener) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ }
+};
+
+var defaultController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command)
+ {
+ //File Menu
+ case "cmd_attachFile":
+ case "cmd_attachPage":
+ case "cmd_close":
+ case "cmd_save":
+ case "cmd_saveAsFile":
+ case "cmd_saveAsDraft":
+ case "cmd_saveAsTemplate":
+ case "cmd_sendButton":
+ case "cmd_sendNow":
+ case "cmd_sendWithCheck":
+ case "cmd_sendLater":
+ case "cmd_printSetup":
+ case "cmd_printpreview":
+ case "cmd_print":
+
+ //Edit Menu
+ case "cmd_account":
+ case "cmd_preferences":
+
+ //Options Menu
+ case "cmd_selectAddress":
+ case "cmd_outputFormat":
+ case "cmd_quoteMessage":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+ isCommandEnabled: function(command)
+ {
+ var composeHTML = gMsgCompose && gMsgCompose.composeHTML;
+
+ switch (command)
+ {
+ //File Menu
+ case "cmd_attachFile":
+ case "cmd_attachPage":
+ case "cmd_close":
+ case "cmd_save":
+ case "cmd_saveAsFile":
+ case "cmd_saveAsDraft":
+ case "cmd_saveAsTemplate":
+ case "cmd_printSetup":
+ case "cmd_printpreview":
+ case "cmd_print":
+ return !gWindowLocked;
+ case "cmd_sendButton":
+ case "cmd_sendLater":
+ case "cmd_sendWithCheck":
+ case "cmd_sendButton":
+ return !gWindowLocked && !gSendLocked;
+ case "cmd_sendNow":
+ return !gWindowLocked && !Services.io.offline && !gSendLocked;
+
+ //Edit Menu
+ case "cmd_account":
+ case "cmd_preferences":
+ return true;
+
+ //Options Menu
+ case "cmd_selectAddress":
+ return !gWindowLocked;
+ case "cmd_outputFormat":
+ return composeHTML;
+ case "cmd_quoteMessage":
+ var selectedURIs = GetSelectedMessages();
+ if (selectedURIs && selectedURIs.length > 0)
+ return true;
+ return false;
+
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ switch (command)
+ {
+ //File Menu
+ case "cmd_attachFile" : if (defaultController.isCommandEnabled(command)) AttachFile(); break;
+ case "cmd_attachPage" : AttachPage(); break;
+ case "cmd_close" : DoCommandClose(); break;
+ case "cmd_save" : Save(); break;
+ case "cmd_saveAsFile" : SaveAsFile(true); break;
+ case "cmd_saveAsDraft" : SaveAsDraft(); break;
+ case "cmd_saveAsTemplate" : SaveAsTemplate(); break;
+ case "cmd_sendButton" :
+ if (defaultController.isCommandEnabled(command))
+ {
+ if (Services.io.offline)
+ SendMessageLater();
+ else
+ SendMessage();
+ }
+ break;
+ case "cmd_sendNow" : if (defaultController.isCommandEnabled(command)) SendMessage(); break;
+ case "cmd_sendWithCheck" : if (defaultController.isCommandEnabled(command)) SendMessageWithCheck(); break;
+ case "cmd_sendLater" : if (defaultController.isCommandEnabled(command)) SendMessageLater(); break;
+ case "cmd_printSetup" : PrintUtils.showPageSetup(); break;
+ case "cmd_printpreview" : PrintUtils.printPreview(PrintPreviewListener); break;
+ case "cmd_print" :
+ let browser = GetCurrentEditorElement();
+ PrintUtils.printWindow(browser.outerWindowID, browser);
+ break;
+
+ //Edit Menu
+ case "cmd_account" :
+ let currentAccountKey = getCurrentAccountKey();
+ let account = MailServices.accounts.getAccount(currentAccountKey);
+ MsgAccountManager(null, account.incomingServer);
+ break;
+ case "cmd_preferences" : DoCommandPreferences(); break;
+
+ //Options Menu
+ case "cmd_selectAddress" : if (defaultController.isCommandEnabled(command)) SelectAddress(); break;
+ case "cmd_quoteMessage" : if (defaultController.isCommandEnabled(command)) QuoteSelectedMessage(); break;
+ default:
+ return;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ }
+};
+
+var gAttachmentBucketController =
+{
+ supportsCommand: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_delete":
+ case "cmd_renameAttachment":
+ case "cmd_selectAll":
+ case "cmd_openAttachment":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_delete":
+ return MessageGetNumSelectedAttachments() > 0;
+ case "cmd_renameAttachment":
+ return MessageGetNumSelectedAttachments() == 1;
+ case "cmd_selectAll":
+ return MessageHasAttachments();
+ case "cmd_openAttachment":
+ return MessageGetNumSelectedAttachments() == 1;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_delete":
+ if (MessageGetNumSelectedAttachments() > 0)
+ RemoveSelectedAttachment();
+ break;
+ case "cmd_renameAttachment":
+ if (MessageGetNumSelectedAttachments() == 1)
+ RenameSelectedAttachment();
+ break;
+ case "cmd_selectAll":
+ if (MessageHasAttachments())
+ SelectAllAttachments();
+ break;
+ case "cmd_openAttachment":
+ if (MessageGetNumSelectedAttachments() == 1)
+ OpenSelectedAttachment();
+ break;
+ default:
+ return;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ }
+};
+
+function QuoteSelectedMessage()
+{
+ var selectedURIs = GetSelectedMessages();
+ if (selectedURIs)
+ for (let i = 0; i < selectedURIs.length; i++)
+ gMsgCompose.quoteMessage(selectedURIs[i]);
+}
+
+function GetSelectedMessages()
+{
+ var mailWindow = gMsgCompose && Services.wm.getMostRecentWindow("mail:3pane");
+ return mailWindow && mailWindow.gFolderDisplay.selectedMessageUris;
+}
+
+function SetupCommandUpdateHandlers()
+{
+ top.controllers.appendController(defaultController);
+
+ let attachmentBucket = document.getElementById("attachmentBucket");
+ attachmentBucket.controllers.appendController(gAttachmentBucketController);
+
+ document.getElementById("optionsMenuPopup")
+ .addEventListener("popupshowing", updateOptionItems, true);
+}
+
+function UnloadCommandUpdateHandlers()
+{
+ document.getElementById("optionsMenuPopup")
+ .removeEventListener("popupshowing", updateOptionItems, true);
+
+ top.controllers.removeController(defaultController);
+
+ let attachmentBucket = document.getElementById("attachmentBucket");
+ attachmentBucket.controllers.removeController(gAttachmentBucketController);
+}
+
+function CommandUpdate_MsgCompose()
+{
+ var focusedWindow = top.document.commandDispatcher.focusedWindow;
+
+ // we're just setting focus to where it was before
+ if (focusedWindow == gLastWindowToHaveFocus) {
+ return;
+ }
+
+ gLastWindowToHaveFocus = focusedWindow;
+
+ updateComposeItems();
+}
+
+function updateComposeItems()
+{
+ try {
+ // Edit Menu
+ goUpdateCommand("cmd_rewrap");
+
+ // Insert Menu
+ if (gMsgCompose && gMsgCompose.composeHTML)
+ {
+ goUpdateCommand("cmd_renderedHTMLEnabler");
+ goUpdateCommand("cmd_decreaseFontStep");
+ goUpdateCommand("cmd_increaseFontStep");
+ goUpdateCommand("cmd_bold");
+ goUpdateCommand("cmd_italic");
+ goUpdateCommand("cmd_underline");
+ goUpdateCommand("cmd_ul");
+ goUpdateCommand("cmd_ol");
+ goUpdateCommand("cmd_indent");
+ goUpdateCommand("cmd_outdent");
+ goUpdateCommand("cmd_align");
+ goUpdateCommand("cmd_smiley");
+ }
+
+ // Options Menu
+ goUpdateCommand("cmd_spelling");
+ } catch(e) {}
+}
+
+function openEditorContextMenu(popup)
+{
+ gContextMenu = new nsContextMenu(popup);
+ if (gContextMenu.shouldDisplay)
+ {
+ // If message body context menu then focused element should be content.
+ var showPasteExtra =
+ top.document.commandDispatcher.focusedWindow == content;
+ gContextMenu.showItem("context-pasteNoFormatting", showPasteExtra);
+ gContextMenu.showItem("context-pasteQuote", showPasteExtra);
+ if (showPasteExtra)
+ {
+ goUpdateCommand("cmd_pasteNoFormatting");
+ goUpdateCommand("cmd_pasteQuote");
+ }
+ return true;
+ }
+ return false;
+}
+
+function updateEditItems()
+{
+ goUpdateCommand("cmd_pasteNoFormatting");
+ goUpdateCommand("cmd_pasteQuote");
+ goUpdateCommand("cmd_delete");
+ goUpdateCommand("cmd_renameAttachment");
+ goUpdateCommand("cmd_selectAll");
+ goUpdateCommand("cmd_openAttachment");
+ goUpdateCommand("cmd_findReplace");
+ goUpdateCommand("cmd_find");
+ goUpdateCommand("cmd_findNext");
+ goUpdateCommand("cmd_findPrev");
+}
+
+function updateOptionItems()
+{
+ goUpdateCommand("cmd_quoteMessage");
+}
+
+/**
+ * Update all the commands for sending a message to reflect their current state.
+ */
+function updateSendCommands(aHaveController) {
+ updateSendLock();
+ if (aHaveController) {
+ goUpdateCommand("cmd_sendButton");
+ goUpdateCommand("cmd_sendNow");
+ goUpdateCommand("cmd_sendLater");
+ goUpdateCommand("cmd_sendWithCheck");
+ } else {
+ goSetCommandEnabled("cmd_sendButton",
+ defaultController.isCommandEnabled("cmd_sendButton"));
+ goSetCommandEnabled("cmd_sendNow",
+ defaultController.isCommandEnabled("cmd_sendNow"));
+ goSetCommandEnabled("cmd_sendLater",
+ defaultController.isCommandEnabled("cmd_sendLater"));
+ goSetCommandEnabled("cmd_sendWithCheck",
+ defaultController.isCommandEnabled("cmd_sendWithCheck"));
+ }
+}
+
+var messageComposeOfflineQuitObserver = {
+ observe: function(aSubject, aTopic, aState) {
+ // sanity checks
+ if (aTopic == "network:offline-status-changed")
+ {
+ MessageComposeOfflineStateChanged(aState == "offline");
+ }
+ // check whether to veto the quit request (unless another observer already
+ // did)
+ else if (aTopic == "quit-application-requested" &&
+ aSubject instanceof Ci.nsISupportsPRBool &&
+ !aSubject.data)
+ aSubject.data = !ComposeCanClose();
+ }
+}
+
+function AddMessageComposeOfflineQuitObserver()
+{
+ Services.obs.addObserver(messageComposeOfflineQuitObserver,
+ "network:offline-status-changed");
+ Services.obs.addObserver(messageComposeOfflineQuitObserver,
+ "quit-application-requested");
+
+ // set the initial state of the send button
+ MessageComposeOfflineStateChanged(Services.io.offline);
+}
+
+function RemoveMessageComposeOfflineQuitObserver()
+{
+ Services.obs.removeObserver(messageComposeOfflineQuitObserver,
+ "network:offline-status-changed");
+ Services.obs.removeObserver(messageComposeOfflineQuitObserver,
+ "quit-application-requested");
+}
+
+function MessageComposeOfflineStateChanged(goingOffline)
+{
+ try {
+ var sendButton = document.getElementById("button-send");
+ var sendNowMenuItem = document.getElementById("menu_sendNow");
+
+ if (!gSavedSendNowKey) {
+ gSavedSendNowKey = sendNowMenuItem.getAttribute('key');
+ }
+
+ // don't use goUpdateCommand here ... the defaultController might not be installed yet
+ updateSendCommands(false);
+
+ if (goingOffline)
+ {
+ sendButton.label = sendButton.getAttribute('later_label');
+ sendButton.setAttribute('tooltiptext', sendButton.getAttribute('later_tooltiptext'));
+ sendNowMenuItem.removeAttribute('key');
+ }
+ else
+ {
+ sendButton.label = sendButton.getAttribute('now_label');
+ sendButton.setAttribute('tooltiptext', sendButton.getAttribute('now_tooltiptext'));
+ if (gSavedSendNowKey) {
+ sendNowMenuItem.setAttribute('key', gSavedSendNowKey);
+ }
+ }
+
+ } catch(e) {}
+}
+
+function DoCommandClose()
+{
+ if (ComposeCanClose()) {
+ // Notify the SendListener that Send has been aborted and Stopped
+ if (gMsgCompose)
+ gMsgCompose.onSendNotPerformed(null, Cr.NS_ERROR_ABORT);
+
+ // note: if we're not caching this window, this destroys it for us
+ MsgComposeCloseWindow();
+ }
+
+ return false;
+}
+
+function DoCommandPreferences()
+{
+ goPreferences('composing_messages_pane');
+}
+
+function toggleAffectedChrome(aHide)
+{
+ // chrome to toggle includes:
+ // (*) menubar
+ // (*) toolbox
+ // (*) sidebar
+ // (*) statusbar
+
+ if (!gChromeState)
+ gChromeState = {};
+
+ var statusbar = document.getElementById("status-bar");
+
+ // sidebar states map as follows:
+ // hidden => hide/show nothing
+ // collapsed => hide/show only the splitter
+ // shown => hide/show the splitter and the box
+ if (aHide)
+ {
+ // going into print preview mode
+ gChromeState.sidebar = SidebarGetState();
+ SidebarSetState("hidden");
+
+ // deal with the Status Bar
+ gChromeState.statusbarWasHidden = statusbar.hidden;
+ statusbar.hidden = true;
+ }
+ else
+ {
+ // restoring normal mode (i.e., leaving print preview mode)
+ SidebarSetState(gChromeState.sidebar);
+
+ // restore the Status Bar
+ statusbar.hidden = gChromeState.statusbarWasHidden;
+ }
+
+ // if we are unhiding and sidebar used to be there rebuild it
+ if (!aHide && gChromeState.sidebar == "visible")
+ SidebarRebuild();
+
+ getMailToolbox().hidden = aHide;
+ document.getElementById("appcontent").collapsed = aHide;
+}
+
+var PrintPreviewListener = {
+ getPrintPreviewBrowser()
+ {
+ var browser = document.getElementById("ppBrowser");
+ if (!browser)
+ {
+ browser = document.createElement("browser");
+ browser.setAttribute("id", "ppBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("disablehistory", "true");
+ browser.setAttribute("disablesecurity", "true");
+ browser.setAttribute("type", "content");
+ document.getElementById("sidebar-parent")
+ .insertBefore(browser, document.getElementById("appcontent"));
+ }
+ return browser;
+ },
+ getSourceBrowser()
+ {
+ return GetCurrentEditorElement();
+ },
+ getNavToolbox()
+ {
+ return getMailToolbox();
+ },
+ onEnter()
+ {
+ toggleAffectedChrome(true);
+ },
+ onExit()
+ {
+ document.getElementById("ppBrowser").collapsed = true;
+ toggleAffectedChrome(false);
+ }
+}
+
+function ToggleWindowLock()
+{
+ gWindowLocked = !gWindowLocked;
+ updateComposeItems();
+}
+
+/* This function will go away soon as now arguments are passed to the window using a object of type nsMsgComposeParams instead of a string */
+function GetArgs(originalData)
+{
+ var args = new Object();
+
+ if (originalData == "")
+ return null;
+
+ var data = "";
+ var separator = String.fromCharCode(1);
+
+ var quoteChar = "";
+ var prevChar = "";
+ var nextChar = "";
+ for (let i = 0; i < originalData.length; i++, prevChar = aChar)
+ {
+ var aChar = originalData.charAt(i)
+ var aCharCode = originalData.charCodeAt(i)
+ if ( i < originalData.length - 1)
+ nextChar = originalData.charAt(i + 1);
+ else
+ nextChar = "";
+
+ if (aChar == quoteChar && (nextChar == "," || nextChar == ""))
+ {
+ quoteChar = "";
+ data += aChar;
+ }
+ else if ((aCharCode == 39 || aCharCode == 34) && prevChar == "=") //quote or double quote
+ {
+ if (quoteChar == "")
+ quoteChar = aChar;
+ data += aChar;
+ }
+ else if (aChar == ",")
+ {
+ if (quoteChar == "")
+ data += separator;
+ else
+ data += aChar
+ }
+ else
+ data += aChar
+ }
+
+ var pairs = data.split(separator);
+
+ for (let i = pairs.length - 1; i >= 0; i--)
+ {
+ var pos = pairs[i].indexOf('=');
+ if (pos == -1)
+ continue;
+ var argname = pairs[i].substring(0, pos);
+ var argvalue = pairs[i].substring(pos + 1);
+ if (argvalue.charAt(0) == "'" && argvalue.charAt(argvalue.length - 1) == "'")
+ args[argname] = argvalue.substring(1, argvalue.length - 1);
+ else
+ try {
+ args[argname] = decodeURIComponent(argvalue);
+ } catch (e) {args[argname] = argvalue;}
+ // dump("[" + argname + "=" + args[argname] + "]\n");
+ }
+ return args;
+}
+
+function ComposeFieldsReady()
+{
+ //If we are in plain text, we need to set the wrap column
+ if (! gMsgCompose.composeHTML) {
+ try {
+ gMsgCompose.editor.wrapWidth = gMsgCompose.wrapLength;
+ }
+ catch (e) {
+ dump("### textEditor.wrapWidth exception text: " + e + " - failed\n");
+ }
+ }
+ CompFields2Recipients(gMsgCompose.compFields);
+ SetComposeWindowTitle();
+ enableEditableFields();
+}
+
+// checks if the passed in string is a mailto url, if it is, generates nsIMsgComposeParams
+// for the url and returns them.
+function handleMailtoArgs(mailtoUrl)
+{
+ // see if the string is a mailto url....do this by checking the first 7 characters of the string
+ if (/^mailto:/i.test(mailtoUrl))
+ {
+ // if it is a mailto url, turn the mailto url into a MsgComposeParams object....
+ var uri = Services.io.newURI(mailtoUrl);
+
+ if (uri)
+ return MailServices.compose.getParamsForMailto(uri);
+ }
+
+ return null;
+}
+/**
+ * Handle ESC keypress from composition window for
+ * notifications with close button in the
+ * attachmentNotificationBox.
+ */
+function handleEsc()
+{
+ let activeElement = document.activeElement;
+
+ // If findbar is visible and the focus is in the message body,
+ // hide it. (Focus on the findbar is handled by findbar itself).
+ let findbar = document.getElementById("FindToolbar");
+ if (findbar && !findbar.hidden && activeElement.id == "content-frame") {
+ findbar.close();
+ return;
+ }
+
+ // If there is a notification in the attachmentNotificationBox
+ // AND focus is in message body, subject field or on the notification,
+ // hide it.
+ let notification = document.getElementById("attachmentNotificationBox")
+ .currentNotification;
+ if (notification && (activeElement.id == "content-frame" ||
+ activeElement.parentNode.parentNode.id == "msgSubject" ||
+ notification.contains(activeElement) ||
+ activeElement.classList.contains("messageCloseButton"))) {
+ notification.close();
+ }
+}
+
+/**
+ * On paste or drop, we may want to modify the content before inserting it into
+ * the editor, replacing file URLs with data URLs when appropriate.
+ */
+function onPasteOrDrop(e) {
+ // For paste use e.clipboardData, for drop use e.dataTransfer.
+ let dataTransfer = ("clipboardData" in e) ? e.clipboardData : e.dataTransfer;
+
+ if (!dataTransfer.types.includes("text/html")) {
+ return;
+ }
+
+ if (!gMsgCompose.composeHTML) {
+ // We're in the plain text editor. Nothing to do here.
+ return;
+ }
+
+ let html = dataTransfer.getData("text/html");
+ let doc = (new DOMParser()).parseFromString(html, "text/html");
+ let tmpD = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ let pendingConversions = 0;
+ let needToPreventDefault = true;
+ for (let img of doc.images) {
+ if (!/^file:/i.test(img.src)) {
+ // Doesn't start with file:. Nothing to do here.
+ continue;
+ }
+
+ // This may throw if the URL is invalid for the OS.
+ let nsFile;
+ try {
+ nsFile = Services.io.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler)
+ .getFileFromURLSpec(img.src);
+ } catch (ex) {
+ continue;
+ }
+
+ if (!nsFile.exists()) {
+ continue;
+ }
+
+ if (!tmpD.contains(nsFile)) {
+ // Not anywhere under the temp dir.
+ continue;
+ }
+
+ let contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(nsFile);
+ if (!contentType.startsWith("image/")) {
+ continue;
+ }
+
+ // If we ever get here, we need to prevent the default paste or drop since
+ // the code below will do its own insertion.
+ if (needToPreventDefault) {
+ e.preventDefault();
+ needToPreventDefault = false;
+ }
+
+ File.createFromNsIFile(nsFile).then(function(file) {
+ if (file.lastModified < (Date.now() - 60000)) {
+ // Not put in temp in the last minute. May be something other than
+ // a copy-paste. Let's not allow that.
+ return;
+ }
+
+ let doTheInsert = function() {
+ // Now run it through sanitation to make sure there wasn't any
+ // unwanted things in the content.
+ let ParserUtils = Cc["@mozilla.org/parserutils;1"]
+ .getService(Ci.nsIParserUtils);
+ let html2 = ParserUtils.sanitize(doc.documentElement.innerHTML,
+ ParserUtils.SanitizerAllowStyle);
+ getBrowser().contentDocument.execCommand("insertHTML", false, html2);
+ }
+
+ // Everything checks out. Convert file to data URL.
+ let reader = new FileReader();
+ reader.addEventListener("load", function() {
+ let dataURL = reader.result;
+ pendingConversions--;
+ img.src = dataURL;
+ if (pendingConversions == 0) {
+ doTheInsert();
+ }
+ });
+
+ reader.addEventListener("error", function() {
+ pendingConversions--;
+ if (pendingConversions == 0) {
+ doTheInsert();
+ }
+ });
+
+ pendingConversions++;
+ reader.readAsDataURL(file);
+ });
+ }
+}
+
+function ComposeStartup(aParams)
+{
+ var params = null; // New way to pass parameters to the compose window as a nsIMsgComposeParameters object
+ var args = null; // old way, parameters are passed as a string
+ gBodyFromArgs = false;
+
+ if (aParams)
+ params = aParams;
+ else if (window.arguments && window.arguments[0]) {
+ try {
+ if (window.arguments[0] instanceof Ci.nsIMsgComposeParams)
+ params = window.arguments[0];
+ else
+ params = handleMailtoArgs(window.arguments[0]);
+ }
+ catch(ex) { dump("ERROR with parameters: " + ex + "\n"); }
+
+ // if still no dice, try and see if the params is an old fashioned list of string attributes
+ // XXX can we get rid of this yet?
+ if (!params)
+ {
+ args = GetArgs(window.arguments[0]);
+ }
+ }
+
+ // Set the document language to the preference as early as possible.
+ document.documentElement
+ .setAttribute("lang", Services.prefs.getCharPref("spellchecker.dictionary"));
+
+ var identityList = GetMsgIdentityElement();
+
+ document.addEventListener("paste", onPasteOrDrop);
+ document.addEventListener("drop", onPasteOrDrop);
+
+ if (identityList)
+ FillIdentityList(identityList);
+
+ if (!params) {
+ // This code will go away soon as now arguments are passed to the window
+ // using a object of type nsMsgComposeParams instead of a string.
+ params = Cc["@mozilla.org/messengercompose/composeparams;1"]
+ .createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+
+ if (args) { //Convert old fashion arguments into params
+ var composeFields = params.composeFields;
+ if (args.bodyislink && args.bodyislink == "true")
+ params.bodyIsLink = true;
+ if (args.type)
+ params.type = args.type;
+ if (args.format) {
+ // Only use valid values.
+ if (args.format == Ci.nsIMsgCompFormat.PlainText ||
+ args.format == Ci.nsIMsgCompFormat.HTML ||
+ args.format == Ci.nsIMsgCompFormat.OppositeOfDefault)
+ params.format = args.format;
+ else if (args.format.toLowerCase().trim() == "html")
+ params.format = Ci.nsIMsgCompFormat.HTML;
+ else if (args.format.toLowerCase().trim() == "text")
+ params.format = Ci.nsIMsgCompFormat.PlainText;
+ }
+ if (args.originalMsgURI)
+ params.originalMsgURI = args.originalMsgURI;
+ if (args.preselectid)
+ params.identity = getIdentityForKey(args.preselectid);
+ if (args.from)
+ composeFields.from = args.from;
+ if (args.to)
+ composeFields.to = args.to;
+ if (args.cc)
+ composeFields.cc = args.cc;
+ if (args.bcc)
+ composeFields.bcc = args.bcc;
+ if (args.newsgroups)
+ composeFields.newsgroups = args.newsgroups;
+ if (args.subject)
+ composeFields.subject = args.subject;
+ if (args.attachment)
+ {
+ var attachmentList = args.attachment.split(",");
+ var commandLine = Cc["@mozilla.org/toolkit/command-line;1"]
+ .createInstance();
+ for (let i = 0; i < attachmentList.length; i++)
+ {
+ let attachmentStr = attachmentList[i];
+ let uri = commandLine.resolveURI(attachmentStr);
+ let attachment = Cc["@mozilla.org/messengercompose/attachment;1"]
+ .createInstance(Ci.nsIMsgAttachment);
+
+ if (uri instanceof Ci.nsIFileURL)
+ {
+ if (uri.file.exists())
+ attachment.size = uri.file.fileSize;
+ else
+ attachment = null;
+ }
+
+ // Only want to attach if a file that exists or it is not a file.
+ if (attachment)
+ {
+ attachment.url = uri.spec;
+ composeFields.addAttachment(attachment);
+ }
+ else
+ {
+ let title = sComposeMsgsBundle.getString("errorFileAttachTitle");
+ let msg = sComposeMsgsBundle.getFormattedString("errorFileAttachMessage",
+ [attachmentStr]);
+ Services.prompt.alert(null, title, msg);
+ }
+ }
+ }
+ if (args.newshost)
+ composeFields.newshost = args.newshost;
+ if (args.message) {
+ let msgFile = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsIFile);
+ if (OS.Path.dirname(args.message) == ".") {
+ let workingDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ args.message = OS.Path.join(workingDir.path, OS.Path.basename(args.message));
+ }
+ msgFile.initWithPath(args.message);
+
+ if (!msgFile.exists()) {
+ let title = sComposeMsgsBundle.getString("errorFileMessageTitle");
+ let msg = sComposeMsgsBundle.getFormattedString("errorFileMessageMessage",
+ [args.message]);
+ Services.prompt.alert(null, title, msg);
+ } else {
+ let data = "";
+ let fstream = null;
+ let cstream = null;
+
+ try {
+ fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ fstream.init(msgFile, -1, 0, 0); // Open file in default/read-only mode.
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let str = {};
+ let read = 0;
+
+ do {
+ // Read as much as we can and put it in str.value.
+ read = cstream.readString(0xffffffff, str);
+ data += str.value;
+ } while (read != 0);
+ } catch (e) {
+ let title = sComposeMsgsBundle.getString("errorFileMessageTitle");
+ let msg = sComposeMsgsBundle.getFormattedString("errorLoadFileMessageMessage",
+ [args.message]);
+ Services.prompt.alert(null, title, msg);
+
+ } finally {
+ if (cstream)
+ cstream.close();
+ if (fstream)
+ fstream.close();
+ }
+
+ if (data) {
+ let pos = data.search(/\S/); // Find first non-whitespace character.
+
+ if (params.format != Ci.nsIMsgCompFormat.PlainText &&
+ (args.message.endsWith(".htm") ||
+ args.message.endsWith(".html") ||
+ data.substr(pos, 14).toLowerCase() == "<!doctype html" ||
+ data.substr(pos, 5).toLowerCase() == "<html")) {
+ // We replace line breaks because otherwise they'll be converted
+ // to <br> in nsMsgCompose::BuildBodyMessageAndSignature().
+ // Don't do the conversion if the user asked explicitly for plain
+ // text.
+ data = data.replace(/\r?\n/g, " ");
+ }
+ gBodyFromArgs = true;
+ composeFields.body = data;
+ }
+ }
+ } else if (args.body) {
+ gBodyFromArgs = true;
+ composeFields.body = args.body;
+ }
+ }
+ }
+
+ gComposeType = params.type;
+
+ // Detect correct identity when missing or mismatched.
+ // An identity with no email is likely not valid.
+ // When editing a draft, 'params.identity' is pre-populated with the identity
+ // that created the draft or the identity owning the draft folder for a
+ // "foreign", draft, see ComposeMessage() in mailCommands.js. We don't want
+ // the latter, so use the creator identity which could be null.
+ if (gComposeType == Ci.nsIMsgCompType.Draft) {
+ let creatorKey = params.composeFields.creatorIdentityKey;
+ params.identity = creatorKey ? getIdentityForKey(creatorKey) : null;
+ }
+ let from = [];
+ if (params.composeFields.from)
+ from = MailServices.headerParser
+ .parseEncodedHeader(params.composeFields.from, null);
+ from = (from.length && from[0] && from[0].email) ?
+ from[0].email.toLowerCase().trim() : null;
+ if (!params.identity || !params.identity.email ||
+ (from && !emailSimilar(from, params.identity.email))) {
+ let identities = MailServices.accounts.allIdentities;
+ let suitableCount = 0;
+
+ // Search for a matching identity.
+ if (from) {
+ for (let ident of identities) {
+ if (ident.email && from == ident.email.toLowerCase()) {
+ if (suitableCount == 0)
+ params.identity = ident;
+ suitableCount++;
+ if (suitableCount > 1)
+ break; // No need to find more, it's already not unique.
+ }
+ }
+ }
+
+ if (!params.identity || !params.identity.email) {
+ let identity = null;
+ // No preset identity and no match, so use the default account.
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ if (defaultAccount) {
+ identity = defaultAccount.defaultIdentity;
+ }
+ if (!identity) {
+ // Get the first identity we have in the list.
+ let identitykey = identityList.getItemAtIndex(0).getAttribute("identitykey");
+ identity = MailServices.accounts.getIdentity(identitykey);
+ }
+ params.identity = identity;
+ }
+
+ // Warn if no or more than one match was found.
+ // But don't warn for +suffix additions (a+b@c.com).
+ if (from && (suitableCount > 1 ||
+ (suitableCount == 0 && !emailSimilar(from, params.identity.email))))
+ gComposeNotificationBar.setIdentityWarning(params.identity.identityName);
+ }
+
+ identityList.selectedItem =
+ identityList.getElementsByAttribute("identitykey", params.identity.key)[0];
+ if (params.composeFields.from)
+ identityList.value = MailServices.headerParser.parseDecodedHeader(params.composeFields.from)[0].toString();
+ LoadIdentity(true);
+
+ // Get the <editor> element to startup an editor
+ var editorElement = GetCurrentEditorElement();
+
+ // Remember the original message URI. When editing a draft which is a reply
+ // or forwarded message, this gets overwritten by the ancestor's message URI
+ // so the disposition flags ("replied" or "forwarded") can be set on the
+ // ancestor.
+ // For our purposes we need the URI of the message being processed, not its
+ // original ancestor.
+ gOriginalMsgURI = params.originalMsgURI;
+ gMsgCompose = MailServices.compose.initCompose(params, window,
+ editorElement.docShell);
+
+ document.getElementById("returnReceiptMenu")
+ .setAttribute("checked", gMsgCompose.compFields.returnReceipt);
+ document.getElementById("dsnMenu")
+ .setAttribute('checked', gMsgCompose.compFields.DSN);
+ document.getElementById("cmd_attachVCard")
+ .setAttribute("checked", gMsgCompose.compFields.attachVCard);
+ document.getElementById("menu_inlineSpellCheck")
+ .setAttribute("checked",
+ Services.prefs.getBoolPref("mail.spellcheck.inline"));
+
+ let editortype = gMsgCompose.composeHTML ? "htmlmail" : "textmail";
+ editorElement.makeEditable(editortype, true);
+
+ // setEditorType MUST be call before setContentWindow
+ if (gMsgCompose.composeHTML) {
+ initLocalFontFaceMenu(document.getElementById("FontFacePopup"));
+ } else {
+ //Remove HTML toolbar, format and insert menus as we are editing in plain
+ //text mode.
+ let toolbar = document.getElementById("FormatToolbar");
+ toolbar.hidden = true;
+ toolbar.setAttribute("hideinmenu", "true");
+ document.getElementById("outputFormatMenu").setAttribute("hidden", true);
+ document.getElementById("formatMenu").setAttribute("hidden", true);
+ document.getElementById("insertMenu").setAttribute("hidden", true);
+ }
+
+ // Do setup common to Message Composer and Web Composer.
+ EditorSharedStartup();
+
+ if (params.bodyIsLink) {
+ let body = gMsgCompose.compFields.body;
+ if (gMsgCompose.composeHTML) {
+ let cleanBody;
+ try {
+ cleanBody = decodeURI(body);
+ } catch(e) {
+ cleanBody = body;
+ }
+
+ body = body.replace(/&/g, "&amp;");
+ gMsgCompose.compFields.body =
+ "<br /><a href=\"" + body + "\">" + cleanBody + "</a><br />";
+ } else {
+ gMsgCompose.compFields.body = "\n<" + body + ">\n";
+ }
+ }
+
+ GetMsgSubjectElement().value = gMsgCompose.compFields.subject;
+
+ var attachments = gMsgCompose.compFields.attachments;
+ while (attachments.hasMoreElements()) {
+ AddAttachment(attachments.getNext().QueryInterface(Ci.nsIMsgAttachment));
+ }
+
+ var event = document.createEvent('Events');
+ event.initEvent('compose-window-init', false, true);
+ document.getElementById("msgcomposeWindow").dispatchEvent(event);
+
+ gMsgCompose.RegisterStateListener(stateListener);
+
+ // Add an observer to be called when document is done loading,
+ // which creates the editor.
+ try {
+ GetCurrentCommandManager().addCommandObserver(gMsgEditorCreationObserver,
+ "obs_documentCreated");
+
+ // Load empty page to create the editor
+ editorElement.webNavigation.loadURI("about:blank",
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, // referrer
+ null, // post-data stream
+ null, // HTTP headers
+ Services.scriptSecurityManager.getSystemPrincipal());
+ } catch (e) {
+ dump(" Failed to startup editor: "+e+"\n");
+ }
+
+ // create URI of the folder from draftId
+ var draftId = gMsgCompose.compFields.draftId;
+ var folderURI = draftId.substring(0, draftId.indexOf("#")).replace("-message", "");
+
+ try {
+ var folder = sRDF.GetResource(folderURI);
+
+ gEditingDraft = (folder instanceof Ci.nsIMsgFolder) &&
+ (folder.flags & Ci.nsMsgFolderFlags.Drafts);
+ }
+ catch (ex) {
+ gEditingDraft = false;
+ }
+
+ gAutoSaveKickedIn = false;
+
+ gAutoSaveInterval = Services.prefs.getBoolPref("mail.compose.autosave")
+ ? Services.prefs.getIntPref("mail.compose.autosaveinterval") * 60000
+ : 0;
+
+ if (gAutoSaveInterval)
+ gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
+}
+
+function splitEmailAddress(aEmail) {
+ let at = aEmail.lastIndexOf("@");
+ return (at != -1) ? [aEmail.slice(0, at), aEmail.slice(at + 1)]
+ : [aEmail, ""];
+}
+
+// Emails are equal ignoring +suffixes (email+suffix@example.com).
+function emailSimilar(a, b) {
+ if (!a || !b)
+ return a == b;
+ a = splitEmailAddress(a.toLowerCase());
+ b = splitEmailAddress(b.toLowerCase());
+ return a[1] == b[1] && a[0].split("+", 1)[0] == b[0].split("+", 1)[0];
+}
+
+// The new, nice, simple way of getting notified when a new editor has been created
+var gMsgEditorCreationObserver =
+{
+ observe: function(aSubject, aTopic, aData)
+ {
+ if (aTopic == "obs_documentCreated")
+ {
+ var editor = GetCurrentEditor();
+ var commandManager = GetCurrentCommandManager();
+ if (editor && commandManager == aSubject) {
+ let editorStyle = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+ // We use addOverrideStyleSheet rather than addStyleSheet so that we get
+ // a synchronous load, rather than having a late-finishing async load
+ // mark our editor as modified when the user hasn't typed anything yet,
+ // but that means the sheet must not @import slow things, especially
+ // not over the network.
+ editorStyle.addOverrideStyleSheet("chrome://messenger/skin/messageQuotes.css");
+ InitEditor(editor);
+ }
+ // Now that we know this document is an editor, update commands now if
+ // the document has focus, or next time it receives focus via
+ // CommandUpdate_MsgCompose()
+ if (gLastWindowToHaveFocus == document.commandDispatcher.focusedWindow)
+ updateComposeItems();
+ else
+ gLastWindowToHaveFocus = null;
+ }
+ }
+}
+
+function WizCallback(state)
+{
+ if (state){
+ ComposeStartup(null);
+ }
+ else
+ {
+ // The account wizard is still closing so we can't close just yet
+ setTimeout(MsgComposeCloseWindow, 0);
+ }
+}
+
+function ComposeLoad()
+{
+ sComposeMsgsBundle = document.getElementById("bundle_composeMsgs");
+ sBrandBundle = document.getElementById("brandBundle");
+
+ var otherHeaders = Services.prefs.getCharPref("mail.compose.other.header");
+
+ sRDF = Cc['@mozilla.org/rdf/rdf-service;1']
+ .getService(Ci.nsIRDFService);
+ sNameProperty = sRDF.GetResource("http://home.netscape.com/NC-rdf#Name?sort=true");
+
+ AddMessageComposeOfflineQuitObserver();
+
+ if (gLogComposePerformance)
+ MailServices.compose.TimeStamp("Start initializing the compose window (ComposeLoad)", false);
+
+ msgWindow.notificationCallbacks = new nsMsgBadCertHandler();
+
+ try {
+ SetupCommandUpdateHandlers();
+ // This will do migration, or create a new account if we need to.
+ // We also want to open the account wizard if no identities are found
+ var state = verifyAccounts(WizCallback, true);
+
+ if (otherHeaders) {
+ var selectNode = document.getElementById('addressCol1#1');
+ var otherHeaders_Array = otherHeaders.split(",");
+ for (let i = 0; i < otherHeaders_Array.length; i++)
+ selectNode.appendItem(otherHeaders_Array[i] + ":", "addr_other");
+ }
+ if (state)
+ ComposeStartup(null);
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ var errorTitle = sComposeMsgsBundle.getString("initErrorDlogTitle");
+ var errorMsg = sComposeMsgsBundle.getString("initErrorDlgMessage");
+ Services.prompt.alert(window, errorTitle, errorMsg);
+
+ MsgComposeCloseWindow();
+ return;
+ }
+ if (gLogComposePerformance)
+ MailServices.compose.TimeStamp("Done with the initialization (ComposeLoad). Waiting on editor to load about:blank", false);
+
+ // Before and after callbacks for the customizeToolbar code
+ var mailToolbox = getMailToolbox();
+ mailToolbox.customizeInit = MailToolboxCustomizeInit;
+ mailToolbox.customizeDone = MailToolboxCustomizeDone;
+ mailToolbox.customizeChange = MailToolboxCustomizeChange;
+}
+
+function ComposeUnload()
+{
+ // Send notification that the window is going away completely.
+ document.getElementById("msgcomposeWindow").dispatchEvent(
+ new Event("compose-window-unload", { bubbles: false, cancelable: false }));
+
+ GetCurrentCommandManager().removeCommandObserver(gMsgEditorCreationObserver,
+ "obs_documentCreated");
+ UnloadCommandUpdateHandlers();
+
+ // Stop InlineSpellCheckerUI so personal dictionary is saved
+ EnableInlineSpellCheck(false);
+
+ EditorCleanup();
+
+ RemoveMessageComposeOfflineQuitObserver();
+
+ if (gMsgCompose)
+ gMsgCompose.UnregisterStateListener(stateListener);
+ if (gAutoSaveTimeout)
+ clearTimeout(gAutoSaveTimeout);
+ if (msgWindow) {
+ msgWindow.closeWindow();
+ msgWindow.notificationCallbacks = null;
+ }
+
+ ReleaseGlobalVariables();
+}
+
+function ComposeSetCharacterSet(aEvent)
+{
+ if (gMsgCompose)
+ SetDocumentCharacterSet(aEvent.target.getAttribute("charset"));
+ else
+ dump("Compose has not been created!\n");
+}
+
+function SetDocumentCharacterSet(aCharset)
+{
+ // Replace generic Japanese with ISO-2022-JP.
+ if (aCharset == "Japanese") {
+ aCharset = "ISO-2022-JP";
+ }
+ gMsgCompose.SetDocumentCharset(aCharset);
+ SetComposeWindowTitle();
+}
+
+function GetCharsetUIString()
+{
+ // The charset here is already the canonical charset (not an alias).
+ let charset = gMsgCompose.compFields.characterSet;
+ if (!charset)
+ return "";
+
+ if (charset.toLowerCase() != gMsgCompose.compFields.defaultCharacterSet.toLowerCase()) {
+ try {
+ return " - " + gCharsetConvertManager.getCharsetTitle(charset);
+ }
+ catch(e) { // Not a canonical charset after all...
+ Cu.reportError("Not charset title for charset=" + charset);
+ return " - " + charset;
+ }
+ }
+ return "";
+}
+
+// Add-ons can override this to customize the behavior.
+function DoSpellCheckBeforeSend()
+{
+ return Services.prefs.getBoolPref("mail.SpellCheckBeforeSend");
+}
+
+/**
+ * Handles message sending operations.
+ * @param msgType nsIMsgCompDeliverMode of the operation.
+ */
+function GenericSendMessage(msgType) {
+ var msgCompFields = gMsgCompose.compFields;
+
+ Recipients2CompFields(msgCompFields);
+ var address = GetMsgIdentityElement().value;
+ address = MailServices.headerParser.makeFromDisplayAddress(address);
+ msgCompFields.from = MailServices.headerParser.makeMimeHeader([address[0]]);
+ var subject = GetMsgSubjectElement().value;
+ msgCompFields.subject = subject;
+ Attachments2CompFields(msgCompFields);
+
+ if (msgType == Ci.nsIMsgCompDeliverMode.Now ||
+ msgType == Ci.nsIMsgCompDeliverMode.Later ||
+ msgType == Ci.nsIMsgCompDeliverMode.Background) {
+ //Do we need to check the spelling?
+ if (DoSpellCheckBeforeSend()) {
+ // We disable spellcheck for the following -subject line, attachment
+ // pane, identity and addressing widget therefore we need to explicitly
+ // focus on the mail body when we have to do a spellcheck.
+ SetMsgBodyFrameFocus();
+ window.cancelSendMessage = false;
+ window.openDialog("chrome://editor/content/EdSpellCheck.xul", "_blank",
+ "dialog,close,titlebar,modal,resizable",
+ true, true, false);
+ if (window.cancelSendMessage)
+ return;
+ }
+
+ // Strip trailing spaces and long consecutive WSP sequences from the
+ // subject line to prevent getting only WSP chars on a folded line.
+ var fixedSubject = subject.replace(/\s{74,}/g, " ")
+ .replace(/\s*$/, "");
+ if (fixedSubject != subject) {
+ subject = fixedSubject;
+ msgCompFields.subject = fixedSubject;
+ GetMsgSubjectElement().value = fixedSubject;
+ }
+
+ // Remind the person if there isn't a subject.
+ if (subject == "") {
+ if (Services.prompt.confirmEx(
+ window,
+ sComposeMsgsBundle.getString("subjectEmptyTitle"),
+ sComposeMsgsBundle.getString("subjectEmptyMessage"),
+ (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_1),
+ sComposeMsgsBundle.getString("sendWithEmptySubjectButton"),
+ sComposeMsgsBundle.getString("cancelSendingButton"),
+ null, null, {value:0}) == 1) {
+ GetMsgSubjectElement().focus();
+ return;
+ }
+ }
+
+ // Check if the user tries to send a message to a newsgroup through a mail
+ // account.
+ var currentAccountKey = getCurrentAccountKey();
+ var account = MailServices.accounts.getAccount(currentAccountKey);
+ if (!account) {
+ throw "UNEXPECTED: currentAccountKey '" + currentAccountKey +
+ "' has no matching account!";
+ }
+
+ if (account.incomingServer.type != "nntp" &&
+ msgCompFields.newsgroups != "") {
+ const kDontAskAgainPref = "mail.compose.dontWarnMail2Newsgroup";
+ // Default to ask user if the pref is not set.
+ var dontAskAgain = Services.prefs.getBoolPref(kDontAskAgainPref);
+ if (!dontAskAgain) {
+ var checkbox = {value:false};
+ var okToProceed = Services.prompt.confirmCheck(
+ window,
+ sComposeMsgsBundle.getString("noNewsgroupSupportTitle"),
+ sComposeMsgsBundle.getString("recipientDlogMessage"),
+ sComposeMsgsBundle.getString("CheckMsg"),
+ checkbox);
+
+ if (!okToProceed)
+ return;
+ }
+ if (checkbox.value)
+ Services.prefs.setBoolPref(kDontAskAgainPref, true);
+
+ // Remove newsgroups to prevent news_p to be set
+ // in nsMsgComposeAndSend::DeliverMessage()
+ msgCompFields.newsgroups = "";
+ }
+
+ // Before sending the message, check what to do with HTML message,
+ // eventually abort.
+ var convert = DetermineConvertibility();
+ var action = DetermineHTMLAction(convert);
+ // Check if e-mail addresses are complete, in case user has turned off
+ // autocomplete to local domain.
+ if (!CheckValidEmailAddress(msgCompFields.to, msgCompFields.cc, msgCompFields.bcc))
+ return;
+
+ if (action == Ci.nsIMsgCompSendFormat.AskUser) {
+ var recommAction = (convert == Ci.nsIMsgCompConvertible.No)
+ ? Ci.nsIMsgCompSendFormat.AskUser
+ : Ci.nsIMsgCompSendFormat.PlainText;
+ var result2 = {action:recommAction, convertible:convert, abort:false};
+ window.openDialog("chrome://messenger/content/messengercompose/askSendFormat.xul",
+ "askSendFormatDialog", "chrome,modal,titlebar,centerscreen",
+ result2);
+ if (result2.abort)
+ return;
+ action = result2.action;
+ }
+
+ // We will remember the users "send format" decision in the address
+ // collector code (see nsAbAddressCollector::CollectAddress())
+ // by using msgCompFields.forcePlainText and
+ // msgCompFields.useMultipartAlternative to determine the
+ // nsIAbPreferMailFormat (unknown, plaintext, or html).
+ // If the user sends both, we remember html.
+ switch (action) {
+ case Ci.nsIMsgCompSendFormat.PlainText:
+ msgCompFields.forcePlainText = true;
+ msgCompFields.useMultipartAlternative = false;
+ break;
+ case Ci.nsIMsgCompSendFormat.HTML:
+ msgCompFields.forcePlainText = false;
+ msgCompFields.useMultipartAlternative = false;
+ break;
+ case Ci.nsIMsgCompSendFormat.Both:
+ msgCompFields.forcePlainText = false;
+ msgCompFields.useMultipartAlternative = true;
+ break;
+ default:
+ throw new Error("Invalid nsIMsgCompSendFormat action; action=" + action);
+ }
+ }
+
+ // Hook for extra compose pre-processing.
+ Services.obs.notifyObservers(window, "mail:composeOnSend");
+
+ var originalCharset = gMsgCompose.compFields.characterSet;
+ // Check if the headers of composing mail can be converted to a mail charset.
+ if (msgType == Ci.nsIMsgCompDeliverMode.Now ||
+ msgType == Ci.nsIMsgCompDeliverMode.Later ||
+ msgType == Ci.nsIMsgCompDeliverMode.Background ||
+ msgType == Ci.nsIMsgCompDeliverMode.Save ||
+ msgType == Ci.nsIMsgCompDeliverMode.SaveAsDraft ||
+ msgType == Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft ||
+ msgType == Ci.nsIMsgCompDeliverMode.SaveAsTemplate) {
+ var fallbackCharset = new Object;
+ // Check encoding, switch to UTF-8 if the default encoding doesn't fit
+ // and disable_fallback_to_utf8 isn't set for this encoding.
+ if (!gMsgCompose.checkCharsetConversion(getCurrentIdentity(),
+ fallbackCharset)) {
+ let disableFallback = Services.prefs
+ .getBoolPref("mailnews.disable_fallback_to_utf8." + originalCharset, false);
+ if (disableFallback)
+ msgCompFields.needToCheckCharset = false;
+ else
+ fallbackCharset.value = "UTF-8";
+ }
+
+ if (fallbackCharset &&
+ fallbackCharset.value && fallbackCharset.value != "")
+ gMsgCompose.SetDocumentCharset(fallbackCharset.value);
+ }
+ try {
+ // Just before we try to send the message, fire off the
+ // compose-send-message event for listeners such as smime so they can do
+ // any pre-security work such as fetching certificates before sending.
+ var event = document.createEvent('UIEvents');
+ event.initEvent('compose-send-message', false, true);
+ var msgcomposeWindow = document.getElementById("msgcomposeWindow");
+ msgcomposeWindow.setAttribute("msgtype", msgType);
+ msgcomposeWindow.dispatchEvent(event);
+ if (event.defaultPrevented)
+ throw Cr.NS_ERROR_ABORT;
+
+ gAutoSaving = (msgType == Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft);
+ if (!gAutoSaving) {
+ // Disable the ui if we're not auto-saving.
+ gWindowLocked = true;
+ disableEditableFields();
+ updateComposeItems();
+ } else {
+ // If we're auto saving, mark the body as not changed here, and not
+ // when the save is done, because the user might change it between now
+ // and when the save is done.
+ SetContentAndBodyAsUnmodified();
+ }
+
+ var progress = Cc["@mozilla.org/messenger/progress;1"]
+ .createInstance(Ci.nsIMsgProgress);
+ if (progress) {
+ progress.registerListener(progressListener);
+ gSendOrSaveOperationInProgress = true;
+ }
+ msgWindow.domWindow = window;
+ msgWindow.rootDocShell.allowAuth = true;
+ gMsgCompose.SendMsg(msgType, getCurrentIdentity(), getCurrentAccountKey(),
+ msgWindow, progress);
+ }
+ catch (ex) {
+ Cu.reportError("GenericSendMessage FAILED: " + ex);
+ gWindowLocked = false;
+ enableEditableFields();
+ updateComposeItems();
+ }
+ if (gMsgCompose && originalCharset != gMsgCompose.compFields.characterSet)
+ SetDocumentCharacterSet(gMsgCompose.compFields.characterSet);
+}
+
+/**
+ * Check if the given address is valid (contains a @).
+ *
+ * @param aAddress The address string to check.
+ */
+function isValidAddress(aAddress) {
+ return (aAddress.includes("@", 1) && !aAddress.endsWith("@"));
+}
+
+/**
+ * Keep the Send buttons disabled until any recipient is entered.
+ */
+function updateSendLock() {
+ gSendLocked = true;
+ if (!gMsgCompose)
+ return;
+
+ // Helper function to check for a valid list name.
+ function isValidListName(aInput) {
+ let listNames = MimeParser.parseHeaderField(aInput,
+ MimeParser.HEADER_ADDRESS);
+ return listNames.length > 0 &&
+ MailServices.ab.mailListNameExists(listNames[0].name);
+ }
+
+ const mailTypes = [ "addr_to", "addr_cc", "addr_bcc" ];
+
+ // Enable the send buttons if anything usable was entered into at least one
+ // recipient field.
+ for (let row = 1; row <= top.MAX_RECIPIENTS; row ++) {
+ let popupValue = awGetPopupElement(row).value;
+ let inputValue = awGetInputElement(row).value.trim();
+ // Check for a valid looking email address or a valid mailing list name
+ // from one of our addressbooks.
+ if ((mailTypes.includes(popupValue) &&
+ (isValidAddress(inputValue) || isValidListName(inputValue))) ||
+ ((popupValue == "addr_newsgroups") && (inputValue != ""))) {
+ gSendLocked = false;
+ break;
+ }
+ }
+}
+
+function CheckValidEmailAddress(aTo, aCC, aBCC)
+{
+ var invalidStr = null;
+ // crude check that the to, cc, and bcc fields contain at least one '@'.
+ // We could parse each address, but that might be overkill.
+ if (aTo.length > 0 && (aTo.indexOf("@") <= 0 && aTo.toLowerCase() != "postmaster" || aTo.indexOf("@") == aTo.length - 1))
+ invalidStr = aTo;
+ else if (aCC.length > 0 && (aCC.indexOf("@") <= 0 && aCC.toLowerCase() != "postmaster" || aCC.indexOf("@") == aCC.length - 1))
+ invalidStr = aCC;
+ else if (aBCC.length > 0 && (aBCC.indexOf("@") <= 0 && aBCC.toLowerCase() != "postmaster" || aBCC.indexOf("@") == aBCC.length - 1))
+ invalidStr = aBCC;
+ if (invalidStr)
+ {
+ var errorTitle = sComposeMsgsBundle.getString("addressInvalidTitle");
+ var errorMsg = sComposeMsgsBundle.getFormattedString("addressInvalid", [invalidStr], 1);
+ Services.prompt.alert(window, errorTitle, errorMsg);
+ return false;
+ }
+ return true;
+}
+
+function SendMessage()
+{
+ let sendInBackground = Services.prefs.getBoolPref("mailnews.sendInBackground");
+ if (sendInBackground && AppConstants.platform != "macosx") {
+ let enumerator = Services.wm.getEnumerator(null);
+ let count = 0;
+ while (enumerator.hasMoreElements() && count < 2)
+ {
+ enumerator.getNext();
+ count++;
+ }
+ if (count == 1)
+ sendInBackground = false;
+ }
+ GenericSendMessage(sendInBackground ? nsIMsgCompDeliverMode.Background
+ : nsIMsgCompDeliverMode.Now);
+}
+
+function SendMessageWithCheck()
+{
+ var warn = Services.prefs.getBoolPref("mail.warn_on_send_accel_key");
+
+ if (warn) {
+ var checkValue = {value:false};
+ var buttonPressed = Services.prompt.confirmEx(window,
+ sComposeMsgsBundle.getString('sendMessageCheckWindowTitle'),
+ sComposeMsgsBundle.getString('sendMessageCheckLabel'),
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
+ sComposeMsgsBundle.getString('sendMessageCheckSendButtonLabel'),
+ null, null,
+ sComposeMsgsBundle.getString('CheckMsg'),
+ checkValue);
+ if (buttonPressed != 0) {
+ return;
+ }
+ if (checkValue.value) {
+ Services.prefs.setBoolPref("mail.warn_on_send_accel_key", false);
+ }
+ }
+
+ if (Services.io.offline)
+ SendMessageLater();
+ else
+ SendMessage();
+}
+
+function SendMessageLater()
+{
+ GenericSendMessage(nsIMsgCompDeliverMode.Later);
+}
+
+function Save()
+{
+ switch (defaultSaveOperation)
+ {
+ case "file" : SaveAsFile(false); break;
+ case "template" : SaveAsTemplate(false); break;
+ default : SaveAsDraft(false); break;
+ }
+}
+
+function SaveAsFile(saveAs)
+{
+ var subject = GetMsgSubjectElement().value;
+ GetCurrentEditorElement().contentDocument.title = subject;
+
+ if (gMsgCompose.bodyConvertible() == nsIMsgCompConvertible.Plain)
+ SaveDocument(saveAs, false, "text/plain");
+ else
+ SaveDocument(saveAs, false, "text/html");
+ defaultSaveOperation = "file";
+}
+
+function SaveAsDraft()
+{
+ GenericSendMessage(nsIMsgCompDeliverMode.SaveAsDraft);
+ defaultSaveOperation = "draft";
+
+ gAutoSaveKickedIn = false;
+ gEditingDraft = true;
+}
+
+function SaveAsTemplate()
+{
+ let savedReferences = null;
+ if (gMsgCompose && gMsgCompose.compFields) {
+ // Clear References header. When we use the template, we don't want that
+ // header, yet, "edit as new message" maintains it. So we need to clear
+ // it when saving the template.
+ // Note: The In-Reply-To header is the last entry in the references header,
+ // so it will get cleared as well.
+ savedReferences = gMsgCompose.compFields.references;
+ gMsgCompose.compFields.references = null;
+ }
+
+ GenericSendMessage(nsIMsgCompDeliverMode.SaveAsTemplate);
+ defaultSaveOperation = "template";
+
+ if (savedReferences)
+ gMsgCompose.compFields.references = savedReferences;
+
+ gAutoSaveKickedIn = false;
+ gEditingDraft = false;
+}
+
+// Sets the additional FCC, in addition to the default FCC.
+function MessageFcc(aFolder) {
+ if (!gMsgCompose)
+ return;
+
+ var msgCompFields = gMsgCompose.compFields;
+ if (!msgCompFields)
+ return;
+
+ // Get the uri for the folder to FCC into.
+ var fccURI = aFolder.URI;
+ msgCompFields.fcc2 = (msgCompFields.fcc2 == fccURI) ? "nocopy://" : fccURI;
+}
+
+function updatePriorityMenu(priorityMenu)
+{
+ var priority = (gMsgCompose && gMsgCompose.compFields && gMsgCompose.compFields.priority) || "Normal";
+ priorityMenu.getElementsByAttribute("value", priority)[0].setAttribute("checked", "true");
+}
+
+function PriorityMenuSelect(target)
+{
+ if (gMsgCompose)
+ {
+ var msgCompFields = gMsgCompose.compFields;
+ if (msgCompFields)
+ msgCompFields.priority = target.getAttribute("value");
+ }
+}
+
+function OutputFormatMenuSelect(target)
+{
+ if (gMsgCompose)
+ {
+ var msgCompFields = gMsgCompose.compFields;
+ var toolbar = document.getElementById("FormatToolbar");
+ var format_menubar = document.getElementById("formatMenu");
+ var insert_menubar = document.getElementById("insertMenu");
+
+ if (msgCompFields)
+ switch (target.getAttribute('id'))
+ {
+ case "format_auto": gSendFormat = nsIMsgCompSendFormat.AskUser; break;
+ case "format_plain": gSendFormat = nsIMsgCompSendFormat.PlainText; break;
+ case "format_html": gSendFormat = nsIMsgCompSendFormat.HTML; break;
+ case "format_both": gSendFormat = nsIMsgCompSendFormat.Both; break;
+ }
+ gHideMenus = (gSendFormat == nsIMsgCompSendFormat.PlainText);
+ format_menubar.hidden = gHideMenus;
+ insert_menubar.hidden = gHideMenus;
+ if (gHideMenus) {
+ gFormatToolbarHidden = toolbar.hidden;
+ toolbar.hidden = true;
+ toolbar.setAttribute("hideinmenu", "true");
+ } else {
+ toolbar.hidden = gFormatToolbarHidden;
+ toolbar.removeAttribute("hideinmenu");
+ }
+ }
+}
+
+function SelectAddress()
+{
+ var msgCompFields = gMsgCompose.compFields;
+
+ Recipients2CompFields(msgCompFields);
+
+ var toAddress = msgCompFields.to;
+ var ccAddress = msgCompFields.cc;
+ var bccAddress = msgCompFields.bcc;
+
+ dump("toAddress: " + toAddress + "\n");
+ window.openDialog("chrome://messenger/content/addressbook/abSelectAddressesDialog.xul",
+ "",
+ "chrome,resizable,titlebar,modal",
+ {composeWindow:top.window,
+ msgCompFields:msgCompFields,
+ toAddress:toAddress,
+ ccAddress:ccAddress,
+ bccAddress:bccAddress});
+ // We have to set focus to the addressingwidget because we seem to loose focus often
+ // after opening the SelectAddresses Dialog- bug # 89950
+ AdjustFocus();
+}
+
+// walk through the recipients list and add them to the inline spell checker ignore list
+function addRecipientsToIgnoreList(aAddressesToAdd)
+{
+ if (InlineSpellCheckerUI.enabled)
+ {
+ // break the list of potentially many recipients back into individual names
+ var emailAddresses = {};
+ var names = {};
+ var fullNames = {};
+ var numAddresses =
+ MailServices.headerParser.parseHeadersWithArray(aAddressesToAdd,
+ emailAddresses, names,
+ fullNames);
+ var tokenizedNames = [];
+
+ // each name could consist of multiple words delimited by commas and/or spaces.
+ // i.e. Green Lantern or Lantern,Green.
+ for (let i = 0; i < names.value.length; i++)
+ {
+ if (!names.value[i])
+ continue;
+ var splitNames = names.value[i].match(/[^\s,]+/g);
+ if (splitNames)
+ tokenizedNames = tokenizedNames.concat(splitNames);
+ }
+
+ if (InlineSpellCheckerUI.mInlineSpellChecker.spellCheckPending)
+ {
+ // spellchecker is enabled, but we must wait for its init to complete
+ Services.obs.addObserver(function observe(subject, topic, data) {
+ if (subject == gMsgCompose.editor)
+ {
+ Services.obs.removeObserver(observe, topic);
+ InlineSpellCheckerUI.mInlineSpellChecker.ignoreWords(tokenizedNames);
+ }
+ }, "inlineSpellChecker-spellCheck-ended");
+ }
+ else
+ {
+ InlineSpellCheckerUI.mInlineSpellChecker.ignoreWords(tokenizedNames);
+ }
+ }
+}
+
+function onAddressColCommand(aWidgetId) {
+ gContentChanged = true;
+ awSetAutoComplete(aWidgetId.slice(aWidgetId.lastIndexOf('#') + 1));
+ updateSendCommands(true);
+}
+
+/**
+ * Called if the list of recipients changed in any way.
+ *
+ * @param aAutomatic Set to true if the change of recipients was invoked
+ * programatically and should not be considered a change
+ * of message content.
+ */
+function onRecipientsChanged(aAutomatic) {
+ if (!aAutomatic) {
+ gContentChanged = true;
+ setupAutocomplete();
+ }
+ updateSendCommands(true);
+}
+
+function InitLanguageMenu()
+{
+ var languageMenuList = document.getElementById("languageMenuList");
+ if (!languageMenuList)
+ return;
+
+ var spellChecker = Cc["@mozilla.org/spellchecker/engine;1"]
+ .getService(mozISpellCheckingEngine);
+ // Get the list of dictionaries from the spellchecker.
+ var dictList = spellChecker.getDictionaryList();
+ var count = dictList.length;
+
+ // If dictionary count hasn't changed then no need to update the menu.
+ if (sDictCount == count)
+ return;
+
+ // Store current dictionary count.
+ sDictCount = count;
+
+ // Load the language string bundle that will help us map
+ // RFC 1766 strings to UI strings.
+ var languageBundle = document.getElementById("languageBundle");
+ var isoStrArray;
+ var langId;
+ var langLabel;
+
+ for (let i = 0; i < count; i++)
+ {
+ try
+ {
+ langId = dictList[i];
+ isoStrArray = dictList[i].split(/[-_]/);
+
+ if (languageBundle && isoStrArray[0])
+ langLabel = languageBundle.getString(isoStrArray[0].toLowerCase());
+
+ // the user needs to be able to distinguish between the UK English dictionary
+ // and say the United States English Dictionary. If we have a isoStr value then
+ // wrap it in parentheses and append it to the menu item string. i.e.
+ // English (US) and English (UK)
+ if (!langLabel)
+ langLabel = langId;
+ // if we have a language ID like US or UK, append it to the menu item, and any sub-variety
+ else if (isoStrArray.length > 1 && isoStrArray[1]) {
+ langLabel += ' (' + isoStrArray[1];
+ if (isoStrArray.length > 2 && isoStrArray[2])
+ langLabel += '-' + isoStrArray[2];
+ langLabel += ')';
+ }
+ }
+ catch (ex)
+ {
+ // getString throws an exception when a key is not found in the
+ // bundle. In that case, just use the original dictList string.
+ langLabel = langId;
+ }
+ dictList[i] = [langLabel, langId];
+ }
+
+ // sort by locale-aware collation
+ dictList.sort(
+ function compareFn(a, b)
+ {
+ return a[0].localeCompare(b[0]);
+ }
+ );
+
+ // Remove any languages from the list.
+ while (languageMenuList.hasChildNodes())
+ languageMenuList.lastChild.remove();
+
+ for (let i = 0; i < count; i++)
+ {
+ var item = document.createElement("menuitem");
+ item.setAttribute("label", dictList[i][0]);
+ item.setAttribute("value", dictList[i][1]);
+ item.setAttribute("type", "radio");
+ languageMenuList.appendChild(item);
+ }
+}
+
+function OnShowDictionaryMenu(aTarget)
+{
+ InitLanguageMenu();
+ var spellChecker = InlineSpellCheckerUI.mInlineSpellChecker.spellChecker;
+ var curLang = spellChecker.GetCurrentDictionary();
+ var languages = aTarget.getElementsByAttribute("value", curLang);
+ if (languages.length > 0)
+ languages[0].setAttribute("checked", true);
+}
+
+function ChangeLanguage(event)
+{
+ // We need to change the dictionary language and if we are using inline spell check,
+ // recheck the message
+ var spellChecker = InlineSpellCheckerUI.mInlineSpellChecker.spellChecker;
+ if (spellChecker.GetCurrentDictionary() != event.target.value)
+ {
+ spellChecker.SetCurrentDictionary(event.target.value);
+
+ ComposeChangeLanguage(event.target.value)
+ }
+ event.stopPropagation();
+}
+
+function ComposeChangeLanguage(aLang)
+{
+ if (document.documentElement.getAttribute("lang") != aLang) {
+
+ // Update the document language as well.
+ // This is needed to synchronize the subject.
+ document.documentElement.setAttribute("lang", aLang);
+
+ // Update spellchecker pref
+ Services.prefs.setCharPref("spellchecker.dictionary", aLang);
+
+ // Now check the document and the subject over again with the new
+ // dictionary.
+ if (InlineSpellCheckerUI.enabled) {
+ InlineSpellCheckerUI.mInlineSpellChecker.spellCheckRange(null);
+
+ // Also force a recheck of the subject. The spell checker for the subject
+ // isn't always ready yet. Usually throws unless the subject was selected
+ // at least once. So don't auto-create it, hence pass 'false'.
+ let inlineSpellChecker =
+ GetMsgSubjectElement().editor.getInlineSpellChecker(false);
+ if (inlineSpellChecker) {
+ inlineSpellChecker.spellCheckRange(null);
+ }
+ }
+ }
+}
+
+function ToggleReturnReceipt(target)
+{
+ var msgCompFields = gMsgCompose.compFields;
+ if (msgCompFields)
+ {
+ msgCompFields.returnReceipt = ! msgCompFields.returnReceipt;
+ target.setAttribute('checked', msgCompFields.returnReceipt);
+ gReceiptOptionChanged = true;
+ }
+}
+
+function ToggleDSN(target)
+{
+ var msgCompFields = gMsgCompose.compFields;
+
+ if (msgCompFields)
+ {
+ msgCompFields.DSN = !msgCompFields.DSN;
+ target.setAttribute('checked', msgCompFields.DSN);
+ gDSNOptionChanged = true;
+ }
+}
+
+function ToggleAttachVCard(target)
+{
+ var msgCompFields = gMsgCompose.compFields;
+ if (msgCompFields)
+ {
+ msgCompFields.attachVCard = ! msgCompFields.attachVCard;
+ target.setAttribute('checked', msgCompFields.attachVCard);
+ gAttachVCardOptionChanged = true;
+ }
+}
+
+function FillIdentityList(menulist)
+{
+ var accounts = FolderUtils.allAccountsSorted(true);
+
+ for (let acc = 0; acc < accounts.length; acc++)
+ {
+ let account = accounts[acc];
+ let identities = account.identities;
+
+ if (identities.length == 0)
+ continue;
+
+ for (let i = 0; i < identities.length; i++)
+ {
+ let identity = identities[i];
+ let item = menulist.appendItem(identity.identityName,
+ identity.fullAddress,
+ account.incomingServer.prettyName);
+ item.setAttribute("identitykey", identity.key);
+ item.setAttribute("accountkey", account.key);
+ if (i == 0)
+ {
+ // Mark the first identity as default.
+ item.setAttribute("default", "true");
+ }
+ }
+ }
+}
+
+function getCurrentAccountKey()
+{
+ // get the accounts key
+ var identityList = GetMsgIdentityElement();
+ return identityList.selectedItem.getAttribute("accountkey");
+}
+
+function getCurrentIdentityKey()
+{
+ // get the identity key
+ var identityList = GetMsgIdentityElement();
+ return identityList.selectedItem.getAttribute("identitykey");
+}
+
+function getIdentityForKey(key)
+{
+ return MailServices.accounts.getIdentity(key);
+}
+
+function getCurrentIdentity()
+{
+ return getIdentityForKey(getCurrentIdentityKey());
+}
+
+function AdjustFocus()
+{
+ let element = awGetInputElement(awGetNumberOfRecipients());
+ if (element.value == "") {
+ awSetFocusTo(element);
+ }
+ else
+ {
+ element = GetMsgSubjectElement();
+ if (element.value == "") {
+ element.focus();
+ }
+ else {
+ SetMsgBodyFrameFocus();
+ }
+ }
+}
+
+function SetComposeWindowTitle()
+{
+ var newTitle = GetMsgSubjectElement().value;
+
+ if (newTitle == "" )
+ newTitle = sComposeMsgsBundle.getString("defaultSubject");
+
+ newTitle += GetCharsetUIString();
+ document.title = sComposeMsgsBundle.getString("windowTitlePrefix") + " " + newTitle;
+}
+
+// Check for changes to document and allow saving before closing
+// This is hooked up to the OS's window close widget (e.g., "X" for Windows)
+function ComposeCanClose()
+{
+ if (gSendOrSaveOperationInProgress)
+ {
+ var brandShortName = sBrandBundle.getString("brandShortName");
+
+ var promptTitle = sComposeMsgsBundle.getString("quitComposeWindowTitle");
+ var promptMsg = sComposeMsgsBundle.getFormattedString("quitComposeWindowMessage2",
+ [brandShortName], 1);
+ var quitButtonLabel = sComposeMsgsBundle.getString("quitComposeWindowQuitButtonLabel2");
+ var waitButtonLabel = sComposeMsgsBundle.getString("quitComposeWindowWaitButtonLabel2");
+
+ 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),
+ waitButtonLabel, quitButtonLabel, null, null, {value:0}) == 1)
+ {
+ gMsgCompose.abort();
+ return true;
+ }
+ return false;
+ }
+
+ // Returns FALSE only if user cancels save action
+ if (gContentChanged || gMsgCompose.bodyModified || (gAutoSaveKickedIn && !gEditingDraft))
+ {
+ // call window.focus, since we need to pop up a dialog
+ // and therefore need to be visible (to prevent user confusion)
+ window.focus();
+ let draftFolderURI = gCurrentIdentity.draftFolder;
+ let draftFolderName = MailUtils.getFolderForURI(draftFolderURI).prettyName;
+ switch (Services.prompt.confirmEx(window,
+ sComposeMsgsBundle.getString("saveDlogTitle"),
+ sComposeMsgsBundle.getFormattedString("saveDlogMessages3", [draftFolderName]),
+ (Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
+ null,
+ null,
+ sComposeMsgsBundle.getString("discardButtonLabel"),
+ null, {value:0}))
+ {
+ case 0: //Save
+ // we can close immediately if we already autosaved the draft
+ if (!gContentChanged && !gMsgCompose.bodyModified)
+ break;
+ gCloseWindowAfterSave = true;
+ GenericSendMessage(nsIMsgCompDeliverMode.AutoSaveAsDraft);
+ return false;
+ case 1: //Cancel
+ return false;
+ case 2: //Don't Save
+ // only delete the draft if we didn't start off editing a draft
+ if (!gEditingDraft && gAutoSaveKickedIn)
+ RemoveDraft();
+ break;
+ }
+ }
+
+ return true;
+}
+
+function RemoveDraft()
+{
+ try
+ {
+ var draftId = gMsgCompose.compFields.draftId;
+ var msgKey = draftId.substr(draftId.indexOf('#') + 1);
+ var folder = sRDF.GetResource(gMsgCompose.savedFolderURI);
+ try {
+ if (folder instanceof Ci.nsIMsgFolder)
+ {
+ let msg = folder.GetMessageHeader(msgKey);
+ folder.deleteMessages([msg], null, true, false, null, false);
+ }
+ }
+ catch (ex) // couldn't find header - perhaps an imap folder.
+ {
+ if (folder instanceof Ci.nsIMsgImapMailFolder)
+ {
+ const kImapMsgDeletedFlag = 0x0008;
+ folder.storeImapFlags(kImapMsgDeletedFlag, true, [msgKey], null);
+ }
+ }
+ } catch (ex) {}
+}
+
+function SetContentAndBodyAsUnmodified()
+{
+ gMsgCompose.bodyModified = false;
+ gContentChanged = false;
+}
+
+function MsgComposeCloseWindow()
+{
+ if (gMsgCompose)
+ gMsgCompose.CloseWindow();
+ else
+ window.close();
+}
+
+// attachedLocalFile must be a nsIFile
+function SetLastAttachDirectory(attachedLocalFile)
+{
+ try {
+ var file = attachedLocalFile.QueryInterface(Ci.nsIFile);
+ var parent = file.parent.QueryInterface(Ci.nsIFile);
+
+ Services.prefs.setComplexValue(kComposeAttachDirPrefName,
+ Ci.nsIFile, parent);
+ }
+ catch (ex) {
+ dump("error: SetLastAttachDirectory failed: " + ex + "\n");
+ }
+}
+
+function AttachFile()
+{
+ //Get file using nsIFilePicker and convert to URL
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(window, sComposeMsgsBundle.getString("chooseFileToAttach"),
+ nsIFilePicker.modeOpenMultiple);
+ let lastDirectory = GetLocalFilePref(kComposeAttachDirPrefName);
+ if (lastDirectory)
+ fp.displayDirectory = lastDirectory;
+
+ fp.appendFilters(nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.files) {
+ return;
+ }
+ try {
+ let firstAttachedFile = AttachFiles(fp.files);
+ if (firstAttachedFile) {
+ SetLastAttachDirectory(firstAttachedFile);
+ }
+ }
+ catch (ex) {
+ dump("failed to get attachments: " + ex + "\n");
+ }
+ });
+}
+
+function AttachFiles(attachments)
+{
+ if (!attachments || !attachments.hasMoreElements())
+ return null;
+
+ var firstAttachedFile = null;
+
+ while (attachments.hasMoreElements()) {
+ var currentFile = attachments.getNext().QueryInterface(Ci.nsIFile);
+
+ if (!firstAttachedFile) {
+ firstAttachedFile = currentFile;
+ }
+
+ var fileHandler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+ var currentAttachment = fileHandler.getURLSpecFromFile(currentFile);
+
+ if (!DuplicateFileCheck(currentAttachment)) {
+ var attachment = Cc["@mozilla.org/messengercompose/attachment;1"].createInstance(Ci.nsIMsgAttachment);
+ attachment.url = currentAttachment;
+ attachment.size = currentFile.fileSize;
+ AddAttachment(attachment);
+ gContentChanged = true;
+ }
+ }
+ return firstAttachedFile;
+}
+
+function AddAttachment(attachment)
+{
+ if (attachment && attachment.url)
+ {
+ var bucket = GetMsgAttachmentElement();
+ var item = document.createElement("listitem");
+
+ if (!attachment.name)
+ attachment.name = gMsgCompose.AttachmentPrettyName(attachment.url, attachment.urlCharset);
+
+ // for security reasons, don't allow *-message:// uris to leak out
+ // we don't want to reveal the .slt path (for mailbox://), or the username or hostname
+ var messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
+ if (messagePrefix.test(attachment.name))
+ attachment.name = sComposeMsgsBundle.getString("messageAttachmentSafeName");
+ else {
+ // for security reasons, don't allow mail protocol uris to leak out
+ // we don't want to reveal the .slt path (for mailbox://), or the username or hostname
+ var mailProtocol = /^file:|^mailbox:|^imap:|^s?news:/i;
+ if (mailProtocol.test(attachment.name))
+ attachment.name = sComposeMsgsBundle.getString("partAttachmentSafeName");
+ }
+
+ var nameAndSize = attachment.name;
+ if (attachment.size != -1)
+ nameAndSize += " (" + gMessenger.formatFileSize(attachment.size) + ")";
+ item.setAttribute("label", nameAndSize); //use for display only
+ item.attachment = attachment; //full attachment object stored here
+ try {
+ item.setAttribute("tooltiptext", decodeURI(attachment.url));
+ } catch(e) {
+ item.setAttribute("tooltiptext", attachment.url);
+ }
+ item.setAttribute("class", "listitem-iconic");
+ item.setAttribute("image", "moz-icon:" + attachment.url);
+ item.setAttribute("crop", "center");
+ bucket.appendChild(item);
+ }
+}
+
+function SelectAllAttachments()
+{
+ var bucketList = GetMsgAttachmentElement();
+ if (bucketList)
+ bucketList.selectAll();
+}
+
+function MessageHasAttachments()
+{
+ var bucketList = GetMsgAttachmentElement();
+ if (bucketList) {
+ return (bucketList && bucketList.hasChildNodes() && (bucketList == top.document.commandDispatcher.focusedElement));
+ }
+ return false;
+}
+
+function MessageGetNumSelectedAttachments()
+{
+ var bucketList = GetMsgAttachmentElement();
+ return (bucketList) ? bucketList.selectedItems.length : 0;
+}
+
+function AttachPage()
+{
+ var params = { action: "5", url: null };
+ window.openDialog("chrome://communicator/content/openLocation.xul",
+ "_blank", "chrome,close,titlebar,modal", params);
+ if (params.url)
+ {
+ var attachment =
+ Cc["@mozilla.org/messengercompose/attachment;1"]
+ .createInstance(Ci.nsIMsgAttachment);
+ attachment.url = params.url;
+ AddAttachment(attachment);
+ }
+}
+
+function DuplicateFileCheck(FileUrl)
+{
+ var bucket = GetMsgAttachmentElement();
+ for (let i = 0; i < bucket.childNodes.length; i++)
+ {
+ let attachment = bucket.childNodes[i].attachment;
+ if (attachment)
+ {
+ if (FileUrl == attachment.url)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function Attachments2CompFields(compFields)
+{
+ var bucket = GetMsgAttachmentElement();
+
+ //First, we need to clear all attachment in the compose fields
+ compFields.removeAttachments();
+
+ for (let i = 0; i < bucket.childNodes.length; i++)
+ {
+ let attachment = bucket.childNodes[i].attachment;
+ if (attachment)
+ compFields.addAttachment(attachment);
+ }
+}
+
+function RemoveAllAttachments()
+{
+ var child;
+ var bucket = GetMsgAttachmentElement();
+ while (bucket.hasChildNodes())
+ {
+ child = bucket.removeChild(bucket.lastChild);
+ // Let's release the attachment object hold by the node else it won't go away until the window is destroyed
+ child.attachment = null;
+ }
+}
+
+function RemoveSelectedAttachment()
+{
+ var child;
+ var bucket = GetMsgAttachmentElement();
+ if (bucket.selectedItems.length > 0) {
+ for (let i = bucket.selectedItems.length - 1; i >= 0; i--)
+ {
+ child = bucket.removeChild(bucket.selectedItems[i]);
+ // Let's release the attachment object hold by the node else it won't go away until the window is destroyed
+ child.attachment = null;
+ }
+ gContentChanged = true;
+ }
+}
+
+function RenameSelectedAttachment()
+{
+ var bucket = GetMsgAttachmentElement();
+ if (bucket.selectedItems.length != 1)
+ return; // not one attachment selected
+
+ var item = bucket.getSelectedItem(0);
+ var attachmentName = {value: item.attachment.name};
+ if (Services.prompt.prompt(
+ window,
+ sComposeMsgsBundle.getString("renameAttachmentTitle"),
+ sComposeMsgsBundle.getString("renameAttachmentMessage"),
+ attachmentName,
+ null,
+ {value: 0}))
+ {
+ var modifiedAttachmentName = attachmentName.value;
+ if (modifiedAttachmentName == "")
+ return; // name was not filled, bail out
+
+ var nameAndSize = modifiedAttachmentName;
+ if (item.attachment.size != -1)
+ nameAndSize += " (" + gMessenger.formatFileSize(item.attachment.size) + ")";
+ item.label = nameAndSize;
+ item.attachment.name = modifiedAttachmentName;
+ gContentChanged = true;
+ }
+}
+
+function FocusOnFirstAttachment()
+{
+ var bucketList = GetMsgAttachmentElement();
+
+ if (bucketList && bucketList.hasChildNodes())
+ bucketList.selectItem(bucketList.firstChild);
+}
+
+function AttachmentElementHasItems()
+{
+ var element = GetMsgAttachmentElement();
+ return element ? element.childNodes.length : 0;
+}
+
+function OpenSelectedAttachment()
+{
+ let bucket = document.getElementById("attachmentBucket");
+ if (bucket.selectedItems.length == 1) {
+ let attachmentUrl = bucket.getSelectedItem(0).attachment.url;
+
+ let messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
+ if (messagePrefix.test(attachmentUrl)) {
+ // We must be dealing with a forwarded attachment, treat this special.
+ let msgHdr = gMessenger.msgHdrFromURI(attachmentUrl);
+ if (msgHdr) {
+ MailUtils.openMessageInNewWindow(msgHdr);
+ }
+ } else {
+ // Turn the URL into a nsIURI object then open it.
+ let uri = Services.io.newURI(attachmentUrl);
+ if (uri) {
+ let channel = Services.io.newChannelFromURI(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ if (channel) {
+ let uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
+ uriLoader.openURI(channel, true, new nsAttachmentOpener());
+ }
+ }
+ }
+ } // if one attachment selected
+}
+
+function nsAttachmentOpener()
+{
+}
+
+nsAttachmentOpener.prototype =
+{
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIURIContentListener) ||
+ iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ doContent: function(contentType, isContentPreferred, request, contentHandler)
+ {
+ return false;
+ },
+
+ isPreferred: function(contentType, desiredContentType)
+ {
+ return false;
+ },
+
+ canHandleContent: function(contentType, isContentPreferred, desiredContentType)
+ {
+ return false;
+ },
+
+ getInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIDOMWindow)) {
+ return window;
+ }
+
+ if (iid.equals(Ci.nsIDocShell)) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ }
+
+ return this.QueryInterface(iid);
+ },
+
+ loadCookie: null,
+ parentContentListener: null
+}
+
+function DetermineHTMLAction(convertible)
+{
+ try {
+ gMsgCompose.expandMailingLists();
+ } catch(ex) {
+ dump("gMsgCompose.expandMailingLists failed: " + ex + "\n");
+ }
+
+ if (!gMsgCompose.composeHTML)
+ {
+ return nsIMsgCompSendFormat.PlainText;
+ }
+
+ if (gSendFormat == nsIMsgCompSendFormat.AskUser)
+ {
+ return gMsgCompose.determineHTMLAction(convertible);
+ }
+
+ return gSendFormat;
+}
+
+function DetermineConvertibility()
+{
+ if (!gMsgCompose.composeHTML)
+ return nsIMsgCompConvertible.Plain;
+
+ try {
+ return gMsgCompose.bodyConvertible();
+ } catch(ex) {}
+ return nsIMsgCompConvertible.No;
+}
+
+function LoadIdentity(startup)
+{
+ var identityElement = GetMsgIdentityElement();
+ var prevIdentity = gCurrentIdentity;
+
+ if (identityElement) {
+ identityElement.value = identityElement.selectedItem.value;
+
+ var idKey = identityElement.selectedItem.getAttribute("identitykey");
+ gCurrentIdentity = MailServices.accounts.getIdentity(idKey);
+
+ let accountKey = null;
+ if (identityElement.selectedItem)
+ accountKey = identityElement.selectedItem.getAttribute("accountkey");
+
+ let maxRecipients = awGetMaxRecipients();
+ for (let i = 1; i <= maxRecipients; i++)
+ {
+ let params = JSON.parse(awGetInputElement(i).searchParam);
+ params.idKey = idKey;
+ params.accountKey = accountKey;
+ awGetInputElement(i).searchParam = JSON.stringify(params);
+ }
+
+ if (!startup && prevIdentity && idKey != prevIdentity.key)
+ {
+ var prevReplyTo = prevIdentity.replyTo;
+ var prevCc = "";
+ var prevBcc = "";
+ var prevReceipt = prevIdentity.requestReturnReceipt;
+ var prevDSN = prevIdentity.requestDSN;
+ var prevAttachVCard = prevIdentity.attachVCard;
+
+ if (prevIdentity.doCc)
+ prevCc += prevIdentity.doCcList;
+
+ if (prevIdentity.doBcc)
+ prevBcc += prevIdentity.doBccList;
+
+ var newReplyTo = gCurrentIdentity.replyTo;
+ var newCc = "";
+ var newBcc = "";
+ var newReceipt = gCurrentIdentity.requestReturnReceipt;
+ var newDSN = gCurrentIdentity.requestDSN;
+ var newAttachVCard = gCurrentIdentity.attachVCard;
+
+ if (gCurrentIdentity.doCc)
+ newCc += gCurrentIdentity.doCcList;
+
+ if (gCurrentIdentity.doBcc)
+ newBcc += gCurrentIdentity.doBccList;
+
+ var needToCleanUp = false;
+ var msgCompFields = gMsgCompose.compFields;
+
+ if (!gReceiptOptionChanged &&
+ prevReceipt == msgCompFields.returnReceipt &&
+ prevReceipt != newReceipt)
+ {
+ msgCompFields.returnReceipt = newReceipt;
+ document.getElementById("returnReceiptMenu").setAttribute('checked',msgCompFields.returnReceipt);
+ }
+
+ if (!gDSNOptionChanged &&
+ prevDSN == msgCompFields.DSN &&
+ prevDSN != newDSN)
+ {
+ msgCompFields.DSN = newDSN;
+ document.getElementById("dsnMenu").setAttribute('checked',msgCompFields.DSN);
+ }
+
+ if (!gAttachVCardOptionChanged &&
+ prevAttachVCard == msgCompFields.attachVCard &&
+ prevAttachVCard != newAttachVCard)
+ {
+ msgCompFields.attachVCard = newAttachVCard;
+ document.getElementById("cmd_attachVCard").setAttribute('checked',msgCompFields.attachVCard);
+ }
+
+ if (newReplyTo != prevReplyTo)
+ {
+ needToCleanUp = true;
+ if (prevReplyTo != "")
+ awRemoveRecipients(msgCompFields, "addr_reply", prevReplyTo);
+ if (newReplyTo != "")
+ awAddRecipients(msgCompFields, "addr_reply", newReplyTo);
+ }
+
+ let toAddrs = new Set(msgCompFields.splitRecipients(msgCompFields.to, true));
+ let ccAddrs = new Set(msgCompFields.splitRecipients(msgCompFields.cc, true));
+
+ if (newCc != prevCc)
+ {
+ needToCleanUp = true;
+ if (prevCc)
+ awRemoveRecipients(msgCompFields, "addr_cc", prevCc);
+ if (newCc) {
+ // Ensure none of the Ccs are already in To.
+ let cc2 = msgCompFields.splitRecipients(newCc, true);
+ newCc = cc2.filter(x => !toAddrs.has(x)).join(", ");
+ awAddRecipients(msgCompFields, "addr_cc", newCc);
+ }
+ }
+
+ if (newBcc != prevBcc)
+ {
+ needToCleanUp = true;
+ if (prevBcc)
+ awRemoveRecipients(msgCompFields, "addr_bcc", prevBcc);
+ if (newBcc) {
+ // Ensure none of the Bccs are already in To or Cc.
+ let bcc2 = msgCompFields.splitRecipients(newBcc, true);
+ let toCcAddrs = new Set([...toAddrs, ...ccAddrs]);
+ newBcc = bcc2.filter(x => !toCcAddrs.has(x)).join(", ");
+ awAddRecipients(msgCompFields, "addr_bcc", newBcc);
+ }
+ }
+
+ if (needToCleanUp)
+ awCleanupRows();
+
+ try {
+ gMsgCompose.identity = gCurrentIdentity;
+ } catch (ex) { dump("### Cannot change the identity: " + ex + "\n");}
+
+ var event = document.createEvent('Events');
+ event.initEvent('compose-from-changed', false, true);
+ document.getElementById("msgcomposeWindow").dispatchEvent(event);
+
+ gComposeNotificationBar.clearIdentityWarning();
+ }
+
+ if (!startup) {
+ if (Services.prefs.getBoolPref("mail.autoComplete.highlightNonMatches"))
+ document.getElementById('addressCol2#1').highlightNonMatches = true;
+
+ // Only do this if we aren't starting up...
+ // It gets done as part of startup already.
+ addRecipientsToIgnoreList(gCurrentIdentity.fullAddress);
+ }
+ }
+}
+
+function setupAutocomplete()
+{
+ var autoCompleteWidget = document.getElementById("addressCol2#1");
+
+ // if the pref is set to turn on the comment column, honor it here.
+ // this element then gets cloned for subsequent rows, so they should
+ // honor it as well
+ //
+ if (Services.prefs.getBoolPref("mail.autoComplete.highlightNonMatches"))
+ autoCompleteWidget.highlightNonMatches = true;
+
+ if (Services.prefs.getIntPref("mail.autoComplete.commentColumn", 0) != 0)
+ autoCompleteWidget.showCommentColumn = true;
+}
+
+function subjectKeyPress(event)
+{
+ switch(event.keyCode) {
+ case KeyEvent.DOM_VK_TAB:
+ if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
+ SetMsgBodyFrameFocus();
+ event.preventDefault();
+ }
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ SetMsgBodyFrameFocus();
+ break;
+ }
+}
+
+function AttachmentBucketClicked(event)
+{
+ if (event.button != 0)
+ return;
+
+ if (event.originalTarget.localName == "listboxbody")
+ goDoCommand('cmd_attachFile');
+ else if (event.originalTarget.localName == "listitem" && event.detail == 2)
+ OpenSelectedAttachment();
+}
+
+// Content types supported in the attachmentBucketObserver.
+let flavours = [ "text/x-moz-message", "application/x-moz-file",
+ "text/x-moz-url", ];
+
+var attachmentBucketObserver = {
+ onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ let dataList = [];
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let types = Array.from(dt.mozTypesAt(i));
+ for (let flavour of flavours) {
+ if (types.includes(flavour)) {
+ let data = dt.mozGetDataAt(flavour, i);
+ if (data) {
+ dataList.push({ data, flavour });
+ }
+ break;
+ }
+ }
+ }
+
+ for (let { data, flavour } of dataList) {
+ let isValidAttachment = false;
+ let prettyName;
+ let size;
+
+ // We could be dropping an attachment of various flavours;
+ // check and do the right thing.
+ switch (flavour) {
+ case "application/x-moz-file": {
+ if (data instanceof Ci.nsIFile) {
+ size = data.fileSize;
+ }
+
+ try {
+ data = Services.io.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler)
+ .getURLSpecFromFile(data);
+ isValidAttachment = true;
+ } catch (e) {
+ Cu.reportError("Couldn't process the dragged file " +
+ data.leafName + ":" + e);
+ }
+ break;
+ }
+
+ case "text/x-moz-message": {
+ isValidAttachment = true;
+ let msgHdr = gMessenger.messageServiceFromURI(data)
+ .messageURIToMsgHdr(data);
+ prettyName = msgHdr.mime2DecodedSubject + ".eml";
+ size = msgHdr.messageSize;
+ break;
+ }
+
+ case "text/x-moz-url": {
+ let pieces = data.split("\n");
+ data = pieces[0];
+ if (pieces.length > 1) {
+ prettyName = pieces[1];
+ }
+ if (pieces.length > 2) {
+ size = parseInt(pieces[2]);
+ }
+
+ // If this is a URL (or selected text), check if it's a valid URL
+ // by checking if we can extract a scheme using Services.io.
+ // Don't attach invalid or mailto: URLs.
+ try {
+ let scheme = Services.io.extractScheme(data);
+ if (scheme != "mailto") {
+ isValidAttachment = true;
+ }
+ } catch (ex) {}
+ break;
+ }
+ }
+
+ if (isValidAttachment && !DuplicateFileCheck(data)) {
+ let attachment = Cc["@mozilla.org/messengercompose/attachment;1"]
+ .createInstance(Ci.nsIMsgAttachment);
+ attachment.url = data;
+ attachment.name = prettyName;
+
+ if (size !== undefined) {
+ attachment.size = size;
+ }
+
+ AddAttachment(attachment);
+ }
+ }
+
+ aEvent.stopPropagation();
+ },
+
+ onDragOver(aEvent) {
+ let dragSession = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService).getCurrentSession();
+ for (let flavour of flavours) {
+ if (dragSession.isDataFlavorSupported(flavour)) {
+ let attachmentBucket = GetMsgAttachmentElement();
+ attachmentBucket.setAttribute("dragover", "true");
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ break;
+ }
+ }
+ },
+
+ onDragExit(aEvent) {
+ let attachmentBucket = GetMsgAttachmentElement();
+ attachmentBucket.removeAttribute("dragover");
+ },
+};
+
+function DisplaySaveFolderDlg(folderURI)
+{
+ try
+ {
+ var showDialog = gCurrentIdentity.showSaveMsgDlg;
+ }
+ catch (e)
+ {
+ return;
+ }
+
+ if (showDialog){
+ let msgfolder = MailUtils.getFolderForURI(folderURI, true);
+ if (!msgfolder)
+ return;
+ var checkbox = {value:0};
+ var SaveDlgTitle = sComposeMsgsBundle.getString("SaveDialogTitle");
+ var dlgMsg = sComposeMsgsBundle.getFormattedString("SaveDialogMsg",
+ [msgfolder.name,
+ msgfolder.server.prettyName]);
+
+ var CheckMsg = sComposeMsgsBundle.getString("CheckMsg");
+ Services.prompt.alertCheck(window, SaveDlgTitle, dlgMsg, CheckMsg, checkbox);
+ try {
+ gCurrentIdentity.showSaveMsgDlg = !checkbox.value;
+ }//try
+ catch (e) {
+ return;
+ }//catch
+
+ }//if
+ return;
+}
+
+function SetMsgAddressingWidgetElementFocus()
+{
+ awSetFocusTo(awGetInputElement(awGetNumberOfRecipients()));
+}
+
+function SetMsgIdentityElementFocus()
+{
+ GetMsgIdentityElement().focus();
+}
+
+function SetMsgSubjectElementFocus()
+{
+ GetMsgSubjectElement().focus();
+}
+
+function SetMsgAttachmentElementFocus()
+{
+ GetMsgAttachmentElement().focus();
+ FocusOnFirstAttachment();
+}
+
+function SetMsgBodyFrameFocus()
+{
+ //window.content.focus(); fails to blur the currently focused element
+ document.commandDispatcher
+ .advanceFocusIntoSubtree(document.getElementById("appcontent"));
+}
+
+function GetMsgAddressingWidgetElement()
+{
+ if (!gMsgAddressingWidgetElement)
+ gMsgAddressingWidgetElement = document.getElementById("addressingWidget");
+
+ return gMsgAddressingWidgetElement;
+}
+
+function GetMsgIdentityElement()
+{
+ if (!gMsgIdentityElement)
+ gMsgIdentityElement = document.getElementById("msgIdentity");
+
+ return gMsgIdentityElement;
+}
+
+function GetMsgSubjectElement()
+{
+ if (!gMsgSubjectElement)
+ gMsgSubjectElement = document.getElementById("msgSubject");
+
+ return gMsgSubjectElement;
+}
+
+function GetMsgAttachmentElement()
+{
+ if (!gMsgAttachmentElement)
+ gMsgAttachmentElement = document.getElementById("attachmentBucket");
+
+ return gMsgAttachmentElement;
+}
+
+function GetMsgHeadersToolbarElement()
+{
+ if (!gMsgHeadersToolbarElement)
+ gMsgHeadersToolbarElement = document.getElementById("MsgHeadersToolbar");
+
+ return gMsgHeadersToolbarElement;
+}
+
+function IsMsgHeadersToolbarCollapsed()
+{
+ var element = GetMsgHeadersToolbarElement();
+ return element && element.collapsed;
+}
+
+function WhichElementHasFocus()
+{
+ var msgIdentityElement = GetMsgIdentityElement();
+ var msgAddressingWidgetElement = GetMsgAddressingWidgetElement();
+ var msgSubjectElement = GetMsgSubjectElement();
+ var msgAttachmentElement = GetMsgAttachmentElement();
+
+ if (top.document.commandDispatcher.focusedWindow == content)
+ return content;
+
+ var currentNode = top.document.commandDispatcher.focusedElement;
+ while (currentNode)
+ {
+ if (currentNode == msgIdentityElement ||
+ currentNode == msgAddressingWidgetElement ||
+ currentNode == msgSubjectElement ||
+ currentNode == msgAttachmentElement)
+ return currentNode;
+
+ currentNode = currentNode.parentNode;
+ }
+
+ return null;
+}
+
+// Function that performs the logic of switching focus from
+// one element to another in the mail compose window.
+// The default element to switch to when going in either
+// direction (shift or no shift key pressed), is the
+// AddressingWidgetElement.
+//
+// The only exception is when the MsgHeadersToolbar is
+// collapsed, then the focus will always be on the body of
+// the message.
+function SwitchElementFocus(event)
+{
+ var focusedElement = WhichElementHasFocus();
+
+ if (event && event.shiftKey)
+ {
+ if (IsMsgHeadersToolbarCollapsed())
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == gMsgAddressingWidgetElement)
+ SetMsgIdentityElementFocus();
+ else if (focusedElement == gMsgIdentityElement)
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == content)
+ {
+ // only set focus to the attachment element if there
+ // are any attachments.
+ if (AttachmentElementHasItems())
+ SetMsgAttachmentElementFocus();
+ else
+ SetMsgSubjectElementFocus();
+ }
+ else if (focusedElement == gMsgAttachmentElement)
+ SetMsgSubjectElementFocus();
+ else
+ SetMsgAddressingWidgetElementFocus();
+ }
+ else
+ {
+ if (IsMsgHeadersToolbarCollapsed())
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == gMsgAddressingWidgetElement)
+ SetMsgSubjectElementFocus();
+ else if (focusedElement == gMsgSubjectElement)
+ {
+ // only set focus to the attachment element if there
+ // are any attachments.
+ if (AttachmentElementHasItems())
+ SetMsgAttachmentElementFocus();
+ else
+ SetMsgBodyFrameFocus();
+ }
+ else if (focusedElement == gMsgAttachmentElement)
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == content)
+ SetMsgIdentityElementFocus();
+ else
+ SetMsgAddressingWidgetElementFocus();
+ }
+}
+
+function loadHTMLMsgPrefs()
+{
+ var fontFace = Services.prefs.getStringPref("msgcompose.font_face", "");
+ doStatefulCommand("cmd_fontFace", fontFace);
+
+ var fontSize = Services.prefs.getCharPref("msgcompose.font_size", "");
+ if (fontSize)
+ EditorSetFontSize(fontSize);
+
+ var bodyElement = GetBodyElement();
+
+ var textColor = Services.prefs.getCharPref("msgcompose.text_color", "");
+ if (!bodyElement.hasAttribute("text") && textColor)
+ {
+ bodyElement.setAttribute("text", textColor);
+ gDefaultTextColor = textColor;
+ document.getElementById("cmd_fontColor").setAttribute("state", textColor);
+ onFontColorChange();
+ }
+
+ var bgColor = Services.prefs.getCharPref("msgcompose.background_color", "");
+ if (!bodyElement.hasAttribute("bgcolor") && bgColor)
+ {
+ bodyElement.setAttribute("bgcolor", bgColor);
+ gDefaultBackgroundColor = bgColor;
+ document.getElementById("cmd_backgroundColor").setAttribute("state", bgColor);
+ onBackgroundColorChange();
+ }
+}
+
+function AutoSave()
+{
+ if (gMsgCompose.editor && (gContentChanged || gMsgCompose.bodyModified) &&
+ !gSendOrSaveOperationInProgress)
+ {
+ GenericSendMessage(nsIMsgCompDeliverMode.AutoSaveAsDraft);
+ gAutoSaveKickedIn = true;
+ }
+ gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
+}
+
+/**
+ * Helper function to remove a query part from a URL, so for example:
+ * ...?remove=xx&other=yy becomes ...?other=yy.
+ *
+ * @param aURL the URL from which to remove the query part
+ * @param aQuery the query part to remove
+ * @return the URL with the query part removed
+ */
+function removeQueryPart(aURL, aQuery)
+{
+ // Quick pre-check.
+ if (!aURL.includes(aQuery))
+ return aURL;
+
+ let indexQM = aURL.indexOf("?");
+ if (indexQM < 0)
+ return aURL;
+
+ let queryParts = aURL.substr(indexQM + 1).split("&");
+ let indexPart = queryParts.indexOf(aQuery);
+ if (indexPart < 0)
+ return aURL;
+ queryParts.splice(indexPart, 1);
+ return aURL.substr(0, indexQM + 1) + queryParts.join("&");
+}
+
+function InitEditor(editor)
+{
+ // Set the eEditorMailMask flag to avoid using content prefs for the spell
+ // checker, otherwise the dictionary setting in preferences is ignored and
+ // the dictionary is inconsistent between the subject and message body.
+ var eEditorMailMask = Ci.nsIEditor.eEditorMailMask;
+ editor.flags |= eEditorMailMask;
+ GetMsgSubjectElement().editor.flags |= eEditorMailMask;
+
+ // Control insertion of line breaks.
+ editor.returnInParagraphCreatesNewParagraph =
+ Services.prefs.getBoolPref("mail.compose.default_to_paragraph") ||
+ Services.prefs.getBoolPref("editor.CR_creates_new_p");
+ editor.document.execCommand("defaultparagraphseparator", false,
+ gMsgCompose.composeHTML &&
+ Services.prefs.getBoolPref("mail.compose.default_to_paragraph") ?
+ "p" : "br");
+
+ gMsgCompose.initEditor(editor, window.content);
+ InlineSpellCheckerUI.init(editor);
+ EnableInlineSpellCheck(Services.prefs.getBoolPref("mail.spellcheck.inline"));
+ document.getElementById("menu_inlineSpellCheck").setAttribute("disabled", !InlineSpellCheckerUI.canSpellCheck);
+
+ // Listen for spellchecker changes, set the document language to the
+ // dictionary picked by the user via the right-click menu in the editor.
+ document.addEventListener("spellcheck-changed", updateDocumentLanguage);
+
+ // XXX: the error event fires twice for each load. Why??
+ editor.document.body.addEventListener("error", function(event) {
+ if (event.target.localName != "img") {
+ return;
+ }
+
+ if (event.target.getAttribute("moz-do-not-send") == "true") {
+ return;
+ }
+
+ let src = event.target.src;
+
+ if (!src) {
+ return;
+ }
+
+ if (!/^file:/i.test(src)) {
+ // Check if this is a protocol that can fetch parts.
+ let protocol = src.substr(0, src.indexOf(":")).toLowerCase();
+ if (!(Services.io.getProtocolHandler(protocol) instanceof
+ Ci.nsIMsgMessageFetchPartService)) {
+ // Can't fetch parts, don't try to load.
+ return;
+ }
+ }
+
+ if (event.target.classList.contains("loading-internal")) {
+ // We're already loading this, or tried so unsuccesfully.
+ return;
+ }
+
+ if (gOriginalMsgURI) {
+ let msgSvc = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger)
+ .messageServiceFromURI(gOriginalMsgURI);
+ let originalMsgNeckoURI = msgSvc.getUrlForUri(gOriginalMsgURI);
+
+ if (src.startsWith(removeQueryPart(originalMsgNeckoURI.spec,
+ "type=application/x-message-display"))) {
+ // Reply/Forward/Edit Draft/Edit as New can contain references to
+ // images in the original message. Load those and make them data: URLs
+ // now.
+ event.target.classList.add("loading-internal");
+ try {
+ loadBlockedImage(src);
+ } catch (e) {
+ // Couldn't load the referenced image.
+ Cu.reportError(e);
+ }
+ }
+ else {
+ // Appears to reference a random message. Notify and keep blocking.
+ gComposeNotificationBar.setBlockedContent(src);
+ }
+ }
+ else {
+ // For file:, and references to parts of random messages, show the
+ // blocked content notification.
+ gComposeNotificationBar.setBlockedContent(src);
+ }
+ }, true);
+
+ // Convert mailnews URL back to data: URL.
+ let background = editor.document.body.background;
+ if (background && gOriginalMsgURI) {
+ // Check that background has the same URL as the message itself.
+ let msgSvc = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger)
+ .messageServiceFromURI(gOriginalMsgURI);
+ let originalMsgNeckoURI = msgSvc.getUrlForUri(gOriginalMsgURI);
+
+ if (background.startsWith(
+ removeQueryPart(originalMsgNeckoURI.spec,
+ "type=application/x-message-display"))) {
+ try {
+ editor.document.body.background = loadBlockedImage(background, true);
+ } catch (e) {
+ // Couldn't load the referenced image.
+ Cu.reportError(e);
+ }
+ }
+ }
+}
+
+/**
+ * The event listener for the "spellcheck-changed" event updates
+ * the document language.
+ */
+function updateDocumentLanguage(event)
+{
+ document.documentElement.setAttribute("lang", event.detail.dictionary);
+}
+
+function EnableInlineSpellCheck(aEnableInlineSpellCheck)
+{
+ InlineSpellCheckerUI.enabled = aEnableInlineSpellCheck;
+ GetMsgSubjectElement().setAttribute("spellcheck", aEnableInlineSpellCheck);
+}
+
+function getMailToolbox()
+{
+ return document.getElementById("compose-toolbox");
+}
+
+function MailToolboxCustomizeInit()
+{
+ if (document.commandDispatcher.focusedWindow == content)
+ window.focus();
+ disableEditableFields();
+ GetMsgHeadersToolbarElement().setAttribute("moz-collapsed", true);
+ document.getElementById("compose-toolbar-sizer").setAttribute("moz-collapsed", true);
+ document.getElementById("content-frame").setAttribute("moz-collapsed", true);
+ toolboxCustomizeInit("mail-menubar");
+}
+
+function MailToolboxCustomizeDone(aToolboxChanged)
+{
+ toolboxCustomizeDone("mail-menubar", getMailToolbox(), aToolboxChanged);
+ GetMsgHeadersToolbarElement().removeAttribute("moz-collapsed");
+ document.getElementById("compose-toolbar-sizer").removeAttribute("moz-collapsed");
+ document.getElementById("content-frame").removeAttribute("moz-collapsed");
+ enableEditableFields();
+ SetMsgBodyFrameFocus();
+}
+
+function MailToolboxCustomizeChange(aEvent)
+{
+ toolboxCustomizeChange(getMailToolbox(), aEvent);
+}
+
+/**
+ * Object to handle message related notifications that are showing in a
+ * notificationbox below the composed message content.
+ */
+var gComposeNotificationBar = {
+
+ get notificationBar() {
+ delete this.notificationBar;
+ return this.notificationBar = document.getElementById("attachmentNotificationBox");
+ },
+
+ setBlockedContent: function(aBlockedURI) {
+ let brandName = sBrandBundle.getString("brandShortName");
+ let buttonLabel = sComposeMsgsBundle.getString("blockedContentPrefLabel");
+ let buttonAccesskey = sComposeMsgsBundle.getString("blockedContentPrefAccesskey");
+
+ let buttons = [{
+ label: buttonLabel,
+ accessKey: buttonAccesskey,
+ popup: "blockedContentOptions",
+ callback: function(aNotification, aButton) {
+ return true; // keep notification open
+ }
+ }];
+
+ // The popup value is a space separated list of all the blocked urls.
+ let popup = document.getElementById("blockedContentOptions");
+ let urls = popup.value ? popup.value.split(" ") : [];
+ if (!urls.includes(aBlockedURI)) {
+ urls.push(aBlockedURI);
+ }
+ popup.value = urls.join(" ");
+
+ let msg = sComposeMsgsBundle.getFormattedString("blockedContentMessage",
+ [brandName, brandName]);
+ msg = PluralForm.get(urls.length, msg);
+
+ if (!this.isShowingBlockedContentNotification()) {
+ this.notificationBar
+ .appendNotification(msg, "blockedContent", null,
+ this.notificationBar.PRIORITY_WARNING_MEDIUM,
+ buttons);
+ }
+ else {
+ this.notificationBar.getNotificationWithValue("blockedContent")
+ .setAttribute("label", msg);
+ }
+ },
+
+ isShowingBlockedContentNotification: function() {
+ return !!this.notificationBar.getNotificationWithValue("blockedContent");
+ },
+
+ clearBlockedContentNotification: function() {
+ this.notificationBar.removeNotification(
+ this.notificationBar.getNotificationWithValue("blockedContent"));
+ },
+
+ clearNotifications: function(aValue) {
+ this.notificationBar.removeAllNotifications(true);
+ },
+
+ setIdentityWarning: function(aIdentityName) {
+ if (!this.notificationBar.getNotificationWithValue("identityWarning")) {
+ let text = sComposeMsgsBundle.getString("identityWarning").split("%S");
+ let label = new DocumentFragment();
+ label.appendChild(document.createTextNode(text[0]));
+ label.appendChild(document.createElement("b"));
+ label.lastChild.appendChild(document.createTextNode(aIdentityName));
+ label.appendChild(document.createTextNode(text[1]));
+ this.notificationBar.appendNotification(label, "identityWarning", null,
+ this.notificationBar.PRIORITY_WARNING_HIGH, null);
+ }
+ },
+
+ clearIdentityWarning: function() {
+ let idWarning = this.notificationBar.getNotificationWithValue("identityWarning");
+ if (idWarning)
+ this.notificationBar.removeNotification(idWarning);
+ }
+};
+
+/**
+ * Populate the menuitems of what blocked content to unblock.
+ */
+function onBlockedContentOptionsShowing(aEvent) {
+ let urls = aEvent.target.value ? aEvent.target.value.split(" ") : [];
+
+ // Out with the old...
+ let childNodes = aEvent.target.childNodes;
+ for (let i = childNodes.length - 1; i >= 0; i--) {
+ childNodes[i].remove();
+ }
+
+ // ... and in with the new.
+ for (let url of urls) {
+ let menuitem = document.createElement("menuitem");
+ let fString = sComposeMsgsBundle.getFormattedString("blockedAllowResource",
+ [url]);
+ menuitem.setAttribute("label", fString);
+ menuitem.setAttribute("crop", "center");
+ menuitem.setAttribute("value", url);
+ menuitem.setAttribute("oncommand",
+ "onUnblockResource(this.value, this.parentNode);");
+ aEvent.target.appendChild(menuitem);
+ }
+}
+
+/**
+ * Handle clicking the "Load <url>" in the blocked content notification bar.
+ * @param {String} aURL - the URL that was unblocked
+ * @param {Node} aNode - the node holding as value the URLs of the blocked
+ * resources in the message (space separated).
+ */
+function onUnblockResource(aURL, aNode) {
+ try {
+ loadBlockedImage(aURL);
+ } catch (e) {
+ // Couldn't load the referenced image.
+ Cu.reportError(e);
+ } finally {
+ // Remove it from the list on success and failure.
+ let urls = aNode.value.split(" ");
+ for (let i = 0; i < urls.length; i++) {
+ if (urls[i] == aURL) {
+ urls.splice(i, 1);
+ aNode.value = urls.join(" ");
+ if (urls.length == 0) {
+ gComposeNotificationBar.clearBlockedContentNotification();
+ }
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Convert the blocked content to a data URL and swap the src to that for the
+ * elements that were using it.
+ *
+ * @param {String} aURL - (necko) URL to unblock
+ * @param {Bool} aReturnDataURL - return data: URL instead of processing image
+ * @return {String} the image as data: URL.
+ * @throw Error() if reading the data failed
+ */
+function loadBlockedImage(aURL, aReturnDataURL = false) {
+ let filename;
+ if (/^(file|chrome):/i.test(aURL)) {
+ filename = aURL.substr(aURL.lastIndexOf("/") + 1);
+ }
+ else {
+ let fnMatch = /[?&;]filename=([^?&]+)/.exec(aURL);
+ filename = (fnMatch && fnMatch[1]) || "";
+ }
+
+ filename = decodeURIComponent(filename);
+ let uri = Services.io.newURI(aURL);
+ let contentType;
+ if (filename) {
+ try {
+ contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromURI(uri);
+ } catch (ex) {
+ contentType = "image/png";
+ }
+
+ if (!contentType.startsWith("image/")) {
+ // Unsafe to unblock this. It would just be garbage either way.
+ throw new Error("Won't unblock; URL=" + aURL +
+ ", contentType=" + contentType);
+ }
+ }
+ else {
+ // Assuming image/png is the best we can do.
+ contentType = "image/png";
+ }
+
+ let channel =
+ Services.io.newChannelFromURI(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+
+ let inputStream = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(inputStream);
+ let streamData = "";
+ try {
+ while (stream.available() > 0) {
+ streamData += stream.readBytes(stream.available());
+ }
+ } catch(e) {
+ stream.close();
+ throw new Error("Couln't read all data from URL=" + aURL + " (" + e +")");
+ }
+ stream.close();
+
+ let encoded = btoa(streamData);
+ let dataURL = "data:" + contentType +
+ (filename ? ";filename=" + encodeURIComponent(filename) : "") +
+ ";base64," + encoded;
+
+ if (aReturnDataURL) {
+ return dataURL;
+ }
+
+ let editor = GetCurrentEditor();
+ for (let img of editor.document.images) {
+ if (img.src == aURL) {
+ img.src = dataURL; // Swap to data URL.
+ img.classList.remove("loading-internal");
+ }
+ }
+}
diff --git a/comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js b/comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js
new file mode 100644
index 0000000000..38cd1f5ecc
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js
@@ -0,0 +1,1167 @@
+/* -*- 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");
+
+top.MAX_RECIPIENTS = 1; /* for the initial listitem created in the XUL */
+
+var inputElementType = "";
+var selectElementType = "";
+var selectElementIndexTable = null;
+
+var gNumberOfCols = 0;
+
+var gDragService = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService);
+
+/**
+ * global variable inherited from MsgComposeCommands.js
+ *
+ var gMsgCompose;
+ */
+
+function awGetMaxRecipients()
+{
+ return top.MAX_RECIPIENTS;
+}
+
+function awGetNumberOfCols()
+{
+ if (gNumberOfCols == 0)
+ {
+ var listbox = document.getElementById('addressingWidget');
+ var listCols = listbox.getElementsByTagName('listcol');
+ gNumberOfCols = listCols.length;
+ if (!gNumberOfCols)
+ gNumberOfCols = 1; /* if no cols defined, that means we have only one! */
+ }
+
+ return gNumberOfCols;
+}
+
+function awInputElementName()
+{
+ if (inputElementType == "")
+ inputElementType = document.getElementById("addressCol2#1").localName;
+ return inputElementType;
+}
+
+function awSelectElementName()
+{
+ if (selectElementType == "")
+ selectElementType = document.getElementById("addressCol1#1").localName;
+ return selectElementType;
+}
+
+// TODO: replace awGetSelectItemIndex with recipient type index constants
+
+function awGetSelectItemIndex(itemData)
+{
+ if (selectElementIndexTable == null)
+ {
+ selectElementIndexTable = new Object();
+ var selectElem = document.getElementById("addressCol1#1");
+ for (var i = 0; i < selectElem.childNodes[0].childNodes.length; i ++)
+ {
+ var aData = selectElem.childNodes[0].childNodes[i].getAttribute("value");
+ selectElementIndexTable[aData] = i;
+ }
+ }
+ return selectElementIndexTable[itemData];
+}
+
+function Recipients2CompFields(msgCompFields)
+{
+ if (!msgCompFields) {
+ throw new Error("Message Compose Error: msgCompFields is null (ExtractRecipients)");
+ return;
+ }
+
+ var i = 1;
+ var addrTo = "";
+ var addrCc = "";
+ var addrBcc = "";
+ var addrReply = "";
+ var addrNg = "";
+ var addrFollow = "";
+ var to_Sep = "";
+ var cc_Sep = "";
+ var bcc_Sep = "";
+ var reply_Sep = "";
+ var ng_Sep = "";
+ var follow_Sep = "";
+
+ var recipientType;
+ var inputField;
+ var fieldValue;
+ var recipient;
+ while ((inputField = awGetInputElement(i)))
+ {
+ fieldValue = inputField.value;
+
+ if (fieldValue != "")
+ {
+ recipientType = awGetPopupElement(i).value;
+ recipient = null;
+
+ switch (recipientType)
+ {
+ case "addr_to" :
+ case "addr_cc" :
+ case "addr_bcc" :
+ case "addr_reply" :
+ try {
+ let headerParser = MailServices.headerParser;
+ recipient =
+ headerParser.makeFromDisplayAddress(fieldValue)
+ .map(fullValue => headerParser.makeMimeAddress(
+ fullValue.name,
+ fullValue.email))
+ .join(", ");
+ } catch (ex) {
+ recipient = fieldValue;
+ }
+ break;
+ }
+
+ switch (recipientType)
+ {
+ case "addr_to" : addrTo += to_Sep + recipient; to_Sep = ","; break;
+ case "addr_cc" : addrCc += cc_Sep + recipient; cc_Sep = ","; break;
+ case "addr_bcc" : addrBcc += bcc_Sep + recipient; bcc_Sep = ","; break;
+ case "addr_reply" : addrReply += reply_Sep + recipient; reply_Sep = ","; break;
+ case "addr_newsgroups" : addrNg += ng_Sep + fieldValue; ng_Sep = ","; break;
+ case "addr_followup" : addrFollow += follow_Sep + fieldValue; follow_Sep = ","; break;
+ case "addr_other":
+ let headerName = awGetPopupElement(i).label;
+ headerName = headerName.substring(0, headerName.indexOf(':'));
+ msgCompFields.setRawHeader(headerName, fieldValue, null);
+ break;
+ }
+ }
+ i ++;
+ }
+
+ msgCompFields.to = addrTo;
+ msgCompFields.cc = addrCc;
+ msgCompFields.bcc = addrBcc;
+ msgCompFields.replyTo = addrReply;
+ msgCompFields.newsgroups = addrNg;
+ msgCompFields.followupTo = addrFollow;
+}
+
+function CompFields2Recipients(msgCompFields)
+{
+ if (msgCompFields) {
+ var listbox = document.getElementById('addressingWidget');
+ var newListBoxNode = listbox.cloneNode(false);
+ var listBoxColsClone = listbox.firstChild.cloneNode(true);
+ newListBoxNode.appendChild(listBoxColsClone);
+ let templateNode = listbox.querySelector("listitem");
+ // dump("replacing child in comp fields 2 recips \n");
+ listbox.parentNode.replaceChild(newListBoxNode, listbox);
+
+ top.MAX_RECIPIENTS = 0;
+ var msgReplyTo = msgCompFields.replyTo;
+ var msgTo = msgCompFields.to;
+ var msgCC = msgCompFields.cc;
+ var msgBCC = msgCompFields.bcc;
+ var msgNewsgroups = msgCompFields.newsgroups;
+ var msgFollowupTo = msgCompFields.followupTo;
+ var havePrimaryRecipient = false;
+ if (msgReplyTo)
+ awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgReplyTo, false),
+ "addr_reply", newListBoxNode, templateNode);
+ if (msgTo) {
+ var rcp = msgCompFields.splitRecipients(msgTo, false);
+ if (rcp.length)
+ {
+ awSetInputAndPopupFromArray(rcp, "addr_to", newListBoxNode, templateNode);
+ havePrimaryRecipient = true;
+ }
+ }
+ if (msgCC)
+ awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgCC, false),
+ "addr_cc", newListBoxNode, templateNode);
+ if (msgBCC)
+ awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgBCC, false),
+ "addr_bcc", newListBoxNode, templateNode);
+ if (msgNewsgroups) {
+ awSetInputAndPopup(msgNewsgroups, "addr_newsgroups", newListBoxNode, templateNode);
+ havePrimaryRecipient = true;
+ }
+ if(msgFollowupTo)
+ awSetInputAndPopup(msgFollowupTo, "addr_followup", newListBoxNode, templateNode);
+
+ // If it's a new message, we need to add an extra empty recipient.
+ if (!havePrimaryRecipient)
+ _awSetInputAndPopup("", "addr_to", newListBoxNode, templateNode);
+ awFitDummyRows(2);
+
+ // CompFields2Recipients is called whenever a user replies or edits an existing message.
+ // We want to add all of the recipients for this message to the ignore list for spell check
+ let currentAddress = gCurrentIdentity ? gCurrentIdentity.fullAddress : "";
+ addRecipientsToIgnoreList([currentAddress,msgTo,msgCC,msgBCC].filter(adr => adr).join(", "));
+ }
+}
+
+function awSetInputAndPopupId(inputElem, popupElem, rowNumber)
+{
+ popupElem.id = "addressCol1#" + rowNumber;
+ inputElem.id = "addressCol2#" + rowNumber;
+ inputElem.setAttribute("aria-labelledby", popupElem.id);
+}
+
+function awSetInputAndPopupValue(inputElem, inputValue, popupElem, popupValue, rowNumber)
+{
+ inputElem.value = inputValue.trimLeft();
+
+ popupElem.selectedItem = popupElem.childNodes[0].childNodes[awGetSelectItemIndex(popupValue)];
+
+ if (rowNumber >= 0)
+ awSetInputAndPopupId(inputElem, popupElem, rowNumber);
+
+ _awSetAutoComplete(popupElem, inputElem);
+
+ onRecipientsChanged(true);
+}
+
+function _awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode)
+{
+ top.MAX_RECIPIENTS++;
+
+ var newNode = templateNode.cloneNode(true);
+ parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ var select = newNode.getElementsByTagName(awSelectElementName());
+
+ if (input && input.length == 1 && select && select.length == 1)
+ awSetInputAndPopupValue(input[0], inputValue, select[0], popupValue, top.MAX_RECIPIENTS)
+}
+
+function awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode)
+{
+ if ( inputValue && popupValue )
+ {
+ var addressArray = inputValue.split(",");
+
+ for ( var index = 0; index < addressArray.length; index++ )
+ _awSetInputAndPopup(addressArray[index], popupValue, parentNode, templateNode);
+ }
+}
+
+function awSetInputAndPopupFromArray(inputArray, popupValue, parentNode, templateNode)
+{
+ if (popupValue)
+ {
+ for (let recipient of inputArray)
+ _awSetInputAndPopup(recipient, popupValue, parentNode, templateNode);
+ }
+}
+
+function awRemoveRecipients(msgCompFields, recipientType, recipientsList)
+{
+ if (!msgCompFields)
+ return;
+
+ var recipientArray = msgCompFields.splitRecipients(recipientsList, false);
+
+ for (var index = 0; index < recipientArray.length; index++)
+ for (var row = 1; row <= top.MAX_RECIPIENTS; row ++)
+ {
+ var popup = awGetPopupElement(row);
+ if (popup.value == recipientType) {
+ var input = awGetInputElement(row);
+ if (input.value == recipientArray[index])
+ {
+ awSetInputAndPopupValue(input, "", popup, "addr_to", -1);
+ break;
+ }
+ }
+ }
+}
+
+function awAddRecipients(msgCompFields, recipientType, recipientsList)
+{
+ if (!msgCompFields)
+ return;
+
+ var recipientArray = msgCompFields.splitRecipients(recipientsList, false);
+
+ for (var index = 0; index < recipientArray.length; index++)
+ awAddRecipient(recipientType, recipientArray[index]);
+}
+
+// this was broken out of awAddRecipients so it can be re-used...adds a new row matching recipientType and
+// drops in the single address.
+function awAddRecipient(recipientType, address)
+{
+ for (var row = 1; row <= top.MAX_RECIPIENTS; row ++)
+ {
+ if (awGetInputElement(row).value == "")
+ break;
+ }
+
+ if (row > top.MAX_RECIPIENTS)
+ awAppendNewRow(false);
+
+ awSetInputAndPopupValue(awGetInputElement(row), address, awGetPopupElement(row), recipientType, row);
+
+ /* be sure we still have an empty row left at the end */
+ if (row == top.MAX_RECIPIENTS)
+ {
+ awAppendNewRow(true);
+ awSetInputAndPopupValue(awGetInputElement(top.MAX_RECIPIENTS), "", awGetPopupElement(top.MAX_RECIPIENTS), recipientType, top.MAX_RECIPIENTS);
+ }
+
+ // add the recipient to our spell check ignore list
+ addRecipientsToIgnoreList(address);
+}
+
+function awTestRowSequence()
+{
+ /*
+ This function is for debug and testing purpose only, normal users should not run it!
+
+ Everytime we insert or delete a row, we must be sure we didn't break the ID sequence of
+ the addressing widget rows. This function will run a quick test to see if the sequence still ok
+
+ You need to define the pref mail.debug.test_addresses_sequence to true in order to activate it
+ */
+
+ var test_sequence;
+ if (Services.prefs.getPrefType("mail.debug.test_addresses_sequence") == Ci.nsIPrefBranch.PREF_BOOL)
+ test_sequence = Services.prefs.getBoolPref("mail.debug.test_addresses_sequence");
+ if (!test_sequence)
+ return true;
+
+ /* debug code to verify the sequence still good */
+
+ var listbox = document.getElementById('addressingWidget');
+ var listitems = listbox.getElementsByTagName('listitem');
+ if (listitems.length >= top.MAX_RECIPIENTS )
+ {
+ for (var i = 1; i <= listitems.length; i ++)
+ {
+ var item = listitems [i - 1];
+ let inputID = item.querySelector(awInputElementName()).id.split("#")[1];
+ let popupID = item.querySelector(awSelectElementName()).id.split("#")[1];
+ if (inputID != i || popupID != i)
+ {
+ dump("#ERROR: sequence broken at row " + i + ", inputID=" + inputID + ", popupID=" + popupID + "\n");
+ return false;
+ }
+ dump("---SEQUENCE OK---\n");
+ return true;
+ }
+ }
+ else
+ dump("#ERROR: listitems.length(" + listitems.length + ") < top.MAX_RECIPIENTS(" + top.MAX_RECIPIENTS + ")\n");
+
+ return false;
+}
+
+function awCleanupRows()
+{
+ var maxRecipients = top.MAX_RECIPIENTS;
+ var rowID = 1;
+
+ for (var row = 1; row <= maxRecipients; row ++)
+ {
+ var inputElem = awGetInputElement(row);
+ if (inputElem.value == "" && row < maxRecipients)
+ awRemoveRow(awGetRowByInputElement(inputElem));
+ else
+ {
+ awSetInputAndPopupId(inputElem, awGetPopupElement(row), rowID);
+ rowID ++;
+ }
+ }
+
+ awTestRowSequence();
+}
+
+function awDeleteRow(rowToDelete)
+{
+ /* When we delete a row, we must reset the id of others row in order to not break the sequence */
+ var maxRecipients = top.MAX_RECIPIENTS;
+ awRemoveRow(rowToDelete);
+
+ // assume 2 column update (input and popup)
+ for (var row = rowToDelete + 1; row <= maxRecipients; row ++)
+ awSetInputAndPopupId(awGetInputElement(row), awGetPopupElement(row), (row-1));
+
+ awTestRowSequence();
+}
+
+function awClickEmptySpace(target, setFocus)
+{
+ if (target == null ||
+ (target.localName != "listboxbody" &&
+ target.localName != "listcell" &&
+ target.localName != "listitem"))
+ return;
+
+ let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+
+ if ( lastInput && lastInput.value )
+ awAppendNewRow(setFocus);
+ else if (setFocus)
+ awSetFocusTo(lastInput);
+}
+
+function awReturnHit(inputElement)
+{
+ let row = awGetRowByInputElement(inputElement);
+ let nextInput = awGetInputElement(row+1);
+
+ if ( !nextInput )
+ {
+ if ( inputElement.value )
+ awAppendNewRow(true);
+ else // No address entered, switch to Subject field
+ {
+ var subjectField = document.getElementById( 'msgSubject' );
+ subjectField.select();
+ subjectField.focus();
+ }
+ }
+ else
+ {
+ nextInput.select();
+ awSetFocusTo(nextInput);
+ }
+
+ // be sure to add the recipient to our ignore list
+ // when the user hits enter in an autocomplete widget...
+ addRecipientsToIgnoreList(inputElement.value);
+}
+
+function awDeleteHit(inputElement)
+{
+ let row = awGetRowByInputElement(inputElement);
+
+ /* 1. don't delete the row if it's the last one remaining, just reset it! */
+ if (top.MAX_RECIPIENTS <= 1)
+ {
+ inputElement.value = "";
+ return;
+ }
+
+ /* 2. Set the focus to the previous field if possible */
+ // Note: awSetFocusTo() is asynchronous, i.e. we'll focus after row removal.
+ if (row > 1)
+ awSetFocusTo(awGetInputElement(row - 1))
+ else
+ awSetFocusTo(awGetInputElement(2))
+
+ /* 3. Delete the row */
+ awDeleteRow(row);
+}
+
+function awAppendNewRow(setFocus)
+{
+ var listbox = document.getElementById('addressingWidget');
+ var listitem1 = awGetListItem(1);
+
+ if ( listbox && listitem1 )
+ {
+ var lastRecipientType = awGetPopupElement(top.MAX_RECIPIENTS).value;
+
+ var nextDummy = awGetNextDummyRow();
+ var newNode = listitem1.cloneNode(true);
+ if (nextDummy)
+ listbox.replaceChild(newNode, nextDummy);
+ else
+ listbox.appendChild(newNode);
+
+ top.MAX_RECIPIENTS++;
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ if ( input && input.length == 1 )
+ {
+ input[0].value = "";
+
+ // We always clone the first row. The problem is that the first row
+ // could be focused. When we clone that row, we end up with a cloned
+ // XUL textbox that has a focused attribute set. Therefore we think
+ // we're focused and don't properly refocus. The best solution to this
+ // would be to clone a template row that didn't really have any presentation,
+ // rather than using the real visible first row of the listbox.
+ //
+ // For now we'll just put in a hack that ensures the focused attribute
+ // is never copied when the node is cloned.
+ if (input[0].getAttribute('focused') != '')
+ input[0].removeAttribute('focused');
+ }
+ var select = newNode.getElementsByTagName(awSelectElementName());
+ if ( select && select.length == 1 )
+ {
+ // It only makes sense to clone some field types; others
+ // should not be cloned, since it just makes the user have
+ // to go to the trouble of selecting something else. In such
+ // cases let's default to 'To' (a reasonable default since
+ // we already default to 'To' on the first dummy field of
+ // a new message).
+ switch (lastRecipientType)
+ {
+ case "addr_reply":
+ case "addr_other":
+ select[0].selectedIndex = awGetSelectItemIndex("addr_to");
+ break;
+ case "addr_followup":
+ select[0].selectedIndex = awGetSelectItemIndex("addr_newsgroups");
+ break;
+ default:
+ // e.g. "addr_to","addr_cc","addr_bcc","addr_newsgroups":
+ select[0].selectedIndex = awGetSelectItemIndex(lastRecipientType);
+ }
+
+ awSetInputAndPopupId(input[0], select[0], top.MAX_RECIPIENTS);
+
+ if (input)
+ _awSetAutoComplete(select[0], input[0]);
+ }
+
+ // Focus the new input widget.
+ if (setFocus && input[0] )
+ awSetFocusTo(input[0]);
+ }
+}
+
+// functions for accessing the elements in the addressing widget
+
+/**
+ * Returns the recipient type popup for a row.
+ *
+ * @param row Index of the recipient row to return. Starts at 1.
+ * @return This returns the menulist (not its child menupopup), despite the
+ * function name.
+ */
+function awGetPopupElement(row)
+{
+ return document.getElementById("addressCol1#" + row);
+}
+
+/**
+ * Returns the recipient inputbox for a row.
+ *
+ * @param row Index of the recipient row to return. Starts at 1.
+ * @return This returns the textbox element.
+ */
+function awGetInputElement(row)
+{
+ return document.getElementById("addressCol2#" + row);
+}
+
+function awGetElementByCol(row, col)
+{
+ var colID = "addressCol" + col + "#" + row;
+ return document.getElementById(colID);
+}
+
+function awGetListItem(row)
+{
+ var listbox = document.getElementById('addressingWidget');
+
+ if ( listbox && row > 0)
+ {
+ var listitems = listbox.getElementsByTagName('listitem');
+ if ( listitems && listitems.length >= row )
+ return listitems[row-1];
+ }
+ return 0;
+}
+
+function awGetRowByInputElement(inputElement)
+{
+ var row = 0;
+ if (inputElement) {
+ var listitem = inputElement.parentNode.parentNode;
+ while (listitem) {
+ if (listitem.localName == "listitem")
+ ++row;
+ listitem = listitem.previousSibling;
+ }
+ }
+ return row;
+}
+
+
+// Copy Node - copy this node and insert ahead of the (before) node. Append to end if before=0
+function awCopyNode(node, parentNode, beforeNode)
+{
+ var newNode = node.cloneNode(true);
+
+ if ( beforeNode )
+ parentNode.insertBefore(newNode, beforeNode);
+ else
+ parentNode.appendChild(newNode);
+
+ return newNode;
+}
+
+// remove row
+
+function awRemoveRow(row)
+{
+ awGetListItem(row).remove();
+ awFitDummyRows();
+
+ top.MAX_RECIPIENTS --;
+}
+
+/**
+ * Set focus to the specified element, typically a recipient input element.
+ * We do this asynchronusly to allow other processes like adding or removing rows
+ * to complete before shifting focus.
+ *
+ * @param element the element to receive focus asynchronously
+ */
+function awSetFocusTo(element) {
+ // Remember the (input) element to focus for asynchronous focusing, so that we
+ // play safe if this gets called again and the original element gets removed
+ // before we can focus it.
+ top.awInputToFocus = element;
+ setTimeout(_awSetFocusTo, 0);
+}
+
+function _awSetFocusTo() {
+ top.awInputToFocus.focus();
+}
+
+// Deprecated - use awSetFocusTo() instead.
+// ### TODO: This function should be removed if we're sure addons aren't using it.
+function awSetFocus(row, inputElement) {
+ awSetFocusTo(inputElement);
+}
+
+function awTabFromRecipient(element, event) {
+ var row = awGetRowByInputElement(element);
+ if (!event.shiftKey && row < top.MAX_RECIPIENTS) {
+ var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
+ var listBox = document.getElementById("addressingWidget");
+ listBox.listBoxObject.ensureIndexIsVisible(listBoxRow + 1);
+ }
+
+ // be sure to add the recipient to our ignore list
+ // when the user tabs out of an autocomplete line...
+ addRecipientsToIgnoreList(element.value);
+}
+
+function awTabFromMenulist(element, event)
+{
+ var row = awGetRowByInputElement(element);
+ if (event.shiftKey && row > 1) {
+ var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
+ var listBox = document.getElementById("addressingWidget");
+ listBox.listBoxObject.ensureIndexIsVisible(listBoxRow - 1);
+ }
+}
+
+function awGetNumberOfRecipients()
+{
+ return top.MAX_RECIPIENTS;
+}
+
+function DropOnAddressingTarget(event, onWidget) {
+ let dragSession = gDragService.getCurrentSession();
+
+ let trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/x-moz-address");
+
+ let added = false;
+ for (let i = 0; i < dragSession.numDropItems; ++i) {
+ dragSession.getData(trans, i);
+ let dataObj = {};
+ let bestFlavor = {};
+ let len = {};
+
+ // Ensure we catch any empty data that may have slipped through.
+ try {
+ trans.getAnyTransferData(bestFlavor, dataObj, len);
+ } catch(ex) {
+ continue;
+ }
+ if (dataObj) {
+ dataObj = dataObj.value.QueryInterface(Ci.nsISupportsString);
+ }
+ if (!dataObj) {
+ continue;
+ }
+
+ // Pull the address out of the data object.
+ let address = dataObj.data.substring(0, len.value);
+ if (!address) {
+ continue;
+ }
+
+ if (onWidget) {
+ // Break down and add each address.
+ parseAndAddAddresses(address,
+ awGetPopupElement(top.MAX_RECIPIENTS).value);
+ } else {
+ // Add address into the bucket.
+ DropRecipient(address);
+ }
+ added = true;
+ }
+
+ // We added at least one address during the drop.
+ // Disable the default handler and stop propagating the event
+ // to avoid data being dropped twice.
+ if (added) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+}
+
+function _awSetAutoComplete(selectElem, inputElem)
+{
+ let params = JSON.parse(inputElem.getAttribute('autocompletesearchparam'));
+ params.type = selectElem.value;
+ inputElem.setAttribute('autocompletesearchparam', JSON.stringify(params));
+}
+
+function awSetAutoComplete(rowNumber)
+{
+ var inputElem = awGetInputElement(rowNumber);
+ var selectElem = awGetPopupElement(rowNumber);
+ _awSetAutoComplete(selectElem, inputElem)
+}
+
+function awRecipientTextCommand(userAction, element)
+{
+ if (userAction == "typing" || userAction == "scrolling")
+ awReturnHit(element);
+}
+
+// Called when an autocomplete session item is selected and the status of
+// the session it was selected from is nsIAutoCompleteStatus::failureItems.
+//
+// As of this writing, the only way that can happen is when an LDAP
+// autocomplete session returns an error to be displayed to the user.
+//
+// There are hardcoded messages in here, but these are just fallbacks for
+// when string bundles have already failed us.
+//
+function awRecipientErrorCommand(errItem, element)
+{
+ // remove the angle brackets from the general error message to construct
+ // the title for the alert. someday we'll pass this info using a real
+ // exception object, and then this code can go away.
+ //
+ var generalErrString;
+ if (errItem.value != "") {
+ generalErrString = errItem.value.slice(1, errItem.value.length-1);
+ } else {
+ generalErrString = "Unknown LDAP server problem encountered";
+ }
+
+ // try and get the string of the specific error to contruct the complete
+ // err msg, otherwise fall back to something generic. This message is
+ // handed to us as an nsISupportsString in the param slot of the
+ // autocomplete error item, by agreement documented in
+ // nsILDAPAutoCompFormatter.idl
+ //
+ var specificErrString = "";
+ try {
+ var specificError = errItem.param.QueryInterface(Ci.nsISupportsString);
+ specificErrString = specificError.data;
+ } catch (ex) {
+ }
+ if (specificErrString == "") {
+ specificErrString = "Internal error";
+ }
+
+ Services.prompt.alert(window, generalErrString, specificErrString);
+}
+
+function awRecipientKeyPress(event, element)
+{
+ switch(event.key) {
+ case "ArrowUp":
+ awArrowHit(element, -1);
+ break;
+ case "ArrowDown":
+ awArrowHit(element, 1);
+ break;
+ case "Enter":
+ case "Tab":
+ // if the user text contains a comma or a line return, ignore
+ if (element.value.includes(',')) {
+ var addresses = element.value;
+ element.value = ""; // clear out the current line so we don't try to autocomplete it..
+ parseAndAddAddresses(addresses, awGetPopupElement(awGetRowByInputElement(element)).value);
+ }
+ else if (event.key == "Tab")
+ awTabFromRecipient(element, event);
+
+ break;
+ }
+}
+
+function awArrowHit(inputElement, direction)
+{
+ var row = awGetRowByInputElement(inputElement) + direction;
+ if (row) {
+ var nextInput = awGetInputElement(row);
+
+ if (nextInput)
+ awSetFocusTo(nextInput);
+ else if (inputElement.value)
+ awAppendNewRow(true);
+ }
+}
+
+function awRecipientKeyDown(event, element)
+{
+ switch(event.key) {
+ case "Delete":
+ case "Backspace":
+ /* do not query directly the value of the text field else the autocomplete widget could potentially
+ alter it value while doing some internal cleanup, instead, query the value through the first child
+ */
+ if (!element.value)
+ awDeleteHit(element);
+
+ //We need to stop the event else the listbox will receive it and the function
+ //awKeyDown will be executed!
+ event.stopPropagation();
+ break;
+ }
+}
+
+function awKeyDown(event, listboxElement)
+{
+ switch(event.key) {
+ case "Delete":
+ case "Backspace":
+ /* Warning, the listboxElement.selectedItems will change everytime we delete a row */
+ var length = listboxElement.selectedCount;
+ for (var i = 1; i <= length; i++) {
+ var inputs = listboxElement.selectedItem.getElementsByTagName(awInputElementName());
+ if (inputs && inputs.length == 1)
+ awDeleteHit(inputs[0]);
+ }
+ break;
+ }
+}
+
+function awMenulistKeyPress(event, element)
+{
+ switch(event.key) {
+ case "Tab":
+ awTabFromMenulist(element, event);
+ break;
+ }
+}
+
+/* ::::::::::: addressing widget dummy rows ::::::::::::::::: */
+
+var gAWContentHeight = 0;
+var gAWRowHeight = 0;
+
+function awFitDummyRows()
+{
+ awCalcContentHeight();
+ awCreateOrRemoveDummyRows();
+}
+
+function awCreateOrRemoveDummyRows()
+{
+ var listbox = document.getElementById("addressingWidget");
+ var listboxHeight = listbox.boxObject.height;
+
+ // remove rows to remove scrollbar
+ let kids = listbox.querySelectorAll('[_isDummyRow]');
+ for (let i = kids.length - 1; gAWContentHeight > listboxHeight && i >= 0; --i) {
+ gAWContentHeight -= gAWRowHeight;
+ kids[i].remove();
+ }
+
+ // add rows to fill space
+ if (gAWRowHeight) {
+ while (gAWContentHeight + gAWRowHeight < listboxHeight) {
+ awCreateDummyItem(listbox);
+ gAWContentHeight += gAWRowHeight;
+ }
+ }
+}
+
+function awCalcContentHeight()
+{
+ var listbox = document.getElementById("addressingWidget");
+ var items = listbox.getElementsByTagName("listitem");
+
+ gAWContentHeight = 0;
+ if (items.length > 0) {
+ // all rows are forced to a uniform height in xul listboxes, so
+ // find the first listitem with a boxObject and use it as precedent
+ var i = 0;
+ do {
+ gAWRowHeight = items[i].boxObject.height;
+ ++i;
+ } while (i < items.length && !gAWRowHeight);
+ gAWContentHeight = gAWRowHeight*items.length;
+ }
+}
+
+function awCreateDummyItem(aParent)
+{
+ var titem = document.createElement("listitem");
+ titem.setAttribute("_isDummyRow", "true");
+ titem.setAttribute("class", "dummy-row");
+
+ for (var i = awGetNumberOfCols(); i > 0; i--)
+ awCreateDummyCell(titem);
+
+ if (aParent)
+ aParent.appendChild(titem);
+
+ return titem;
+}
+
+function awCreateDummyCell(aParent)
+{
+ var cell = document.createElement("listcell");
+ cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
+ if (aParent)
+ aParent.appendChild(cell);
+
+ return cell;
+}
+
+function awGetNextDummyRow()
+{
+ // gets the next row from the top down
+ return document.querySelector('#addressingWidget > [_isDummyRow]');
+}
+
+function awSizerListen()
+{
+ // when splitter is clicked, fill in necessary dummy rows each time the mouse is moved
+ awCalcContentHeight(); // precalculate
+ document.addEventListener("mousemove", awSizerMouseMove, true);
+ document.addEventListener("mouseup", awSizerMouseUp);
+}
+
+function awSizerMouseMove()
+{
+ awCreateOrRemoveDummyRows(2);
+}
+
+function awSizerMouseUp()
+{
+ document.removeEventListener("mousemove", awSizerMouseMove);
+ document.removeEventListener("mouseup", awSizerMouseUp);
+}
+
+function awSizerResized(aSplitter)
+{
+ // set the height on the listbox rather than on the toolbox
+ var listbox = document.getElementById("addressingWidget");
+ listbox.height = listbox.boxObject.height;
+ // remove all the heights set on the splitter's previous siblings
+ for (let sib = aSplitter.previousSibling; sib; sib = sib.previousSibling)
+ sib.removeAttribute("height");
+}
+
+function awDocumentKeyPress(event)
+{
+ try {
+ var id = event.target.id;
+ if (id.startsWith('addressCol1'))
+ awRecipientKeyPress(event, event.target);
+ } catch (e) { }
+}
+
+function awRecipientInputCommand(event, inputElement)
+{
+ gContentChanged=true;
+ setupAutocomplete();
+}
+
+// Given an arbitrary block of text like a comma delimited list of names or a names separated by spaces,
+// we will try to autocomplete each of the names and then take the FIRST match for each name, adding it the
+// addressing widget on the compose window.
+
+var gAutomatedAutoCompleteListener = null;
+
+function parseAndAddAddresses(addressText, recipientType)
+{
+ // strip any leading >> characters inserted by the autocomplete widget
+ var strippedAddresses = addressText.replace(/.* >> /, "");
+
+ var addresses = MailServices.headerParser
+ .makeFromDisplayAddress(strippedAddresses);
+
+ if (addresses.length)
+ {
+ // we need to set up our own autocomplete session and search for results
+
+ setupAutocomplete(); // be safe, make sure we are setup
+ if (!gAutomatedAutoCompleteListener)
+ gAutomatedAutoCompleteListener = new AutomatedAutoCompleteHandler();
+
+ gAutomatedAutoCompleteListener.init(addresses.map(addr => addr.toString()),
+ addresses.length, recipientType);
+ }
+}
+
+function AutomatedAutoCompleteHandler()
+{
+}
+
+// state driven self contained object which will autocomplete a block of addresses without any UI.
+// force picks the first match and adds it to the addressing widget, then goes on to the next
+// name to complete.
+
+AutomatedAutoCompleteHandler.prototype =
+{
+ param: this,
+ sessionName: null,
+ namesToComplete: {},
+ numNamesToComplete: 0,
+ indexIntoNames: 0,
+
+ numSessionsToSearch: 0,
+ numSessionsSearched: 0,
+ recipientType: null,
+ searchResults: null,
+
+ init:function(namesToComplete, numNamesToComplete, recipientType)
+ {
+ this.indexIntoNames = 0;
+ this.numNamesToComplete = numNamesToComplete;
+ this.namesToComplete = namesToComplete;
+
+ this.recipientType = recipientType;
+
+ // set up the auto complete sessions to use
+ setupAutocomplete();
+ this.autoCompleteNextAddress();
+ },
+
+ autoCompleteNextAddress:function()
+ {
+ this.numSessionsToSearch = 0;
+ this.numSessionsSearched = 0;
+ this.searchResults = new Array;
+
+ if (this.indexIntoNames < this.numNamesToComplete && this.namesToComplete[this.indexIntoNames])
+ {
+ /* XXX This is used to work, until switching to the new toolkit broke it
+ We should fix it see bug 456550.
+ if (!this.namesToComplete[this.indexIntoNames].includes('@')) // don't autocomplete if address has an @ sign in it
+ {
+ // make sure total session count is updated before we kick off ANY actual searches
+ if (gAutocompleteSession)
+ this.numSessionsToSearch++;
+
+ if (gLDAPSession && gCurrentAutocompleteDirectory)
+ this.numSessionsToSearch++;
+
+ if (gAutocompleteSession)
+ {
+ gAutocompleteSession.onAutoComplete(this.namesToComplete[this.indexIntoNames], null, this);
+ // AB searches are actually synchronous. So by the time we get here we have already looked up results.
+
+ // if we WERE going to also do an LDAP lookup, then check to see if we have a valid match in the AB, if we do
+ // don't bother with the LDAP search too just return
+
+ if (gLDAPSession && gCurrentAutocompleteDirectory && this.searchResults[0] && this.searchResults[0].defaultItemIndex != -1)
+ {
+ this.processAllResults();
+ return;
+ }
+ }
+
+ if (gLDAPSession && gCurrentAutocompleteDirectory)
+ gLDAPSession.onStartLookup(this.namesToComplete[this.indexIntoNames], null, this);
+ }
+ */
+
+ if (!this.numSessionsToSearch)
+ this.processAllResults(); // ldap and ab are turned off, so leave text alone
+ }
+ },
+
+ onStatus:function(aStatus)
+ {
+ return;
+ },
+
+ onAutoComplete: function(aResults, aStatus)
+ {
+ // store the results until all sessions are done and have reported in
+ if (aResults)
+ this.searchResults[this.numSessionsSearched] = aResults;
+
+ this.numSessionsSearched++; // bump our counter
+
+ if (this.numSessionsToSearch <= this.numSessionsSearched)
+ setTimeout('gAutomatedAutoCompleteListener.processAllResults()', 0); // we are all done
+ },
+
+ processAllResults: function()
+ {
+ // Take the first result and add it to the compose window
+ var addressToAdd;
+
+ // loop through the results looking for the non default case (default case is the address book with only one match, the default domain)
+ var sessionIndex;
+
+ var searchResultsForSession;
+
+ for (sessionIndex in this.searchResults)
+ {
+ searchResultsForSession = this.searchResults[sessionIndex];
+ if (searchResultsForSession && searchResultsForSession.defaultItemIndex > -1)
+ {
+ addressToAdd = searchResultsForSession.items
+ .queryElementAt(searchResultsForSession.defaultItemIndex,
+ Ci.nsIAutoCompleteItem).value;
+ break;
+ }
+ }
+
+ // still no match? loop through looking for the -1 default index
+ if (!addressToAdd)
+ {
+ for (sessionIndex in this.searchResults)
+ {
+ searchResultsForSession = this.searchResults[sessionIndex];
+ if (searchResultsForSession && searchResultsForSession.defaultItemIndex == -1)
+ {
+ addressToAdd = searchResultsForSession.items
+ .queryElementAt(0, Ci.nsIAutoCompleteItem).value;
+ break;
+ }
+ }
+ }
+
+ // no matches anywhere...just use what we were given
+ if (!addressToAdd)
+ addressToAdd = this.namesToComplete[this.indexIntoNames];
+
+ // that will automatically set the focus on a new available row, and make sure it is visible
+ awAddRecipient(this.recipientType ? this.recipientType : "addr_to", addressToAdd);
+
+ this.indexIntoNames++;
+ this.autoCompleteNextAddress();
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIAutoCompleteListener) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+}
diff --git a/comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul b/comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul
new file mode 100644
index 0000000000..efd1c6007a
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul
@@ -0,0 +1,16 @@
+<?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/. -->
+
+<overlay id="mailComposeOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <menupopup id="menu_EditPopup" onpopupshowing="updateEditItems();">
+ <menuitem id="menu_inlineSpellCheck"
+ oncommand="EnableInlineSpellCheck(!InlineSpellCheckerUI.enabled);"/>
+ <menuitem id="menu_accountmgr"
+ insertafter="sep_preferences"
+ command="cmd_account"/>
+ </menupopup>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/messengercompose.xul b/comm/suite/mailnews/components/compose/content/messengercompose.xul
new file mode 100644
index 0000000000..89126d814d
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/messengercompose.xul
@@ -0,0 +1,720 @@
+<?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/messengercompose/messengercompose.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/editorFormatToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/addressingWidget.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSMIMEOverlay.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/tasksOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/messengercompose/msgComposeContextOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://editor/content/editorOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/messengercompose/mailComposeOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/mailOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % messengercomposeDTD SYSTEM "chrome://messenger/locale/messengercompose/messengercompose.dtd" >
+%messengercomposeDTD;
+<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % utilityDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd">
+%utilityDTD;
+<!ENTITY % msgCompSMIMEDTD SYSTEM "chrome://messenger-smime/locale/msgCompSMIMEOverlay.dtd">
+%msgCompSMIMEDTD;
+]>
+
+<window id="msgcomposeWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ onunload="ComposeUnload()"
+ onload="ComposeLoad()"
+ onclose="return DoCommandClose()"
+ onfocus="EditorOnFocus()"
+ title="&msgComposeWindow.title;"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ windowtype="msgcompose"
+ macanimationtype="document"
+ drawtitle="true"
+ width="640" height="480"
+ persist="screenX screenY width height sizemode">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_composeMsgs" src="chrome://messenger/locale/messengercompose/composeMsgs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_offlinePrompts" src="chrome://messenger/locale/offline.properties"/>
+ <stringbundle id="languageBundle" src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_comp_smime" src="chrome://messenger-smime/locale/msgCompSMIMEOverlay.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://communicator/content/contentAreaClick.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+ <script src="chrome://messenger/content/accountUtils.js"/>
+ <script src="chrome://messenger/content/mail-offline.js"/>
+ <script src="chrome://editor/content/editor.js"/>
+ <script src="chrome://messenger/content/messengercompose/MsgComposeCommands.js"/>
+ <script src="chrome://messenger/content/messengercompose/addressingWidgetOverlay.js"/>
+ <script src="chrome://messenger/content/addressbook/abDragDrop.js"/>
+ <script src="chrome://messenger-smime/content/msgCompSMIMEOverlay.js"/>
+
+ <commandset id="composeCommands">
+ <commandset id="msgComposeCommandUpdate"
+ commandupdater="true"
+ events="focus"
+ oncommandupdate="CommandUpdate_MsgCompose()"/>
+
+ <commandset id="editorCommands"/>
+ <commandset id="commonEditorMenuItems"/>
+ <commandset id="composerMenuItems"/>
+ <commandset id="composerEditMenuItems"/>
+ <commandset id="composerStyleMenuItems"/>
+ <commandset id="composerTableMenuItems"/>
+ <commandset id="composerListMenuItems"/>
+ <commandset id="tasksCommands"/>
+ <!-- File Menu -->
+ <command id="cmd_attachFile" oncommand="goDoCommand('cmd_attachFile')"/>
+ <command id="cmd_attachPage" oncommand="goDoCommand('cmd_attachPage')"/>
+ <command id="cmd_attachVCard" checked="false" oncommand="ToggleAttachVCard(event.target)"/>
+ <command id="cmd_save" oncommand="goDoCommand('cmd_save')"/>
+ <command id="cmd_saveAsFile" oncommand="goDoCommand('cmd_saveAsFile')"/>
+ <command id="cmd_saveAsDraft" oncommand="goDoCommand('cmd_saveAsDraft')"/>
+ <command id="cmd_saveAsTemplate" oncommand="goDoCommand('cmd_saveAsTemplate')"/>
+ <command id="cmd_sendButton" oncommand="goDoCommand('cmd_sendButton')"/>
+ <command id="cmd_sendNow" oncommand="goDoCommand('cmd_sendNow')"/>
+ <command id="cmd_sendWithCheck" oncommand="goDoCommand('cmd_sendWithCheck')"/>
+ <command id="cmd_sendLater" oncommand="goDoCommand('cmd_sendLater')"/>
+
+ <!-- Edit Menu -->
+ <!--command id="cmd_pasteQuote"/ DO NOT INCLUDE THOSE COMMANDS ELSE THE EDIT MENU WILL BE BROKEN! -->
+ <!--command id="cmd_find"/-->
+ <!--command id="cmd_findNext"/-->
+ <!--command id="cmd_findReplace"/-->
+ <command id="cmd_renameAttachment" oncommand="goDoCommand('cmd_renameAttachment')" disabled="true"/>
+ <command id="cmd_openAttachment" oncommand="goDoCommand('cmd_openAttachment')"/>
+ <command id="cmd_account"
+ label="&accountManagerCmd.label;"
+ accesskey="&accountManagerCmd.accesskey;"
+ oncommand="goDoCommand('cmd_account');"/>
+
+ <!-- Options Menu -->
+ <command id="cmd_selectAddress" oncommand="goDoCommand('cmd_selectAddress')"/>
+ <command id="cmd_outputFormat" oncommand="OutputFormatMenuSelect(event.target)"/>
+ <command id="cmd_quoteMessage" oncommand="goDoCommand('cmd_quoteMessage')"/>
+ <command id="cmd_viewSecurityStatus" oncommand="showMessageComposeSecurityStatus();"/>
+ </commandset>
+
+ <broadcasterset id="composeBroadcasters">
+ <broadcaster id="Communicator:WorkMode"/>
+ <broadcaster id="securityStatus" crypto="" signing=""/>
+ </broadcasterset>
+
+ <observes element="securityStatus" attribute="crypto"/>
+ <observes element="securityStatus" attribute="signing"/>
+
+ <broadcasterset id="mainBroadcasterSet"/>
+
+ <keyset id="tasksKeys">
+ <!-- File Menu -->
+ <key id="key_send" keycode="&sendCmd.keycode;" observes="cmd_sendWithCheck" modifiers="accel"/>
+ <key id="key_sendLater" keycode="&sendLaterCmd.keycode;" observes="cmd_sendLater" modifiers="accel, shift"/>
+
+ <!-- Options Menu -->
+ <!-- key id="key_selectAddresses" key="&selectAddressCmd.key;" command="cmd_selectAddress"/ -->
+
+ <key id="showHideSidebar"/>
+ <!-- Tab/F6 Keys -->
+ <key keycode="VK_TAB" oncommand="SwitchElementFocus(event);" modifiers="control"/>
+ <key keycode="VK_TAB" oncommand="SwitchElementFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);" modifiers="control"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);" modifiers="shift"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);"/>
+ <key keycode="VK_ESCAPE" oncommand="handleEsc();"/>
+ </keyset>
+ <keyset id="editorKeys"/>
+ <keyset id="composeKeys">
+#ifndef XP_MACOSX
+ <key id="key_renameAttachment" keycode="VK_F2"
+ oncommand="goDoCommand('cmd_renameAttachment');"/>
+#endif
+ </keyset>
+
+ <popupset id="contentAreaContextSet"/>
+
+ <popupset id="editorPopupSet">
+ <menupopup id="sidebarPopup"/>
+
+ <menupopup id="msgComposeAttachmentContext"
+ onpopupshowing="updateEditItems();">
+ <menuitem label="&openAttachment.label;"
+ accesskey="&openAttachment.accesskey;"
+ command="cmd_openAttachment"/>
+ <menuitem accesskey="&deleteAttachment.accesskey;"
+ command="cmd_delete"/>
+ <menuitem label="&renameAttachment.label;"
+ accesskey="&renameAttachment.accesskey;"
+ command="cmd_renameAttachment"/>
+ <menuitem label="&selectAllCmd.label;"
+ accesskey="&selectAllAttachments.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem label="&attachFile.label;"
+ accesskey="&attachFile.accesskey;"
+ command="cmd_attachFile"/>
+ <menuitem label="&attachPage.label;"
+ accesskey="&attachPage.accesskey;"
+ command="cmd_attachPage"/>
+ </menupopup>
+ </popupset>
+
+ <menupopup id="blockedContentOptions" value=""
+ onpopupshowing="onBlockedContentOptionsShowing(event);">
+ </menupopup>
+
+ <vbox id="titlebar"/>
+
+ <toolbox id="compose-toolbox"
+ class="toolbox-top"
+ mode="full"
+ defaultmode="full">
+ <toolbar id="compose-toolbar-menubar2"
+ type="menubar"
+ 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">
+ <menubar id="mail-menubar">
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup">
+ <menu id="menu_New">
+ <menupopup id="menu_NewPopup">
+ <menuitem id="menu_newMessage"/>
+ <menuseparator id="menuNewPopupSeparator"/>
+ <menuitem id="menu_newCard"/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ <menuitem id="menu_newEditor"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_Attach"
+ label="&attachMenu.label;"
+ accesskey="&attachMenu.accesskey;">
+ <menupopup id="menu_AttachPopup">
+ <menuitem id="menu_AttachFile"
+ label="&attachFileCmd.label;"
+ accesskey="&attachFileCmd.accesskey;"
+ command="cmd_attachFile"/>
+ <menuitem id="menu_AttachPage"
+ label="&attachPageCmd.label;"
+ accesskey="&attachPageCmd.accesskey;"
+ command="cmd_attachPage"/>
+ <menuseparator id="menuAttachPageSeparator"/>
+ <menuitem id="menu_AttachPageVCard"
+ type="checkbox"
+ label="&attachVCardCmd.label;"
+ accesskey="&attachVCardCmd.accesskey;"
+ command="cmd_attachVCard"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_close"/>
+ <menuseparator id="menuFileAfterCloseSeparator"/>
+ <menuitem id="menu_SaveCmd"
+ label="&saveCmd.label;"
+ accesskey="&saveCmd.accesskey;"
+ key="key_save"
+ command="cmd_save"/>
+ <menu id="menu_SaveAsCmd"
+ label="&saveAsCmd.label;"
+ accesskey="&saveAsCmd.accesskey;">
+ <menupopup id="menu_SaveAsCmdPopup">
+ <menuitem id="menu_SaveAsFileCmd"
+ label="&saveAsFileCmd.label;"
+ accesskey="&saveAsFileCmd.accesskey;"
+ command="cmd_saveAsFile"/>
+ <menuseparator id="menuSaveAfterSaveAsSeparator"/>
+ <menuitem id="menu_SaveAsDraftCmd"
+ label="&saveAsDraftCmd.label;"
+ accesskey="&saveAsDraftCmd.accesskey;"
+ command="cmd_saveAsDraft"/>
+ <menuitem id="menu_SaveAsTemplateCmd"
+ label="&saveAsTemplateCmd.label;"
+ accesskey="&saveAsTemplateCmd.accesskey;"
+ command="cmd_saveAsTemplate"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="menuFileAfterSaveAsSeparator"/>
+ <menuitem id="menu_sendNow"
+ label="&sendNowCmd.label;"
+ accesskey="&sendNowCmd.accesskey;"
+ key="key_send" command="cmd_sendNow"/>
+ <menuitem id="menu_sendLater"
+ label="&sendLaterCmd.label;"
+ accesskey="&sendLaterCmd.accesskey;"
+ key="key_sendLater"
+ command="cmd_sendLater"/>
+ <menuseparator id="menuFileAfterSendLaterSeparator"/>
+ <menuitem id="menu_printSetup"/>
+ <menuitem id="menu_printPreview"/>
+ <menuitem id="menu_print"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_Edit"/>
+ <menu id="menu_View">
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="onViewToolbarsPopupShowing(event)"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="menu_showTaskbar"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewMenuBeforeSecurityStatusSeparator"/>
+ <menuitem id="menu_viewSecurityStatus"
+ label="&menu_viewSecurityStatus.label;"
+ accesskey="&menu_viewSecurityStatus.accesskey;"
+ command="cmd_viewSecurityStatus"/>
+ </menupopup>
+ </menu>
+
+ <menu id="insertMenu"
+ command="cmd_renderedHTMLEnabler"/>
+
+ <menu id="formatMenu"
+ label="&formatMenu.label;"
+ accesskey="&formatMenu.accesskey;"
+ command="cmd_renderedHTMLEnabler">
+ <menupopup id="formatMenuPopup">
+ <menu id="tableMenu"/>
+ <menuseparator id="menuFormatAfterTableSeparator"/>
+ <menuitem id="objectProperties"/>
+ <menuitem id="colorsAndBackground"/>
+ </menupopup>
+ </menu>
+
+ <menu id="optionsMenu"
+ label="&optionsMenu.label;"
+ accesskey="&optionsMenu.accesskey;">
+ <menupopup id="optionsMenuPopup"
+ onpopupshowing="setSecuritySettings(1);">
+ <menuitem id="menu_selectAddress"
+ label="&selectAddressCmd.label;"
+ accesskey="&selectAddressCmd.accesskey;"
+ command="cmd_selectAddress"/>
+ <menuitem id="menu_quoteMessage"
+ label="&quoteCmd.label;"
+ accesskey="&quoteCmd.accesskey;"
+ command="cmd_quoteMessage"/>
+ <menuseparator id="menuOptionsAfterQuoteSeparator"/>
+ <menuitem id="returnReceiptMenu"
+ type="checkbox"
+ label="&returnReceiptMenu.label;"
+ accesskey="&returnReceiptMenu.accesskey;"
+ checked="false"
+ oncommand="ToggleReturnReceipt(event.target)"/>
+ <menuitem id="dsnMenu"
+ type="checkbox"
+ label="&dsnMenu.label;"
+ accesskey="&dsnMenu.accesskey;"
+ checked="false"
+ oncommand="ToggleDSN(event.target);"/>
+ <menu id="outputFormatMenu"
+ label="&outputFormatMenu.label;"
+ accesskey="&outputFormatMenu.accesskey;"
+ command="cmd_outputFormat">
+ <menupopup id="outputFormatMenuPopup">
+ <menuitem id="format_auto" type="radio" name="output_format" label="&autoFormatCmd.label;" accesskey="&autoFormatCmd.accesskey;" checked="true"/>
+ <menuitem id="format_plain" type="radio" name="output_format" label="&plainTextFormatCmd.label;" accesskey="&plainTextFormatCmd.accesskey;"/>
+ <menuitem id="format_html" type="radio" name="output_format" label="&htmlFormatCmd.label;" accesskey="&htmlFormatCmd.accesskey;"/>
+ <menuitem id="format_both" type="radio" name="output_format" label="&bothFormatCmd.label;" accesskey="&bothFormatCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menu id="priorityMenu"
+ label="&priorityMenu.label;"
+ accesskey="&priorityMenu.accesskey;"
+ oncommand="PriorityMenuSelect(event.target);">
+ <menupopup id="priorityMenuPopup"
+ onpopupshowing="updatePriorityMenu(this);">
+ <menuitem id="priority_highest" type="radio" name="priority" label="&highestPriorityCmd.label;" accesskey="&highestPriorityCmd.accesskey;" value="Highest"/>
+ <menuitem id="priority_high" type="radio" name="priority" label="&highPriorityCmd.label;" accesskey="&highPriorityCmd.accesskey;" value="High"/>
+ <menuitem id="priority_normal" type="radio" name="priority" label="&normalPriorityCmd.label;" accesskey="&normalPriorityCmd.accesskey;" value="Normal"/>
+ <menuitem id="priority_low" type="radio" name="priority" label="&lowPriorityCmd.label;" accesskey="&lowPriorityCmd.accesskey;" value="Low"/>
+ <menuitem id="priority_lowest" type="radio" name="priority" label="&lowestPriorityCmd.label;" accesskey="&lowestPriorityCmd.accesskey;" value="Lowest"/>
+ </menupopup>
+ </menu>
+ <menu id="charsetMenu"
+ onpopupshowing="UpdateCharsetMenu(gMsgCompose.compFields.characterSet, this);"
+ oncommand="ComposeSetCharacterSet(event);">
+ <menupopup id="charsetPopup" detectors="false"/>
+ </menu>
+ <menu id="fccMenu"
+ label="&fileCarbonCopyCmd.label;"
+ accesskey="&fileCarbonCopyCmd.accesskey;"
+ oncommand="MessageFcc(event.target._folder);">
+ <menupopup id="fccMenuPopup"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ fileHereLabel="&fileHereMenu.label;"/>
+ </menu>
+ <menuseparator id="smimeOptionsSeparator"/>
+ <menuitem id="menu_securityEncryptRequire1"
+ type="checkbox"
+ label="&menu_securityEncryptRequire.label;"
+ accesskey="&menu_securityEncryptRequire.accesskey;"
+ oncommand="toggleEncryptMessage();"/>
+ <menuitem id="menu_securitySign1"
+ type="checkbox"
+ label="&menu_securitySign.label;"
+ accesskey="&menu_securitySign.accesskey;"
+ oncommand="toggleSignMessage();"/>
+ </menupopup>
+ </menu>
+ <menu id="tasksMenu"/>
+ <menu id="windowMenu"/>
+ <menu id="menu_Help"/>
+ </menubar>
+ </toolbaritem>
+ </toolbar>
+
+ <toolbar id="composeToolbar"
+ class="toolbar-primary chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&mailToolbar.tooltip;"
+ toolbarname="&showComposeToolbarCmd.label;"
+ accesskey="&showComposeToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="button-send,separator,button-address,button-attach,spellingButton,button-security,separator,button-save,spring,throbber-box"
+ context="toolbar-context-menu">
+ <toolbarbutton id="button-send"
+ class="toolbarbutton-1"
+ label="&sendButton.label;"
+ tooltiptext="&sendButton.tooltip;"
+ now_label="&sendButton.label;"
+ now_tooltiptext="&sendButton.tooltip;"
+ later_label="&sendLaterCmd.label;"
+ later_tooltiptext="&sendlaterButton.tooltip;"
+ removable="true"
+ command="cmd_sendButton">
+ <observes element="Communicator:WorkMode"
+ attribute="offline"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-address"
+ class="toolbarbutton-1"
+ label="&addressButton.label;"
+ tooltiptext="&addressButton.tooltip;"
+ removable="true"
+ command="cmd_selectAddress"/>
+
+ <toolbarbutton id="button-attach"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&attachButton.label;"
+ tooltiptext="&attachButton.tooltip;"
+ removable="true"
+ command="cmd_attachFile">
+ <menupopup id="button-attachPopup">
+ <menuitem id="button-attachFile"
+ label="&attachFileCmd.label;"
+ accesskey="&attachFileCmd.accesskey;"
+ command="cmd_attachFile"/>
+ <menuitem id="button-attachPage"
+ label="&attachPageCmd.label;"
+ accesskey="&attachPageCmd.accesskey;"
+ command="cmd_attachPage"/>
+ <menuseparator id="buttonAttachAfterPageSeparator"/>
+ <menuitem id="button-attachVCard"
+ type="checkbox"
+ label="&attachVCardCmd.label;"
+ accesskey="&attachVCardCmd.accesskey;"
+ command="cmd_attachVCard"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="spellingButton"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&spellingButton.label;"
+ removable="true"
+ command="cmd_spelling">
+ <!-- this popup gets dynamically generated -->
+ <menupopup id="languageMenuList"
+ oncommand="ChangeLanguage(event);"
+ onpopupshowing="OnShowDictionaryMenu(event.target);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-save"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&saveButton.label;"
+ tooltiptext="&saveButton.tooltip;"
+ removable="true"
+ command="cmd_save">
+ <menupopup id="button-savePopup">
+ <menuitem id="button-saveAsFile"
+ label="&saveAsFileCmd.label;"
+ accesskey="&saveAsFileCmd.accesskey;"
+ command="cmd_saveAsFile"/>
+ <menuseparator id="buttonSaveAfterFileSeparator"/>
+ <menuitem id="button-saveAsDraft"
+ label="&saveAsDraftCmd.label;"
+ accesskey="&saveAsDraftCmd.accesskey;"
+ command="cmd_saveAsDraft"/>
+ <menuitem id="button-saveAsTemplate"
+ label="&saveAsTemplateCmd.label;"
+ accesskey="&saveAsTemplateCmd.accesskey;"
+ command="cmd_saveAsTemplate"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbaritem id="throbber-box"/>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbar id="MsgHeadersToolbar"
+ persist="collapsed"
+ flex="1"
+ grippytooltiptext="&addressBar.tooltip;"
+ nowindowdrag="true">
+ <hbox id="msgheaderstoolbar-box" flex="1">
+ <vbox id="addresses-box" flex="1">
+ <hbox align="center">
+ <label value="&fromAddr.label;" accesskey="&fromAddr.accesskey;" control="msgIdentity"/>
+ <menulist id="msgIdentity"
+ editable="true"
+ disableautoselect="true"
+ flex="1"
+ oncommand="LoadIdentity(false);">
+ <menupopup id="msgIdentityPopup"/>
+ </menulist>
+ </hbox>
+ <!-- Addressing Widget -->
+ <listbox id="addressingWidget" flex="1"
+ seltype="multiple" rows="4"
+ onkeydown="awKeyDown(event, this);"
+ onclick="awClickEmptySpace(event.originalTarget, true);"
+ ondragover="DragAddressOverTargetControl(event);"
+ ondrop="DropOnAddressingTarget(event, true);">
+
+ <listcols>
+ <listcol id="typecol-addressingWidget"/>
+ <listcol id="textcol-addressingWidget" flex="1"/>
+ </listcols>
+
+ <listitem class="addressingWidgetItem" allowevents="true">
+ <listcell class="addressingWidgetCell" align="stretch">
+ <menulist id="addressCol1#1" disableonsend="true"
+ class="aw-menulist menulist-compact" flex="1"
+ onkeypress="awMenulistKeyPress(event, this);"
+ oncommand="onAddressColCommand(this.id);">
+ <menupopup>
+ <menuitem value="addr_to" label="&toAddr.label;"/>
+ <menuitem value="addr_cc" label="&ccAddr.label;"/>
+ <menuitem value="addr_bcc" label="&bccAddr.label;"/>
+ <menuitem value="addr_reply" label="&replyAddr.label;"/>
+ <menuitem value="addr_newsgroups"
+ label="&newsgroupsAddr.label;"/>
+ <menuitem value="addr_followup"
+ label="&followupAddr.label;"/>
+ </menupopup>
+ </menulist>
+ </listcell>
+
+ <listcell class="addressingWidgetCell">
+ <textbox id="addressCol2#1"
+ class="plain textbox-addressingWidget uri-element"
+ aria-labelledby="addressCol1#1"
+ type="autocomplete" flex="1" maxrows="4"
+ newlines="replacewithcommas"
+ autocompletesearch="mydomain addrbook ldap news"
+ timeout="300" autocompletesearchparam="{}"
+ completedefaultindex="true" forcecomplete="true"
+ minresultsforpopup="2" ignoreblurwhilesearching="true"
+ ontextentered="awRecipientTextCommand(eventParam, this);"
+ onerrorcommand="awRecipientErrorCommand(eventParam, this);"
+ onchange="onRecipientsChanged();"
+ oninput="onRecipientsChanged();"
+ onkeypress="awRecipientKeyPress(event, this);"
+ onkeydown="awRecipientKeyDown(event, this);"
+ disableonsend="true">
+ <image class="person-icon"
+ onclick="this.parentNode.select();"/>
+ </textbox>
+ </listcell>
+ </listitem>
+ </listbox>
+ <hbox align="center">
+ <label value="&subject.label;" accesskey="&subject.accesskey;" control="msgSubject"/>
+ <textbox id="msgSubject" flex="1" class="toolbar" disableonsend="true" spellcheck="true"
+ oninput="gContentChanged=true;SetComposeWindowTitle();"
+ onkeypress="subjectKeyPress(event);" />
+ </hbox>
+ </vbox>
+ <splitter id="attachmentbucket-sizer" collapse="after"/>
+ <vbox id="attachments-box">
+ <label id="attachmentBucketText" value="&attachments.label;" crop="right"
+ accesskey="&attachments.accesskey;" control="attachmentBucket"/>
+ <listbox id="attachmentBucket"
+ seltype="multiple"
+ flex="1"
+ rows="4"
+ tabindex="-1"
+ context="msgComposeAttachmentContext"
+ disableoncustomize="true"
+ onkeypress="if (event.keyCode == 8 || event.keyCode == 46) RemoveSelectedAttachment();"
+ onclick="AttachmentBucketClicked(event);"
+ ondragover="attachmentBucketObserver.onDragOver(event);"
+ ondrop="attachmentBucketObserver.onDrop(event);"
+ ondragexit="attachmentBucketObserver.onDragExit(event);"/>
+ </vbox>
+ </hbox>
+ </toolbar>
+
+ <!-- These toolbar items get filled out from the editorOverlay -->
+ <toolbar id="FormatToolbar"
+ class="chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&formatToolbar.tooltip;"
+ toolbarname="&showFormatToolbarCmd.label;"
+ accesskey="&showFormatToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="paragraph-select-container,font-face-select-container,color-buttons-container,DecreaseFontSizeButton,IncreaseFontSizeButton,separator,boldButton,italicButton,underlineButton,separator,ulButton,olButton,outdentButton,indentButton,separator,AlignPopupButton,InsertPopupButton,smileButtonMenu"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu"
+ nowindowdrag="true">
+ <toolbaritem id="paragraph-select-container"/>
+ <toolbaritem id="font-face-select-container"/>
+ <toolbaritem id="color-buttons-container"
+ disableoncustomize="true"/>
+ <toolbarbutton id="DecreaseFontSizeButton"/>
+ <toolbarbutton id="IncreaseFontSizeButton"/>
+ <toolbarbutton id="boldButton"/>
+ <toolbarbutton id="italicButton"/>
+ <toolbarbutton id="underlineButton"/>
+ <toolbarbutton id="ulButton"/>
+ <toolbarbutton id="olButton"/>
+ <toolbarbutton id="outdentButton"/>
+ <toolbarbutton id="indentButton"/>
+ <toolbarbutton id="AlignPopupButton"/>
+ <toolbarbutton id="InsertPopupButton"/>
+ <toolbarbutton id="smileButtonMenu"/>
+ </toolbar>
+
+ <toolbarpalette id="MsgComposeToolbarPalette">
+ <toolbarbutton id="print-button"
+ label="&printButton.label;"
+ tooltiptext="&printButton.tooltip;"/>
+ <toolbarbutton id="button-security"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&securityButton.label;"
+ tooltiptext="&securityButton.tooltip;"
+ oncommand="doSecurityButton();">
+ <menupopup onpopupshowing="setSecuritySettings(2);">
+ <menuitem id="menu_securityEncryptRequire2"
+ type="checkbox"
+ label="&menu_securityEncryptRequire.label;"
+ accesskey="&menu_securityEncryptRequire.accesskey;"
+ oncommand="setNextCommand('encryptMessage');"/>
+ <menuitem id="menu_securitySign2"
+ type="checkbox"
+ label="&menu_securitySign.label;"
+ accesskey="&menu_securitySign.accesskey;"
+ oncommand="setNextCommand('signMessage');"/>
+ <menuseparator id="smimeToolbarButtonSeparator"/>
+ <menuitem id="menu_securityStatus2"
+ label="&menu_securityStatus.label;"
+ accesskey="&menu_securityStatus.accesskey;"
+ oncommand="setNextCommand('show');"/>
+ </menupopup>
+ </toolbarbutton>
+ </toolbarpalette>
+
+ </toolbox>
+
+ <splitter id="compose-toolbar-sizer"
+ resizeafter="grow"
+ onmousedown="awSizerListen();"
+ oncommand="awSizerResized(this);">
+ <observes element="MsgHeadersToolbar" attribute="collapsed"/>
+ </splitter>
+
+ <!-- sidebar/toolbar/content/status -->
+ <hbox id="sidebar-parent" flex="1">
+ <!-- From sidebarOverlay.xul -->
+ <vbox id="sidebar-box" class="chromeclass-extrachrome" hidden="true"/>
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome" hidden="true"/>
+
+ <!-- The mail message body frame -->
+ <vbox id="appcontent" flex="1">
+ <findbar id="FindToolbar" browserid="content-frame"/>
+ <editor id="content-frame"
+ type="content"
+ primary="true"
+ src="about:blank"
+ name="browser.message.body"
+ minheight="100"
+ flex="1"
+ ondblclick="EditorDblClick(event);"
+ context="contentAreaContextMenu"/>
+ </vbox>
+ </hbox>
+
+ <hbox>
+ <notificationbox id="attachmentNotificationBox"
+ flex="1"
+ notificationside="bottom"/>
+ </hbox>
+
+ <statusbar id="status-bar"
+ class="chromeclass-status">
+ <statusbarpanel id="component-bar"/>
+ <statusbarpanel id="statusText"
+ flex="1"/>
+ <statusbarpanel id="statusbar-progresspanel"
+ class="statusbarpanel-progress"
+ collapsed="true">
+ <progressmeter id="compose-progressmeter"
+ class="progressmeter-statusbar"
+ mode="normal"
+ value="0"/>
+ </statusbarpanel>
+ <statusbarpanel id="signing-status"
+ class="statusbarpanel-iconic"
+ collapsed="true"
+ oncommand="showMessageComposeSecurityStatus();"/>
+ <statusbarpanel id="encryption-status"
+ class="statusbarpanel-iconic"
+ collapsed="true"
+ oncommand="showMessageComposeSecurityStatus();"/>
+ <statusbarpanel id="offline-status"
+ class="statusbarpanel-iconic"
+ checkfunc="MailCheckBeforeOfflineChange();"/>
+ </statusbar>
+
+</window>
diff --git a/comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul b/comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul
new file mode 100644
index 0000000000..f3558873d2
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul
@@ -0,0 +1,23 @@
+<?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/. -->
+
+<overlay id="msgComposeContextOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <menupopup id="contentAreaContextMenu"
+ onpopupshowing="return event.target != this ||
+ openEditorContextMenu(this);">
+ <!-- Hide the menuitems by default so they do not to show up
+ in the sidebar context menu. -->
+ <menuitem id="context-pasteNoFormatting"
+ insertafter="context-paste"
+ hidden="true"
+ command="cmd_pasteNoFormatting"/>
+ <menuitem id="context-pasteQuote"
+ insertafter="context-pasteNoFormatting"
+ hidden="true"
+ command="cmd_pasteQuote"/>
+ </menupopup>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js
new file mode 100644
index 0000000000..f67d919f63
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.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 Startup() {
+ let value = document.getElementById("mail.compose.autosave").value;
+ EnableElementById("autoSaveInterval", value, false);
+}
+
+function EnableMailComposeAutosaveInterval(aValue) {
+ let focus = (document.getElementById("autoSave") == document.commandDispatcher.focusedElement);
+ EnableElementById("autoSaveInterval", aValue, focus);
+}
+
+function PopulateFonts() {
+ var fontsList = document.getElementById("fontSelect");
+ try {
+ var enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"]
+ .getService(Ci.nsIFontEnumerator);
+ var localFonts = enumerator.EnumerateAllFonts();
+ for (let font of localFonts)
+ if (font != "serif" && font != "sans-serif" && font != "monospace")
+ fontsList.appendItem(font, font);
+ } catch (ex) { }
+
+ // Select the item after the list is completely generated.
+ document.getElementById(fontsList.getAttribute("preference"))
+ .setElementValue(fontsList);
+}
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul
new file mode 100644
index 0000000000..c6f3b4fac8
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul
@@ -0,0 +1,212 @@
+<?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 % pref-composing_messagesDTD SYSTEM "chrome://messenger/locale/messengercompose/pref-composing_messages.dtd">
+%pref-composing_messagesDTD;
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+%editorOverlayDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="composing_messages_pane"
+ label="&pref.composing.messages.title;"
+ script="chrome://messenger/content/messengercompose/pref-composing_messages.js"
+ onpaneload="this.PopulateFonts();">
+
+ <preferences id="composing_messages_preferences">
+ <preference id="mail.forward_message_mode"
+ name="mail.forward_message_mode"
+ type="int"/>
+ <preference id="mail.reply_quote_inline"
+ name="mail.reply_quote_inline"
+ type="bool"/>
+ <preference id="mail.compose.autosave"
+ name="mail.compose.autosave"
+ type="bool"
+ onchange="EnableMailComposeAutosaveInterval(this.value);"/>
+ <preference id="mail.compose.autosaveinterval"
+ name="mail.compose.autosaveinterval"
+ type="int"/>
+ <preference id="mail.warn_on_send_accel_key"
+ name="mail.warn_on_send_accel_key"
+ type="bool"/>
+ <preference id="mailnews.wraplength"
+ name="mailnews.wraplength"
+ type="int"/>
+ <preference id="msgcompose.font_face"
+ name="msgcompose.font_face"
+ type="string"/>
+ <preference id="msgcompose.font_size"
+ name="msgcompose.font_size"
+ type="string"/>
+ <preference id="msgcompose.text_color"
+ name="msgcompose.text_color"
+ type="string"/>
+ <preference id="msgcompose.background_color"
+ name="msgcompose.background_color"
+ type="string"/>
+ <preference id="mailnews.reply_header_type"
+ name="mailnews.reply_header_type"
+ type="int"/>
+ <preference id="mail.compose.default_to_paragraph"
+ name="mail.compose.default_to_paragraph"
+ type="bool"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&generalComposing.label;"/>
+
+ <radiogroup id="forwardMessageMode"
+ orient="horizontal"
+ align="center"
+ preference="mail.forward_message_mode">
+ <label value="&forwardMsg.label;" control="forwardMessageMode"/>
+ <radio value="2"
+ label="&inline.label;"
+ accesskey="&inline.accesskey;"/>
+ <radio value="0"
+ label="&asAttachment.label;"
+ accesskey="&asAttachment.accesskey;"/>
+ </radiogroup>
+
+ <checkbox id="replyQuoteInline" label="&replyQuoteInline.label;"
+ preference="mail.reply_quote_inline"
+ accesskey="&replyQuoteInline.accesskey;"/>
+
+ <hbox align="center">
+ <checkbox id="autoSave" label="&autoSave.label;"
+ preference="mail.compose.autosave"
+ accesskey="&autoSave.accesskey;"
+ aria-labelledby="autoSave autoSaveInterval autoSaveEnd"/>
+ <textbox id="autoSaveInterval"
+ type="number"
+ min="1"
+ max="99"
+ size="2"
+ preference="mail.compose.autosaveinterval"
+ aria-labelledby="autoSave autoSaveInterval autoSaveEnd"/>
+ <label id="autoSaveEnd" value="&autoSaveEnd.label;"/>
+ </hbox>
+
+ <checkbox id="mailWarnOnSendAccelKey"
+ label="&warnOnSendAccelKey.label;"
+ accesskey="&warnOnSendAccelKey.accesskey;"
+ preference="mail.warn_on_send_accel_key"/>
+
+ <hbox align="center">
+ <label id="wrapOutLabel"
+ value="&wrapOutMsg.label;"
+ accesskey="&wrapOutMsg.accesskey;"
+ control="wrapLength"/>
+ <textbox id="wrapLength"
+ type="number"
+ min="0"
+ max="999"
+ size="3"
+ preference="mailnews.wraplength"
+ aria-labelledby="wrapOutLabel wrapLength wrapOutEnd"/>
+ <label id="wrapOutEnd" value="&char.label;"/>
+ </hbox>
+ <hbox align="center">
+ <label id="selectHeaderType"
+ value="&selectHeaderType.label;"
+ accesskey="&selectHeaderType.accesskey;"
+ control="mailNewsReplyList"/>
+ <menulist id="mailNewsReplyList"
+ preference="mailnews.reply_header_type">
+ <menupopup>
+ <menuitem value="0"
+ label="&noReplyOption.label;"/>
+ <menuitem value="1"
+ label="&authorWroteOption.label;"/>
+ <menuitem value="2"
+ label="&onDateAuthorWroteOption.label;"/>
+ <menuitem value="3"
+ label="&authorWroteOnDateOption.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+
+ <!-- Composing Mail -->
+
+ <groupbox align="start">
+ <caption label="&defaultMessagesHeader.label;"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&font.label;"
+ accesskey="&font.accesskey;"
+ control="fontSelect"/>
+ <menulist id="fontSelect" preference="msgcompose.font_face">
+ <menupopup>
+ <menuitem value=""
+ label="&fontVarWidth.label;"/>
+ <menuitem value="tt"
+ label="&fontFixedWidth.label;"/>
+ <menuseparator/>
+ <menuitem value="Helvetica, Arial, sans-serif"
+ label="&fontHelvetica.label;"/>
+ <menuitem value="Times New Roman, Times, serif"
+ label="&fontTimes.label;"/>
+ <menuitem value="Courier New, Courier, monospace"
+ label="&fontCourier.label;"/>
+ <menuseparator/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label value="&size.label;"
+ accesskey="&size.accesskey;"
+ control="fontSizeSelect"/>
+ <hbox align="center">
+ <menulist id="fontSizeSelect" preference="msgcompose.font_size">
+ <menupopup>
+ <menuitem value="x-small" label="&size-tinyCmd.label;"/>
+ <menuitem value="small" label="&size-smallCmd.label;"/>
+ <menuitem value="medium" label="&size-mediumCmd.label;"/>
+ <menuitem value="large" label="&size-largeCmd.label;"/>
+ <menuitem value="x-large" label="&size-extraLargeCmd.label;"/>
+ <menuitem value="xx-large" label="&size-hugeCmd.label;"/>
+ </menupopup>
+ </menulist>
+ <label value="&fontColor.label;"
+ accesskey="&fontColor.accesskey;"
+ control="msgComposeTextColor"/>
+ <colorpicker id="msgComposeTextColor"
+ type="button"
+ preference="msgcompose.text_color"/>
+ <label value="&bgColor.label;"
+ accesskey="&bgColor.accesskey;"
+ control="msgComposeBackgroundColor"/>
+ <colorpicker id="msgComposeBackgroundColor"
+ type="button"
+ preference="msgcompose.background_color"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <description>&defaultCompose.label;</description>
+ <radiogroup id="defaultCompose"
+ class="indent"
+ preference="mail.compose.default_to_paragraph">
+ <radio value="false"
+ label="&defaultBodyText.label;"
+ accesskey="&defaultBodyText.accesskey;"/>
+ <radio value="true"
+ label="&defaultParagraph.label;"
+ accesskey="&defaultParagraph.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js
new file mode 100644
index 0000000000..b5c31d424d
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js
@@ -0,0 +1,151 @@
+/* -*- 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 gListbox;
+var gPref;
+var gError;
+
+function Startup()
+{
+ // Store some useful elements in globals.
+ gListbox =
+ {
+ html: document.getElementById("html_domains"),
+ plaintext: document.getElementById("plaintext_domains")
+ };
+ gPref =
+ {
+ html_domains: document.getElementById("mailnews.html_domains"),
+ plaintext_domains: document.getElementById("mailnews.plaintext_domains")
+ };
+ gError = document.getElementById("formatting_error_msg");
+
+ // Make it easier to access the pref pane from onsync.
+ gListbox.html.pane = this;
+ gListbox.plaintext.pane = this;
+}
+
+function AddDomain(aType)
+{
+ var domains = null;
+ var result = {value: null};
+ if (Services.prompt.prompt(window, gListbox[aType].getAttribute("title"),
+ gListbox[aType].getAttribute("msg"), result,
+ null, {value: 0}))
+ domains = result.value.replace(/ /g, "").split(",");
+
+ if (domains)
+ {
+ var added = false;
+ var removed = false;
+ var listbox = gListbox[aType];
+ var other = aType == "html" ? gListbox.plaintext : gListbox.html;
+ for (var i = 0; i < domains.length; i++)
+ {
+ var domainName = TidyDomainName(domains[i], true);
+ if (domainName)
+ {
+ if (!DomainFirstMatch(listbox, domainName))
+ {
+ var match = DomainFirstMatch(other, domainName);
+ if (match)
+ {
+ match.remove();
+ removed = true;
+ }
+ listbox.appendItem(domainName);
+ added = true;
+ }
+ }
+ }
+ if (added)
+ listbox.doCommand();
+ if (removed)
+ other.doCommand();
+ }
+}
+
+function TidyDomainName(aDomain, aWarn)
+{
+ // See if it is an email address and if so take just the domain part.
+ aDomain = aDomain.replace(/.*@/, "");
+
+ // See if it is a valid domain otherwise return null.
+ if (!/.\../.test(aDomain))
+ {
+ if (aWarn)
+ {
+ var errorMsg = gError.getAttribute("inverr").replace(/@string@/, aDomain);
+ Services.prompt.alert(window, gError.getAttribute("title"), errorMsg);
+ }
+ return null;
+ }
+
+ // Finally make sure the domain is in lowercase.
+ return aDomain.toLowerCase();
+}
+
+function DomainFirstMatch(aListbox, aDomain)
+{
+ return aListbox.getElementsByAttribute("label", aDomain).item(0);
+}
+
+function RemoveDomains(aType, aEvent)
+{
+ if (aEvent && aEvent.keyCode != KeyEvent.DOM_VK_DELETE &&
+ aEvent.keyCode != KeyEvent.DOM_VK_BACK_SPACE)
+ return;
+
+ var nextNode = null;
+ var listbox = gListbox[aType];
+
+ while (listbox.selectedItem)
+ {
+ var selectedNode = listbox.selectedItem;
+ nextNode = selectedNode.nextSibling || selectedNode.previousSibling;
+ selectedNode.remove();
+ }
+
+ if (nextNode)
+ listbox.selectItem(nextNode);
+
+ listbox.doCommand();
+}
+
+function ReadDomains(aListbox)
+{
+ var arrayOfPrefs = gPref[aListbox.id].value.replace(/ /g, "").split(",");
+ if (arrayOfPrefs)
+ {
+ var i;
+ // Check all the existing items, remove any that are not needed and
+ // make sure we do not duplicate any by removing from pref array.
+ var domains = aListbox.getElementsByAttribute("label", "*");
+ if (domains)
+ {
+ for (i = domains.length; --i >= 0; )
+ {
+ var domain = domains[i];
+ var index = arrayOfPrefs.indexOf(domain.label);
+ if (index > -1)
+ arrayOfPrefs.splice(index, 1);
+ else
+ domain.remove();
+ }
+ }
+ for (i = 0; i < arrayOfPrefs.length; i++)
+ {
+ var str = TidyDomainName(arrayOfPrefs[i], false);
+ if (str)
+ aListbox.appendItem(str);
+ }
+ }
+}
+
+function WriteDomains(aListbox)
+{
+ var domains = aListbox.getElementsByAttribute("label", "*");
+ return Array.from(domains, e => e.label).join(",");
+}
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul
new file mode 100644
index 0000000000..0167a0990f
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul
@@ -0,0 +1,120 @@
+<?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/messengercompose/pref-formatting.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="formatting_pane"
+ label="&pref.formatting.title;"
+ script="chrome://messenger/content/messengercompose/pref-formatting.js">
+ <preferences id="formatting_preferences">
+ <preference id="mail.default_html_action"
+ name="mail.default_html_action"
+ type="int"/>
+ <preference id="mailnews.html_domains"
+ name="mailnews.html_domains"
+ type="string"/>
+ <preference id="mailnews.plaintext_domains"
+ name="mailnews.plaintext_domains"
+ type="string"/>
+ <preference id="mailnews.sendformat.auto_downgrade"
+ name="mailnews.sendformat.auto_downgrade"
+ type="bool"/>
+ </preferences>
+
+ <data id="formatting_error_msg"
+ title="&domainnameError.title;"
+ inverr="&invalidEntryError.label;"/>
+
+ <description>&sendMaildesc.label;</description>
+
+ <radiogroup id="mailDefaultHTMLAction"
+ preference="mail.default_html_action">
+ <radio value="0"
+ label="&askMe.label;"
+ accesskey="&askMe.accesskey;"/>
+ <radio value="1"
+ label="&convertPlain2.label;"
+ accesskey="&convertPlain2.accesskey;"/>
+ <radio value="2"
+ label="&sendHTML2.label;"
+ accesskey="&sendHTML2.accesskey;"/>
+ <radio value="3"
+ label="&sendBoth2.label;"
+ accesskey="&sendBoth2.accesskey;"/>
+ </radiogroup>
+
+ <groupbox flex="1">
+ <caption label="&domain.title;"/>
+
+ <description>&domaindesc.label;</description>
+
+ <hbox flex="1">
+ <vbox flex="1">
+ <label value="&HTMLdomaintitle.label;"
+ accesskey="&HTMLdomaintitle.accesskey;"
+ control="html_domains"/>
+ <hbox flex="1">
+ <listbox id="html_domains"
+ title="&add.htmltitle;"
+ msg="&add.htmldomain;"
+ flex="1"
+ seltype="multiple"
+ preference="mailnews.html_domains"
+ onsyncfrompreference="return this.pane.ReadDomains(this);"
+ onsynctopreference="return this.pane.WriteDomains(this);"
+ onkeypress="RemoveDomains('html', event);"/>
+ <vbox>
+ <button label="&AddButton.label;"
+ accesskey="&AddHtmlDomain.accesskey;"
+ oncommand="AddDomain('html');">
+ <observes element="html_domains" attribute="disabled"/>
+ </button>
+ <button label="&DeleteButton.label;"
+ accesskey="&DeleteHtmlDomain.accesskey;"
+ oncommand="RemoveDomains('html', null);">
+ <observes element="html_domains" attribute="disabled"/>
+ </button>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <label value="&PlainTexttitle.label;"
+ accesskey="&PlainTexttitle.accesskey;"
+ control="plaintext_domains"/>
+ <hbox flex="1">
+ <listbox id="plaintext_domains"
+ title="&add.plaintexttitle;"
+ msg="&add.plaintextdomain;"
+ flex="1"
+ seltype="multiple"
+ preference="mailnews.plaintext_domains"
+ onsyncfrompreference="return this.pane.ReadDomains(this);"
+ onsynctopreference="return this.pane.WriteDomains(this);"
+ onkeypress="RemoveDomains('plaintext', event);"/>
+ <vbox>
+ <button label="&AddButton.label;"
+ accesskey="&AddPlainText.accesskey;"
+ oncommand="AddDomain('plaintext');">
+ <observes element="plaintext_domains" attribute="disabled"/>
+ </button>
+ <button label="&DeleteButton.label;"
+ accesskey="&DeletePlainText.accesskey;"
+ oncommand="RemoveDomains('plaintext', null);">
+ <observes element="plaintext_domains" attribute="disabled"/>
+ </button>
+ </vbox>
+ </hbox>
+ </vbox>
+ </hbox>
+ </groupbox>
+
+ <checkbox id="autoDowngrade"
+ label="&autoDowngrade.label;"
+ accesskey="&autoDowngrade.accesskey;"
+ preference="mailnews.sendformat.auto_downgrade"/>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/jar.mn b/comm/suite/mailnews/components/compose/jar.mn
new file mode 100644
index 0000000000..c9465fa8d7
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/jar.mn
@@ -0,0 +1,14 @@
+# 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.jar:
+ content/messenger/messengercompose/pref-composing_messages.xul (content/prefs/pref-composing_messages.xul)
+ content/messenger/messengercompose/pref-composing_messages.js (content/prefs/pref-composing_messages.js)
+ content/messenger/messengercompose/pref-formatting.xul (content/prefs/pref-formatting.xul)
+ content/messenger/messengercompose/pref-formatting.js (content/prefs/pref-formatting.js)
+* content/messenger/messengercompose/messengercompose.xul (content/messengercompose.xul)
+ content/messenger/messengercompose/mailComposeOverlay.xul (content/mailComposeOverlay.xul)
+ content/messenger/messengercompose/msgComposeContextOverlay.xul (content/msgComposeContextOverlay.xul)
+ content/messenger/messengercompose/MsgComposeCommands.js (content/MsgComposeCommands.js)
+ content/messenger/messengercompose/addressingWidgetOverlay.js (content/addressingWidgetOverlay.js)
diff --git a/comm/suite/mailnews/components/compose/moz.build b/comm/suite/mailnews/components/compose/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ["jar.mn"]