summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/aboutMessage.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/content/aboutMessage.js')
-rw-r--r--comm/mail/base/content/aboutMessage.js617
1 files changed, 617 insertions, 0 deletions
diff --git a/comm/mail/base/content/aboutMessage.js b/comm/mail/base/content/aboutMessage.js
new file mode 100644
index 0000000000..29ce47ba4d
--- /dev/null
+++ b/comm/mail/base/content/aboutMessage.js
@@ -0,0 +1,617 @@
+/* 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/. */
+
+/* globals Enigmail, MailE10SUtils */
+
+// mailCommon.js
+/* globals commandController, DBViewWrapper, dbViewWrapperListener,
+ nsMsgViewIndex_None, TreeSelection */
+/* globals gDBView: true, gFolder: true, gViewWrapper: true */
+
+// mailContext.js
+/* globals mailContextMenu */
+
+// msgHdrView.js
+/* globals AdjustHeaderView ClearCurrentHeaders ClearPendingReadTimer
+ HideMessageHeaderPane OnLoadMsgHeaderPane OnTagsChange
+ OnUnloadMsgHeaderPane HandleAllAttachments AttachmentMenuController */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ UIDensity: "resource:///modules/UIDensity.jsm",
+ UIFontSize: "resource:///modules/UIFontSize.jsm",
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+});
+
+const messengerBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+);
+
+var gMessage, gMessageURI;
+var autodetectCharset;
+
+function getMessagePaneBrowser() {
+ return document.getElementById("messagepane");
+}
+
+function messagePaneOnResize() {
+ const doc = getMessagePaneBrowser().contentDocument;
+ // Bail out if it's http content or we don't have images.
+ if (doc?.URL.startsWith("http") || !doc?.images) {
+ return;
+ }
+
+ for (let img of doc.images) {
+ img.toggleAttribute(
+ "overflowing",
+ img.clientWidth - doc.body.offsetWidth >= 0 &&
+ (img.clientWidth <= img.naturalWidth || !img.naturalWidth)
+ );
+ }
+}
+
+function ReloadMessage() {
+ if (!gMessageURI) {
+ return;
+ }
+ displayMessage(gMessageURI, gViewWrapper);
+}
+
+function MailSetCharacterSet() {
+ let messageService = MailServices.messageServiceFromURI(gMessageURI);
+ gMessage = messageService.messageURIToMsgHdr(gMessageURI);
+ messageService.loadMessage(
+ gMessageURI,
+ getMessagePaneBrowser().docShell,
+ top.msgWindow,
+ null,
+ true
+ );
+ autodetectCharset = true;
+}
+
+window.addEventListener("DOMContentLoaded", event => {
+ if (event.target != document) {
+ return;
+ }
+
+ UIDensity.registerWindow(window);
+ UIFontSize.registerWindow(window);
+
+ OnLoadMsgHeaderPane();
+
+ Enigmail.msg.messengerStartup();
+ Enigmail.hdrView.hdrViewLoad();
+
+ MailServices.mailSession.AddFolderListener(
+ folderListener,
+ Ci.nsIFolderListener.removed
+ );
+
+ preferenceObserver.init();
+ Services.obs.addObserver(msgObserver, "message-content-updated");
+
+ const browser = getMessagePaneBrowser();
+
+ if (parent == top) {
+ // Standalone message display? Focus the message pane.
+ browser.focus();
+ }
+
+ if (window.parent == window.top) {
+ mailContextMenu.init();
+ }
+
+ // There might not be a msgWindow variable on the top window
+ // if we're e.g. showing a message in a dedicated window.
+ if (top.msgWindow) {
+ // Necessary plumbing to communicate status updates back to
+ // the user.
+ browser.docShell
+ ?.QueryInterface(Ci.nsIWebProgress)
+ .addProgressListener(
+ top.msgWindow.statusFeedback,
+ Ci.nsIWebProgress.NOTIFY_ALL
+ );
+ }
+
+ window.dispatchEvent(
+ new CustomEvent("aboutMessageLoaded", { bubbles: true })
+ );
+});
+
+window.addEventListener("unload", () => {
+ ClearPendingReadTimer();
+ OnUnloadMsgHeaderPane();
+ MailServices.mailSession.RemoveFolderListener(folderListener);
+ preferenceObserver.cleanUp();
+ Services.obs.removeObserver(msgObserver, "message-content-updated");
+ gViewWrapper?.close();
+});
+
+function displayMessage(uri, viewWrapper) {
+ // Clear the state flags, if this window is re-used.
+ window.msgLoaded = false;
+ window.msgLoading = false;
+
+ // Clean up existing objects before starting again.
+ ClearPendingReadTimer();
+ gMessage = null;
+ if (gViewWrapper && viewWrapper != gViewWrapper) {
+ // Don't clean up gViewWrapper if we're going to reuse it. If we're inside
+ // about:3pane, close the view wrapper, but don't call `onLeavingFolder`,
+ // because about:3pane will do that if we're actually leaving the folder.
+ gViewWrapper?.close(parent != top);
+ gViewWrapper = null;
+ }
+ gDBView = null;
+
+ gMessageURI = uri;
+ ClearCurrentHeaders();
+
+ if (!uri) {
+ HideMessageHeaderPane();
+ MailE10SUtils.loadAboutBlank(getMessagePaneBrowser());
+ window.msgLoaded = true;
+ window.dispatchEvent(
+ new CustomEvent("messageURIChanged", { bubbles: true, detail: uri })
+ );
+ return;
+ }
+
+ let messageService = MailServices.messageServiceFromURI(uri);
+ gMessage = messageService.messageURIToMsgHdr(uri);
+ gFolder = gMessage.folder;
+
+ messageHistory.push(uri);
+
+ if (gFolder) {
+ if (viewWrapper) {
+ if (viewWrapper != gViewWrapper) {
+ gViewWrapper = viewWrapper.clone(dbViewWrapperListener);
+ }
+ } else {
+ gViewWrapper = new DBViewWrapper(dbViewWrapperListener);
+ gViewWrapper._viewFlags = Ci.nsMsgViewFlagsType.kThreadedDisplay;
+ gViewWrapper.open(gFolder);
+ }
+ } else {
+ gViewWrapper = new DBViewWrapper(dbViewWrapperListener);
+ gViewWrapper.openSearchView();
+ }
+ gDBView = gViewWrapper.dbView;
+ let selection = (gDBView.selection = new TreeSelection());
+ selection.view = gDBView;
+ let index = gDBView.findIndexOfMsgHdr(gMessage, true);
+ selection.select(index == nsMsgViewIndex_None ? -1 : index);
+ gDBView?.setJSTree({
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgJSTree"]),
+ _inBatch: false,
+ beginUpdateBatch() {
+ this._inBatch = true;
+ },
+ endUpdateBatch() {
+ this._inBatch = false;
+ },
+ ensureRowIsVisible(index) {},
+ invalidate() {},
+ invalidateRange(startIndex, endIndex) {},
+ rowCountChanged(index, count) {
+ let wasSuppressed = gDBView.selection.selectEventsSuppressed;
+ gDBView.selection.selectEventsSuppressed = true;
+ gDBView.selection.adjustSelection(index, count);
+ gDBView.selection.selectEventsSuppressed = wasSuppressed;
+ },
+ currentIndex: null,
+ });
+
+ if (gMessage.flags & Ci.nsMsgMessageFlags.HasRe) {
+ document.title = `Re: ${gMessage.mime2DecodedSubject || ""}`;
+ } else {
+ document.title = gMessage.mime2DecodedSubject;
+ }
+
+ let browser = getMessagePaneBrowser();
+ const browserChanged = MailE10SUtils.changeRemoteness(browser, null);
+ // The message pane browser should inherit `docShellIsActive` from the
+ // about:message browser, but changing remoteness causes that to not happen.
+ browser.docShellIsActive = !document.hidden;
+ browser.docShell.allowAuth = false;
+ browser.docShell.allowDNSPrefetch = false;
+
+ if (browserChanged) {
+ browser.docShell
+ ?.QueryInterface(Ci.nsIWebProgress)
+ .addProgressListener(
+ top.msgWindow.statusFeedback,
+ Ci.nsIWebProgress.NOTIFY_ALL
+ );
+ }
+
+ if (gMessage.flags & Ci.nsMsgMessageFlags.Partial) {
+ document.body.classList.add("partial-message");
+ } else if (document.body.classList.contains("partial-message")) {
+ document.body.classList.remove("partial-message");
+ document.body.classList.add("completed-message");
+ }
+
+ // @implements {nsIUrlListener}
+ let urlListener = {
+ OnStartRunningUrl(url) {},
+ OnStopRunningUrl(url, status) {
+ window.msgLoading = true;
+ window.dispatchEvent(
+ new CustomEvent("messageURIChanged", { bubbles: true, detail: uri })
+ );
+ if (url instanceof Ci.nsIMsgMailNewsUrl && url.seeOtherURI) {
+ // Show error page if needed.
+ HideMessageHeaderPane();
+ MailE10SUtils.loadURI(getMessagePaneBrowser(), url.seeOtherURI);
+ }
+ },
+ };
+ try {
+ messageService.loadMessage(
+ uri,
+ browser.docShell,
+ top.msgWindow,
+ urlListener,
+ false
+ );
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_OFFLINE) {
+ throw ex;
+ }
+
+ // TODO: This should be replaced with a real page, and made not ugly.
+ let title = messengerBundle.GetStringFromName("nocachedbodytitle");
+ // This string includes some HTML! Get rid of it.
+ title = title.replace(/<\/?title>/gi, "");
+ let body = messengerBundle.GetStringFromName("nocachedbodybody2");
+ HideMessageHeaderPane();
+ MailE10SUtils.loadURI(
+ getMessagePaneBrowser(),
+ "data:text/html;base64," +
+ btoa(
+ `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8" />
+ <title>${title}</title>
+ </head>
+ <body>
+ <h1>${title}</h1>
+ <p>${body}</p>
+ </body>
+ </html>`
+ )
+ );
+ }
+ autodetectCharset = false;
+}
+
+var folderListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFolderListener"]),
+
+ onFolderRemoved(parentFolder, childFolder) {},
+ onMessageRemoved(parentFolder, msg) {
+ messageHistory.onMessageRemoved(parentFolder, msg);
+ },
+};
+
+var msgObserver = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (
+ topic == "message-content-updated" &&
+ gMessageURI == subject.QueryInterface(Ci.nsISupportsString).data
+ ) {
+ // This notification is triggered after a partial pop3 message was
+ // fully downloaded. The old message URI is now gone. To reload the
+ // message, we display it with its new URI.
+ displayMessage(data, gViewWrapper);
+ }
+ },
+};
+
+var preferenceObserver = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ _topics: [
+ "mail.inline_attachments",
+ "mail.show_headers",
+ "mail.showCondensedAddresses",
+ "mailnews.display.disallow_mime_handlers",
+ "mailnews.display.html_as",
+ "mailnews.display.prefer_plaintext",
+ "mailnews.headers.showReferences",
+ "rss.show.summary",
+ ],
+
+ _reloadTimeout: null,
+
+ init() {
+ for (let topic of this._topics) {
+ Services.prefs.addObserver(topic, this);
+ }
+ },
+
+ cleanUp() {
+ for (let topic of this._topics) {
+ Services.prefs.removeObserver(topic, this);
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (data == "mail.show_headers") {
+ AdjustHeaderView(Services.prefs.getIntPref(data));
+ }
+ if (!this._reloadTimeout) {
+ // Clear the event queue before reloading the message. Several prefs may
+ // be changed at once.
+ this._reloadTimeout = setTimeout(() => {
+ this._reloadTimeout = null;
+ ReloadMessage();
+ });
+ }
+ },
+};
+
+var messageHistory = {
+ MAX_HISTORY_SIZE: 20,
+ /**
+ * @typedef {object} MessageHistoryEntry
+ * @property {string} messageURI - URI of the message for this entry.
+ * @property {string} folderURI - URI of the folder for this entry.
+ */
+ /**
+ * @type {MessageHistoryEntry[]}
+ */
+ _history: [],
+ _currentIndex: -1,
+ /**
+ * Remove the message from the history, cleaning up the state as needed in
+ * the process.
+ *
+ * @param {nsIMsgFolder} parentFolder
+ * @param {nsIMsgDBHdr} message
+ */
+ onMessageRemoved(parentFolder, message) {
+ if (!this._history.length) {
+ return;
+ }
+ const messageURI = parentFolder.generateMessageURI(message.messageKey);
+ const folderURI = parentFolder.URI;
+ const oldLength = this._history.length;
+ let removedEntriesBeforeFuture = 0;
+ this._history = this._history.filter((entry, index) => {
+ const keepEntry =
+ entry.messageURI !== messageURI || entry.folderURI !== folderURI;
+ if (!keepEntry && index <= this._currentIndex) {
+ ++removedEntriesBeforeFuture;
+ }
+ return keepEntry;
+ });
+ this._currentIndex -= removedEntriesBeforeFuture;
+ // Correct for first entry getting removed while it's the current entry.
+ if (this._history.length && this._currentIndex == -1) {
+ this._currentIndex = 0;
+ }
+ if (oldLength === this._history.length) {
+ return;
+ }
+ window.top.goUpdateCommand("cmd_goBack");
+ window.top.goUpdateCommand("cmd_goForward");
+ },
+ /**
+ * Get the actual index in the history based on a delta from the current
+ * index.
+ *
+ * @param {number} delta - Relative delta from the current index. Forward is
+ * positive, backward is negative.
+ * @returns {number} Absolute index in the history, bounded to the history
+ * size.
+ */
+ _getAbsoluteIndex(delta) {
+ return Math.min(
+ Math.max(this._currentIndex + delta, 0),
+ this._history.length - 1
+ );
+ },
+ /**
+ * Add a message to the end of the history. Does nothing if the message is
+ * already the current item. Moves the history forward by one step if the next
+ * item already matches the given message. Else removes any "future" history
+ * if the current position isn't the newest entry in the history.
+ *
+ * If the history is growing larger than what we want to keep, it is trimmed.
+ *
+ * Assumes the view is currently in the folder that should be comitted to
+ * history.
+ *
+ * @param {string} messageURI - Message to add to the history.
+ */
+ push(messageURI) {
+ if (!messageURI) {
+ return;
+ }
+ let currentItem = this._history[this._currentIndex];
+ let currentFolder = gFolder?.URI;
+ if (
+ currentItem &&
+ messageURI === currentItem.messageURI &&
+ currentFolder === currentItem.folderURI
+ ) {
+ return;
+ }
+ let nextMessageIndex = this._currentIndex + 1;
+ let erasedFuture = false;
+ if (nextMessageIndex < this._history.length) {
+ let nextMessage = this._history[nextMessageIndex];
+ if (
+ nextMessage &&
+ messageURI === nextMessage.messageURI &&
+ currentFolder === nextMessage.folderURI
+ ) {
+ this._currentIndex = nextMessageIndex;
+ if (this._currentIndex === 1) {
+ window.top.goUpdateCommand("cmd_goBack");
+ }
+ if (this._currentIndex + 1 === this._history.length) {
+ window.top.goUpdateCommand("cmd_goForward");
+ }
+ return;
+ }
+ this._history.splice(nextMessageIndex, Infinity);
+ erasedFuture = true;
+ }
+ this._history.push({ messageURI, folderURI: currentFolder });
+ this._currentIndex = nextMessageIndex;
+ if (this._history.length > this.MAX_HISTORY_SIZE) {
+ let amountOfItemsToRemove = this._history.length - this.MAX_HISTORY_SIZE;
+ this._history.splice(0, amountOfItemsToRemove);
+ this._currentIndex -= amountOfItemsToRemove;
+ }
+ if (!currentItem || this._currentIndex === 0) {
+ window.top.goUpdateCommand("cmd_goBack");
+ }
+ if (erasedFuture) {
+ window.top.goUpdateCommand("cmd_goForward");
+ }
+ },
+ /**
+ * Go forward or back in history relative to the current position.
+ *
+ * @param {number} delta
+ * @returns {?MessageHistoryEntry} The message and folder URI that are now at
+ * the active position in the history. If null is returned, no action was
+ * taken.
+ */
+ pop(delta) {
+ let targetIndex = this._getAbsoluteIndex(delta);
+ if (this._currentIndex == targetIndex && gMessage) {
+ return null;
+ }
+ this._currentIndex = targetIndex;
+ window.top.goUpdateCommand("cmd_goBack");
+ window.top.goUpdateCommand("cmd_goForward");
+ return this._history[targetIndex];
+ },
+ /**
+ * Get the current state of the message history.
+ *
+ * @returns {{entries: MessageHistoryEntry[], currentIndex: number}}
+ * A list of message and folder URIs as strings and the current index in the
+ * entries.
+ */
+ getHistory() {
+ return { entries: this._history.slice(), currentIndex: this._currentIndex };
+ },
+ /**
+ * Get a specific history entry relative to the current positon.
+ *
+ * @param {number} delta - Relative index to get the value of.
+ * @returns {?MessageHistoryEntry} If found, the message and
+ * folder URI at the given position.
+ */
+ getMessageAt(delta) {
+ if (!this._history.length) {
+ return null;
+ }
+ return this._history[this._getAbsoluteIndex(delta)];
+ },
+ /**
+ * Check if going forward or back in the history by the given steps is
+ * possible. A special case is when no message is currently selected, going
+ * back to relative position 0 (so the current index) is possible.
+ *
+ * @param {number} delta - Relative position to go to from the current index.
+ * @returns {boolean} If there is a target available at that position in the
+ * current history.
+ */
+ canPop(delta) {
+ let resultIndex = this._currentIndex + delta;
+ return (
+ resultIndex >= 0 &&
+ resultIndex < this._history.length &&
+ (resultIndex !== this._currentIndex || !gMessage)
+ );
+ },
+ /**
+ * Clear the message history, resetting it to its initial empty state.
+ */
+ clear() {
+ this._history.length = 0;
+ this._currentIndex = -1;
+ window.top.goUpdateCommand("cmd_goBack");
+ window.top.goUpdateCommand("cmd_goForward");
+ },
+};
+
+commandController.registerCallback(
+ "cmd_delete",
+ () => commandController.doCommand("cmd_deleteMessage"),
+ () => commandController.isCommandEnabled("cmd_deleteMessage")
+);
+commandController.registerCallback(
+ "cmd_shiftDelete",
+ () => commandController.doCommand("cmd_shiftDeleteMessage"),
+ () => commandController.isCommandEnabled("cmd_shiftDeleteMessage")
+);
+commandController.registerCallback("cmd_find", () =>
+ document.getElementById("FindToolbar").onFindCommand()
+);
+commandController.registerCallback("cmd_findAgain", () =>
+ document.getElementById("FindToolbar").onFindAgainCommand(false)
+);
+commandController.registerCallback("cmd_findPrevious", () =>
+ document.getElementById("FindToolbar").onFindAgainCommand(true)
+);
+commandController.registerCallback("cmd_print", () => {
+ top.PrintUtils.startPrintWindow(getMessagePaneBrowser().browsingContext, {});
+});
+commandController.registerCallback("cmd_fullZoomReduce", () => {
+ top.ZoomManager.reduce();
+});
+commandController.registerCallback("cmd_fullZoomEnlarge", () => {
+ top.ZoomManager.enlarge();
+});
+commandController.registerCallback("cmd_fullZoomReset", () => {
+ top.ZoomManager.reset();
+});
+commandController.registerCallback("cmd_fullZoomToggle", () => {
+ top.ZoomManager.toggleZoom();
+});
+
+// Attachments commands.
+commandController.registerCallback(
+ "cmd_openAllAttachments",
+ () => HandleAllAttachments("open"),
+ () => AttachmentMenuController.someFilesAvailable()
+);
+
+commandController.registerCallback(
+ "cmd_saveAllAttachments",
+ () => HandleAllAttachments("save"),
+ () => AttachmentMenuController.someFilesAvailable()
+);
+
+commandController.registerCallback(
+ "cmd_detachAllAttachments",
+ () => HandleAllAttachments("detach"),
+ () => AttachmentMenuController.canDetachFiles()
+);
+
+commandController.registerCallback(
+ "cmd_deleteAllAttachments",
+ () => HandleAllAttachments("delete"),
+ () => AttachmentMenuController.canDetachFiles()
+);