diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/base/content/mailCore.js | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.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 '')
-rw-r--r-- | comm/mail/base/content/mailCore.js | 1063 |
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"); + } + } + }, +}; |