summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/mailCore.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/content/mailCore.js')
-rw-r--r--comm/mail/base/content/mailCore.js1063
1 files changed, 1063 insertions, 0 deletions
diff --git a/comm/mail/base/content/mailCore.js b/comm/mail/base/content/mailCore.js
new file mode 100644
index 0000000000..781d5449d6
--- /dev/null
+++ b/comm/mail/base/content/mailCore.js
@@ -0,0 +1,1063 @@
+/* -*- Mode: JS; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Core mail routines used by all of the major mail windows (address book,
+ * 3-pane, compose and stand alone message window).
+ * Routines to support custom toolbars in mail windows, opening up a new window
+ * of a particular type all live here.
+ * Before adding to this file, ask yourself, is this a JS routine that is going
+ * to be used by all of the main mail windows?
+ */
+
+/* import-globals-from ../../extensions/mailviews/content/msgViewPickerOverlay.js */
+/* import-globals-from customizeToolbar.js */
+/* import-globals-from utilityOverlay.js */
+
+/* globals gChatTab */ // From globals chat-messenger.js
+/* globals currentAttachments */ // From msgHdrView.js
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyGetter(this, "gViewSourceUtils", function () {
+ let scope = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://global/content/viewSourceUtils.js",
+ scope
+ );
+ scope.gViewSourceUtils.viewSource = async function (aArgs) {
+ // Check if external view source is enabled. If so, try it. If it fails,
+ // fallback to internal view source.
+ if (Services.prefs.getBoolPref("view_source.editor.external")) {
+ try {
+ await this.openInExternalEditor(aArgs);
+ return;
+ } catch (ex) {}
+ }
+
+ window.openDialog(
+ "chrome://messenger/content/viewSource.xhtml",
+ "_blank",
+ "all,dialog=no",
+ aArgs
+ );
+ };
+ return scope.gViewSourceUtils;
+});
+
+Object.defineProperty(this, "BrowserConsoleManager", {
+ get() {
+ let { loader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ return loader.require("devtools/client/webconsole/browser-console-manager")
+ .BrowserConsoleManager;
+ },
+ configurable: true,
+ enumerable: true,
+});
+
+var gCustomizeSheet = false;
+
+function overlayRestoreDefaultSet() {
+ let toolbox = null;
+ if ("arguments" in window && window.arguments[0]) {
+ toolbox = window.arguments[0];
+ } else if (window.frameElement && "toolbox" in window.frameElement) {
+ toolbox = window.frameElement.toolbox;
+ }
+
+ let mode = toolbox.getAttribute("defaultmode");
+ let align = toolbox.getAttribute("defaultlabelalign");
+ let menulist = document.getElementById("modelist");
+
+ if (mode == "full" && align == "end") {
+ toolbox.setAttribute("mode", "textbesideicon");
+ toolbox.setAttribute("labelalign", align);
+ overlayUpdateToolbarMode("textbesideicon");
+ } else if (mode == "full" && align == "") {
+ toolbox.setAttribute("mode", "full");
+ toolbox.removeAttribute("labelalign");
+ overlayUpdateToolbarMode(mode);
+ }
+
+ restoreDefaultSet();
+
+ if (mode == "full" && align == "end") {
+ menulist.value = "textbesideicon";
+ }
+}
+
+function overlayUpdateToolbarMode(aModeValue) {
+ let toolbox = null;
+ if ("arguments" in window && window.arguments[0]) {
+ toolbox = window.arguments[0];
+ } else if (window.frameElement && "toolbox" in window.frameElement) {
+ toolbox = window.frameElement.toolbox;
+ }
+
+ // If they chose a mode of textbesideicon or full,
+ // then map that to a mode of full, and a labelalign of true or false.
+ if (aModeValue == "textbesideicon" || aModeValue == "full") {
+ var align = aModeValue == "textbesideicon" ? "end" : "bottom";
+ toolbox.setAttribute("labelalign", align);
+ Services.xulStore.persist(toolbox, "labelalign");
+ aModeValue = "full";
+ }
+ updateToolbarMode(aModeValue);
+}
+
+function overlayOnLoad() {
+ let restoreButton = document
+ .getElementById("main-box")
+ .querySelector("[oncommand*='restore']");
+ restoreButton.setAttribute("oncommand", "overlayRestoreDefaultSet();");
+
+ // Add the textBesideIcon menu item if it's not already there.
+ let menuitem = document.getElementById("textbesideiconItem");
+ if (!menuitem) {
+ let menulist = document.getElementById("modelist");
+ let label = document
+ .getElementById("iconsBesideText.label")
+ .getAttribute("value");
+ menuitem = menulist.appendItem(label, "textbesideicon");
+ menuitem.id = "textbesideiconItem";
+ }
+
+ // If they have a mode of full and a labelalign of true,
+ // then pretend the mode is textbesideicon when populating the popup.
+ let toolbox = null;
+ if ("arguments" in window && window.arguments[0]) {
+ toolbox = window.arguments[0];
+ } else if (window.frameElement && "toolbox" in window.frameElement) {
+ toolbox = window.frameElement.toolbox;
+ }
+
+ let toolbarWindow = document.getElementById("CustomizeToolbarWindow");
+ toolbarWindow.setAttribute("toolboxId", toolbox.id);
+ toolbox.setAttribute("doCustomization", "true");
+
+ let mode = toolbox.getAttribute("mode");
+ let align = toolbox.getAttribute("labelalign");
+ if (mode == "full" && align == "end") {
+ toolbox.setAttribute("mode", "textbesideicon");
+ }
+
+ onLoad();
+ overlayRepositionDialog();
+
+ // Re-set and re-persist the mode, if we changed it above.
+ if (mode == "full" && align == "end") {
+ toolbox.setAttribute("mode", mode);
+ Services.xulStore.persist(toolbox, "mode");
+ }
+}
+
+function overlayRepositionDialog() {
+ // Position the dialog so it is fully visible on the screen
+ // (if possible)
+
+ // Seems to be necessary to get the correct dialog height/width
+ window.sizeToContent();
+ var wH = window.outerHeight;
+ var wW = window.outerWidth;
+ var sH = window.screen.height;
+ var sW = window.screen.width;
+ var sX = window.screenX;
+ var sY = window.screenY;
+ var sAL = window.screen.availLeft;
+ var sAT = window.screen.availTop;
+
+ var nX = Math.max(Math.min(sX, sW - wW), sAL);
+ var nY = Math.max(Math.min(sY, sH - wH), sAT);
+ window.moveTo(nX, nY);
+}
+
+function CustomizeMailToolbar(toolboxId, customizePopupId) {
+ if (toolboxId === "mail-toolbox" && window.tabmail) {
+ // Open the unified toolbar customization panel only for mail.
+ document.querySelector("unified-toolbar").showCustomization();
+ return;
+ }
+
+ // Disable the toolbar context menu items
+ var menubar = document.getElementById("mail-menubar");
+ for (var i = 0; i < menubar.children.length; ++i) {
+ menubar.children[i].setAttribute("disabled", true);
+ }
+
+ var customizePopup = document.getElementById(customizePopupId);
+ customizePopup.setAttribute("disabled", "true");
+
+ var toolbox = document.getElementById(toolboxId);
+
+ var customizeURL = "chrome://messenger/content/customizeToolbar.xhtml";
+ gCustomizeSheet = Services.prefs.getBoolPref(
+ "toolbar.customization.usesheet"
+ );
+
+ let externalToolbars = [];
+ if (toolbox.getAttribute("id") == "mail-toolbox") {
+ if (
+ AppConstants.platform != "macosx" &&
+ document.getElementById("toolbar-menubar")
+ ) {
+ externalToolbars.push(document.getElementById("toolbar-menubar"));
+ }
+ }
+
+ if (gCustomizeSheet) {
+ var sheetFrame = document.getElementById("customizeToolbarSheetIFrame");
+ var panel = document.getElementById("customizeToolbarSheetPopup");
+ sheetFrame.hidden = false;
+ sheetFrame.toolbox = toolbox;
+ sheetFrame.panel = panel;
+ if (externalToolbars.length > 0) {
+ sheetFrame.externalToolbars = externalToolbars;
+ }
+
+ // The document might not have been loaded yet, if this is the first time.
+ // If it is already loaded, reload it so that the onload initialization code
+ // re-runs.
+ if (sheetFrame.getAttribute("src") == customizeURL) {
+ sheetFrame.contentWindow.location.reload();
+ } else {
+ sheetFrame.setAttribute("src", customizeURL);
+ }
+
+ // Open the panel, but make it invisible until the iframe has loaded so
+ // that the user doesn't see a white flash.
+ panel.style.visibility = "hidden";
+ toolbox.addEventListener(
+ "beforecustomization",
+ function () {
+ panel.style.removeProperty("visibility");
+ },
+ { capture: false, once: true }
+ );
+ panel.openPopup(toolbox, "after_start", 0, 0);
+ } else {
+ var wintype = document.documentElement.getAttribute("windowtype");
+ wintype = wintype.replace(/:/g, "");
+
+ window.openDialog(
+ customizeURL,
+ "CustomizeToolbar" + wintype,
+ "chrome,all,dependent",
+ toolbox,
+ externalToolbars
+ );
+ }
+}
+
+function MailToolboxCustomizeDone(aEvent, customizePopupId) {
+ if (gCustomizeSheet) {
+ document.getElementById("customizeToolbarSheetIFrame").hidden = true;
+ document.getElementById("customizeToolbarSheetPopup").hidePopup();
+ }
+
+ // Update global UI elements that may have been added or removed
+
+ // Re-enable parts of the UI we disabled during the dialog
+ var menubar = document.getElementById("mail-menubar");
+ for (var i = 0; i < menubar.children.length; ++i) {
+ menubar.children[i].setAttribute("disabled", false);
+ }
+
+ var customizePopup = document.getElementById(customizePopupId);
+ customizePopup.removeAttribute("disabled");
+
+ let toolbox = document.querySelector('[doCustomization="true"]');
+ if (toolbox) {
+ toolbox.removeAttribute("doCustomization");
+
+ // The GetMail button is stuck in a strange state right now, since the
+ // customization wrapping preserves its children, but not its initialized
+ // state. Fix that here.
+ // That is also true for the File -> "Get new messages for" menuitems in both
+ // menus (old and new App menu). And also Go -> Folder.
+ // TODO bug 904223: try to fix folderWidgets.xml to not do this.
+ // See Bug 520457 and Bug 534448 and Bug 709733.
+ // Fix Bug 565045: Only treat "Get Message Button" if it is in our toolbox
+ for (let popup of [
+ toolbox.querySelector("#button-getMsgPopup"),
+ document.getElementById("menu_getAllNewMsgPopup"),
+ document.getElementById("appmenu_getAllNewMsgPopup"),
+ document.getElementById("menu_GoFolderPopup"),
+ document.getElementById("appmenu_GoFolderPopup"),
+ ]) {
+ if (!popup) {
+ continue;
+ }
+
+ // .teardown() is only available here if the menu has its frame
+ // otherwise the folderWidgets.xml::folder-menupopup binding is not
+ // attached to the popup. So if it is not available, remove the items
+ // explicitly. Only remove elements that were generated by the binding.
+ if ("_teardown" in popup) {
+ popup._teardown();
+ } else {
+ for (let i = popup.children.length - 1; i >= 0; i--) {
+ let child = popup.children[i];
+ if (child.getAttribute("generated") != "true") {
+ continue;
+ }
+ if ("_teardown" in child) {
+ child._teardown();
+ }
+ child.remove();
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Sets up the menu popup that lets the user hide or display toolbars. For
+ * example, in the appmenu / Preferences view. Adds toolbar items to the popup
+ * and sets their attributes.
+ *
+ * @param {Event} event - Event causing the menu popup to appear.
+ * @param {string|string[]} toolboxIds - IDs of toolboxes that contain toolbars.
+ * @param {Element} insertPoint - Where to insert menu items.
+ * @param {string} elementName - What kind of menu item element to use. E.g.
+ * "toolbarbutton" for the appmenu.
+ * @param {string} classes - Classes to set on menu items.
+ * @param {boolean} keepOpen - If to force the menu to stay open when clicking
+ * on this element.
+ */
+function onViewToolbarsPopupShowing(
+ event,
+ toolboxIds,
+ insertPoint,
+ elementName = "menuitem",
+ classes,
+ keepOpen = false
+) {
+ if (!Array.isArray(toolboxIds)) {
+ toolboxIds = [toolboxIds];
+ }
+
+ let popup = event.target.querySelector(".panel-subview-body") || event.target;
+ // Limit the toolbar menu entries to the first level of context menus.
+ if (
+ popup != event.currentTarget &&
+ event.currentTarget.tagName == "menupopup"
+ ) {
+ return;
+ }
+
+ // Remove all collapsible nodes from the menu.
+ for (let i = popup.children.length - 1; i >= 0; --i) {
+ let deadItem = popup.children[i];
+
+ if (deadItem.hasAttribute("iscollapsible")) {
+ deadItem.remove();
+ }
+ }
+
+ // We insert menuitems before the first child if no insert point is given.
+ let firstMenuItem = insertPoint || popup.firstElementChild;
+
+ for (let toolboxId of toolboxIds) {
+ let toolbars = [];
+ let toolbox = document.getElementById(toolboxId);
+
+ if (toolbox) {
+ // We consider child nodes that have a toolbarname attribute.
+ toolbars = toolbars.concat(
+ Array.from(toolbox.querySelectorAll("[toolbarname]"))
+ );
+ }
+
+ if (
+ toolboxId == "mail-toolbox" &&
+ toolbars.every(
+ toolbar => toolbar.getAttribute("id") !== "toolbar-menubar"
+ )
+ ) {
+ if (
+ AppConstants.platform != "macosx" &&
+ document.getElementById("toolbar-menubar")
+ ) {
+ toolbars.push(document.getElementById("toolbar-menubar"));
+ }
+ }
+
+ for (let toolbar of toolbars) {
+ let toolbarName = toolbar.getAttribute("toolbarname");
+ if (!toolbarName) {
+ continue;
+ }
+
+ let menuItem = document.createXULElement(elementName);
+ let hidingAttribute =
+ toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
+
+ menuItem.setAttribute("type", "checkbox");
+ // Mark this menuitem with an iscollapsible attribute, so we
+ // know we can wipe it out later on.
+ menuItem.setAttribute("iscollapsible", true);
+ menuItem.setAttribute("toolbarid", toolbar.id);
+ menuItem.setAttribute("label", toolbarName);
+ menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
+ menuItem.setAttribute(
+ "checked",
+ toolbar.getAttribute(hidingAttribute) != "true"
+ );
+ if (classes) {
+ menuItem.setAttribute("class", classes);
+ }
+ if (keepOpen) {
+ menuItem.setAttribute("closemenu", "none");
+ }
+ popup.insertBefore(menuItem, firstMenuItem);
+
+ menuItem.addEventListener("command", () => {
+ if (toolbar.getAttribute(hidingAttribute) != "true") {
+ toolbar.setAttribute(hidingAttribute, "true");
+ menuItem.removeAttribute("checked");
+ } else {
+ menuItem.setAttribute("checked", true);
+ toolbar.removeAttribute(hidingAttribute);
+ }
+ Services.xulStore.persist(toolbar, hidingAttribute);
+ });
+ }
+ }
+}
+
+function toJavaScriptConsole() {
+ BrowserConsoleManager.openBrowserConsoleOrFocus();
+}
+
+function openAboutDebugging(hash) {
+ let url = "about:debugging" + (hash ? "#" + hash : "");
+ document.getElementById("tabmail").openTab("contentTab", { url });
+}
+
+function toOpenWindowByType(inType, uri) {
+ var topWindow = Services.wm.getMostRecentWindow(inType);
+ if (topWindow) {
+ topWindow.focus();
+ return topWindow;
+ }
+ return window.open(
+ uri,
+ "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
+ );
+}
+
+function toMessengerWindow() {
+ return toOpenWindowByType(
+ "mail:3pane",
+ "chrome://messenger/content/messenger.xhtml"
+ );
+}
+
+function focusOnMail(tabNo, event) {
+ // this is invoked by accel-<number>
+ var topWindow = Services.wm.getMostRecentWindow("mail:3pane");
+ if (topWindow) {
+ topWindow.focus();
+ const tabmail = document.getElementById("tabmail");
+ if (tabmail.globalOverlay) {
+ return;
+ }
+ tabmail.selectTabByIndex(event, tabNo);
+ } else {
+ window.open(
+ "chrome://messenger/content/messenger.xhtml",
+ "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
+ );
+ }
+}
+
+/**
+ * Open the address book and optionally display/edit a card.
+ *
+ * @param {?object} openArgs - Arguments to pass to the address book.
+ * See `externalAction` in aboutAddressBook.js for details.
+ * @returns {?Window} The address book's window global, if the address book was
+ * opened.
+ */
+async function toAddressBook(openArgs) {
+ let messengerWindow = toMessengerWindow();
+ if (messengerWindow.document.readyState != "complete") {
+ await new Promise(resolve => {
+ Services.obs.addObserver(
+ {
+ observe(subject) {
+ if (subject == messengerWindow) {
+ Services.obs.removeObserver(this, "mail-tabs-session-restored");
+ resolve();
+ }
+ },
+ },
+ "mail-tabs-session-restored"
+ );
+ });
+ }
+
+ if (messengerWindow.tabmail.globalOverlay) {
+ return null;
+ }
+
+ return new Promise(resolve => {
+ messengerWindow.tabmail.openTab("addressBookTab", {
+ onLoad(event, browser) {
+ if (openArgs) {
+ browser.contentWindow.externalAction(openArgs);
+ }
+ resolve(browser.contentWindow);
+ },
+ });
+ messengerWindow.focus();
+ });
+}
+
+/**
+ * Open the calendar.
+ */
+async function toCalendar() {
+ let messengerWindow = toMessengerWindow();
+ if (messengerWindow.document.readyState != "complete") {
+ await new Promise(resolve => {
+ Services.obs.addObserver(
+ {
+ observe(subject) {
+ if (subject == messengerWindow) {
+ Services.obs.removeObserver(this, "mail-tabs-session-restored");
+ resolve();
+ }
+ },
+ },
+ "mail-tabs-session-restored"
+ );
+ });
+ }
+
+ return new Promise(resolve => {
+ messengerWindow.tabmail.openTab("calendar", {
+ onLoad(event, browser) {
+ resolve(browser.contentWindow);
+ },
+ });
+ messengerWindow.focus();
+ });
+}
+
+function showChatTab() {
+ let tabmail = document.getElementById("tabmail");
+ if (gChatTab) {
+ tabmail.switchToTab(gChatTab);
+ } else {
+ tabmail.openTab("chat", {});
+ }
+}
+
+/**
+ * Open about:import or importDialog.xhtml.
+ *
+ * @param {"start"|"app"|"addressBook"|"calendar"|"export"} [tabId] - The tab
+ * to open in about:import.
+ */
+function toImport(tabId = "start") {
+ if (Services.prefs.getBoolPref("mail.import.in_new_tab")) {
+ let tab = toMessengerWindow().openTab("contentTab", {
+ url: "about:import",
+ onLoad(event, browser) {
+ if (tabId) {
+ browser.contentWindow.showTab(`tab-${tabId}`, true);
+ }
+ },
+ });
+ // Somehow DOMContentLoaded is called even when about:import is already
+ // open, which resets the active tab. Use setTimeout here as a workaround.
+ setTimeout(
+ () => tab.browser.contentWindow.showTab(`tab-${tabId}`, true),
+ 100
+ );
+ return;
+ }
+ window.openDialog(
+ "chrome://messenger/content/importDialog.xhtml",
+ "importDialog",
+ "chrome,modal,titlebar,centerscreen"
+ );
+}
+
+function toExport() {
+ if (Services.prefs.getBoolPref("mail.import.in_new_tab")) {
+ toImport("export");
+ return;
+ }
+ window.openDialog(
+ "chrome://messenger/content/exportDialog.xhtml",
+ "exportDialog",
+ "chrome,modal,titlebar,centerscreen"
+ );
+}
+
+function toSanitize() {
+ let sanitizerScope = {};
+ Services.scriptloader.loadSubScript(
+ "chrome://messenger/content/sanitize.js",
+ sanitizerScope
+ );
+ sanitizerScope.Sanitizer.sanitize(window);
+}
+
+/**
+ * Opens the Preferences (Options) dialog.
+ *
+ * @param aPaneID ID of prefpane to select automatically.
+ * @param aScrollPaneTo ID of the element to scroll into view.
+ * @param aOtherArgs other prefpane specific arguments
+ */
+function openOptionsDialog(aPaneID, aScrollPaneTo, aOtherArgs) {
+ openPreferencesTab(aPaneID, aScrollPaneTo, aOtherArgs);
+}
+
+function openAddonsMgr(aView) {
+ return new Promise(resolve => {
+ let emWindow;
+ let browserWindow;
+
+ let receivePong = function (aSubject, aTopic, aData) {
+ let browserWin = aSubject.browsingContext.topChromeWindow;
+ if (!emWindow || browserWin == window /* favor the current window */) {
+ emWindow = aSubject;
+ browserWindow = browserWin;
+ }
+ };
+ Services.obs.addObserver(receivePong, "EM-pong");
+ Services.obs.notifyObservers(null, "EM-ping");
+ Services.obs.removeObserver(receivePong, "EM-pong");
+
+ if (emWindow) {
+ if (aView) {
+ emWindow.loadView(aView);
+ }
+ let tabmail = browserWindow.document.getElementById("tabmail");
+ tabmail.switchToTab(tabmail.getBrowserForDocument(emWindow));
+ emWindow.focus();
+ resolve(emWindow);
+ return;
+ }
+
+ // This must be a new load, else the ping/pong would have
+ // found the window above.
+ let tab = openContentTab("about:addons");
+ // Also in `contentTabType.restoreTab` in specialTabs.js.
+ tab.browser.droppedLinkHandler = event =>
+ tab.browser.contentWindow.gDragDrop.onDrop(event);
+
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observer, aTopic);
+ if (aView) {
+ aSubject.loadView(aView);
+ }
+ aSubject.focus();
+ resolve(aSubject);
+ }, "EM-loaded");
+ });
+}
+
+function openActivityMgr() {
+ Cc["@mozilla.org/activity-manager-ui;1"]
+ .getService(Ci.nsIActivityManagerUI)
+ .show(window);
+}
+
+/**
+ * Open the folder properties of current folder with the quota tab selected.
+ */
+function openFolderQuota() {
+ document
+ .getElementById("tabmail")
+ .currentAbout3Pane?.folderPane.editFolder("QuotaTab");
+}
+
+function openIMAccountMgr() {
+ var win = Services.wm.getMostRecentWindow("Messenger:Accounts");
+ if (win) {
+ win.focus();
+ } else {
+ win = Services.ww.openWindow(
+ null,
+ "chrome://messenger/content/chat/imAccounts.xhtml",
+ "Accounts",
+ "chrome,resizable,centerscreen",
+ null
+ );
+ }
+ return win;
+}
+
+function openIMAccountWizard() {
+ const kFeatures = "chrome,centerscreen,modal,titlebar";
+ const kUrl = "chrome://messenger/content/chat/imAccountWizard.xhtml";
+ const kName = "IMAccountWizard";
+
+ if (AppConstants.platform == "macosx") {
+ // On Mac, avoid using the hidden window as a parent as that would
+ // make it visible.
+ let hiddenWindowUrl = Services.prefs.getCharPref(
+ "browser.hiddenWindowChromeURL"
+ );
+ if (window.location.href == hiddenWindowUrl) {
+ Services.ww.openWindow(null, kUrl, kName, kFeatures, null);
+ return;
+ }
+ }
+
+ window.openDialog(kUrl, kName, kFeatures);
+}
+
+function openSavedFilesWnd() {
+ if (window.tabmail?.globalOverlay) {
+ return Promise.resolve();
+ }
+ return openContentTab("about:downloads");
+}
+
+function SetBusyCursor(window, enable) {
+ // setCursor() is only available for chrome windows.
+ // However one of our frames is the start page which
+ // is a non-chrome window, so check if this window has a
+ // setCursor method
+ if ("setCursor" in window) {
+ if (enable) {
+ window.setCursor("progress");
+ } else {
+ window.setCursor("auto");
+ }
+ }
+
+ var numFrames = window.frames.length;
+ for (var i = 0; i < numFrames; i++) {
+ SetBusyCursor(window.frames[i], enable);
+ }
+}
+
+function openAboutDialog() {
+ for (let win of Services.wm.getEnumerator("Mail:About")) {
+ // Only open one about window
+ win.focus();
+ return;
+ }
+
+ let features = "chrome,centerscreen,";
+ if (AppConstants.platform == "win") {
+ features += "dependent";
+ } else if (AppConstants.platform == "macosx") {
+ features += "resizable=no,minimizable=no";
+ } else {
+ features += "dependent,dialog=no";
+ }
+
+ window.openDialog(
+ "chrome://messenger/content/aboutDialog.xhtml",
+ "About",
+ features
+ );
+}
+
+/**
+ * Opens the support page based on the app.support.baseURL pref.
+ */
+function openSupportURL() {
+ openFormattedURL("app.support.baseURL");
+}
+
+/**
+ * Fetches the url for the passed in pref name, formats it and then loads it in the default
+ * browser.
+ *
+ * @param aPrefName - name of the pref that holds the url we want to format and open
+ */
+function openFormattedURL(aPrefName) {
+ var urlToOpen = Services.urlFormatter.formatURLPref(aPrefName);
+
+ var uri = Services.io.newURI(urlToOpen);
+
+ var protocolSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ protocolSvc.loadURI(uri);
+}
+
+/**
+ * Opens the Troubleshooting page in a new tab.
+ */
+function openAboutSupport() {
+ let mailWindow = Services.wm.getMostRecentWindow("mail:3pane");
+ if (mailWindow) {
+ mailWindow.focus();
+ mailWindow.document.getElementById("tabmail").openTab("contentTab", {
+ url: "about:support",
+ });
+ return;
+ }
+
+ window.openDialog(
+ "chrome://messenger/content/messenger.xhtml",
+ "_blank",
+ "chrome,dialog=no,all",
+ null,
+ {
+ tabType: "contentTab",
+ tabParams: { url: "about:support" },
+ }
+ );
+}
+
+/**
+ * Prompt the user to restart the browser in safe mode.
+ */
+function safeModeRestart() {
+ // Is TB in safe mode?
+ if (Services.appinfo.inSafeMode) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
+ Ci.nsISupportsPRBool
+ );
+ Services.obs.notifyObservers(
+ cancelQuit,
+ "quit-application-requested",
+ "restart"
+ );
+
+ if (cancelQuit.data) {
+ return;
+ }
+
+ Services.startup.quit(
+ Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
+ );
+ return;
+ }
+ // prompt the user to confirm
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ let promptTitle = bundle.GetStringFromName(
+ "troubleshootModeRestartPromptTitle"
+ );
+ let promptMessage = bundle.GetStringFromName(
+ "troubleshootModeRestartPromptMessage"
+ );
+ let restartText = bundle.GetStringFromName("troubleshootModeRestartButton");
+ let buttonFlags =
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+
+ let rv = Services.prompt.confirmEx(
+ window,
+ promptTitle,
+ promptMessage,
+ buttonFlags,
+ restartText,
+ null,
+ null,
+ null,
+ {}
+ );
+ if (rv == 0) {
+ Services.env.set("MOZ_SAFE_MODE_RESTART", "1");
+ let { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+ MailUtils.restartApplication();
+ }
+}
+
+function getMostRecentMailWindow() {
+ let win = null;
+
+ win = Services.wm.getMostRecentWindow("mail:3pane", true);
+
+ // If we're lucky, this isn't a popup, and we can just return this.
+ if (win && win.document.documentElement.getAttribute("chromehidden")) {
+ win = null;
+ // This is oldest to newest, so this gets a bit ugly.
+ for (let nextWin of Services.wm.getEnumerator("mail:3pane", true)) {
+ if (!nextWin.document.documentElement.getAttribute("chromehidden")) {
+ win = nextWin;
+ }
+ }
+ }
+
+ return win;
+}
+
+/**
+ * Create a sanitized display name for an attachment in order to help prevent
+ * people from hiding malicious extensions behind a run of spaces, etc. To do
+ * this, we strip leading/trailing whitespace and collapse long runs of either
+ * whitespace or identical characters. Windows especially will drop trailing
+ * dots and whitespace from filename extensions.
+ *
+ * @param aAttachment the AttachmentInfo object
+ * @returns a sanitized display name for the attachment
+ */
+function SanitizeAttachmentDisplayName(aAttachment) {
+ let displayName = aAttachment.name.trim().replace(/\s+/g, " ");
+ if (AppConstants.platform == "win") {
+ displayName = displayName.replace(/[ \.]+$/, "");
+ }
+ return displayName.replace(/(.)\1{9,}/g, "$1…$1");
+}
+
+/**
+ * Appends a dataTransferItem to the associated event for message attachments,
+ * either from the message reader or the composer.
+ *
+ * @param {Event} event - The associated event.
+ * @param {nsIMsgAttachment[]} attachments - The attachments to setup
+ */
+function setupDataTransfer(event, attachments) {
+ let index = 0;
+ for (let attachment of attachments) {
+ if (attachment.contentType == "text/x-moz-deleted") {
+ return;
+ }
+
+ let name = attachment.name || attachment.displayName;
+
+ if (!attachment.url || !name) {
+ continue;
+ }
+
+ // Only add type/filename info for non-file URLs that don't already
+ // have it.
+ let info = [];
+ if (/(^file:|&filename=)/.test(attachment.url)) {
+ info.push(attachment.url);
+ } else {
+ info.push(
+ attachment.url +
+ "&type=" +
+ attachment.contentType +
+ "&filename=" +
+ encodeURIComponent(name)
+ );
+ }
+ info.push(name, attachment.size, attachment.contentType, attachment.uri);
+ if (attachment.sendViaCloud) {
+ info.push(attachment.cloudFileAccountKey, attachment.cloudPartHeaderData);
+ }
+
+ event.dataTransfer.mozSetDataAt("text/x-moz-url", info.join("\n"), index);
+ event.dataTransfer.mozSetDataAt(
+ "text/x-moz-url-data",
+ attachment.url,
+ index
+ );
+ event.dataTransfer.mozSetDataAt("text/x-moz-url-desc", name, index);
+ event.dataTransfer.mozSetDataAt(
+ "application/x-moz-file-promise-url",
+ attachment.url,
+ index
+ );
+ event.dataTransfer.mozSetDataAt(
+ "application/x-moz-file-promise",
+ new nsFlavorDataProvider(),
+ index
+ );
+ event.dataTransfer.mozSetDataAt(
+ "application/x-moz-file-promise-dest-filename",
+ name.replace(/(.{74}).*(.{10})$/u, "$1...$2"),
+ index
+ );
+ index++;
+ }
+}
+
+/**
+ * Checks if Thunderbird was launched in safe mode and updates the menu items.
+ */
+function updateTroubleshootMenuItem() {
+ if (Services.appinfo.inSafeMode) {
+ let safeMode = document.getElementById("helpTroubleshootMode");
+ document.l10n.setAttributes(safeMode, "menu-help-exit-troubleshoot-mode");
+
+ let appSafeMode = document.getElementById("appmenu_troubleshootMode");
+ if (appSafeMode) {
+ document.l10n.setAttributes(
+ appSafeMode,
+ "appmenu-help-exit-troubleshoot-mode2"
+ );
+ }
+ }
+}
+
+function nsFlavorDataProvider() {}
+
+nsFlavorDataProvider.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFlavorDataProvider"]),
+
+ getFlavorData(aTransferable, aFlavor, aData) {
+ // get the url for the attachment
+ if (aFlavor == "application/x-moz-file-promise") {
+ var urlPrimitive = {};
+ aTransferable.getTransferData(
+ "application/x-moz-file-promise-url",
+ urlPrimitive
+ );
+
+ var srcUrlPrimitive = urlPrimitive.value.QueryInterface(
+ Ci.nsISupportsString
+ );
+
+ // now get the destination file location from kFilePromiseDirectoryMime
+ var dirPrimitive = {};
+ aTransferable.getTransferData(
+ "application/x-moz-file-promise-dir",
+ dirPrimitive
+ );
+ var destDirectory = dirPrimitive.value.QueryInterface(Ci.nsIFile);
+
+ // now save the attachment to the specified location
+ // XXX: we need more information than just the attachment url to save it,
+ // fortunately, we have an array of all the current attachments so we can
+ // cheat and scan through them
+
+ var attachment = null;
+ for (let index of currentAttachments.keys()) {
+ attachment = currentAttachments[index];
+ if (attachment.url == srcUrlPrimitive) {
+ break;
+ }
+ }
+
+ // call our code for saving attachments
+ if (attachment) {
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ let name = attachment.name || attachment.displayName;
+ let destFilePath = messenger.saveAttachmentToFolder(
+ attachment.contentType,
+ attachment.url,
+ name.replace(/(.{74}).*(.{10})$/u, "$1...$2"),
+ attachment.uri,
+ destDirectory
+ );
+ aData.value = destFilePath.QueryInterface(Ci.nsISupports);
+ }
+ if (AppConstants.platform == "macosx") {
+ // Workaround dnd of multiple attachments creating duplicates. See bug 1494588.
+ aTransferable.removeDataFlavor("application/x-moz-file-promise");
+ }
+ }
+ },
+};