summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/messenger.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/content/messenger.js')
-rw-r--r--comm/mail/base/content/messenger.js1289
1 files changed, 1289 insertions, 0 deletions
diff --git a/comm/mail/base/content/messenger.js b/comm/mail/base/content/messenger.js
new file mode 100644
index 0000000000..6e11a3b538
--- /dev/null
+++ b/comm/mail/base/content/messenger.js
@@ -0,0 +1,1289 @@
+/**
+ * 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/. */
+
+/* import-globals-from ../../../mailnews/base/prefs/content/accountUtils.js */
+/* import-globals-from ../../components/addrbook/content/addressBookTab.js */
+/* import-globals-from ../../components/customizableui/content/panelUI.js */
+/* import-globals-from ../../components/newmailaccount/content/provisionerCheckout.js */
+/* import-globals-from ../../components/preferences/preferencesTab.js */
+/* import-globals-from glodaFacetTab.js */
+/* import-globals-from mailCore.js */
+/* import-globals-from mail-offline.js */
+/* import-globals-from mailTabs.js */
+/* import-globals-from mailWindowOverlay.js */
+/* import-globals-from messenger-customization.js */
+/* import-globals-from searchBar.js */
+/* import-globals-from spacesToolbar.js */
+/* import-globals-from specialTabs.js */
+/* import-globals-from toolbarIconColor.js */
+
+/* globals CreateMailWindowGlobals, InitMsgWindow, OnMailWindowUnload */ // From mailWindow.js
+
+/* globals loadCalendarComponent */
+
+ChromeUtils.import("resource:///modules/activity/activityModules.jsm");
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ Color: "resource://gre/modules/Color.sys.mjs",
+ ctypes: "resource://gre/modules/ctypes.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ BondOpenPGP: "chrome://openpgp/content/BondOpenPGP.jsm",
+ MailConsts: "resource:///modules/MailConsts.jsm",
+ MailUtils: "resource:///modules/MailUtils.jsm",
+ msgDBCacheManager: "resource:///modules/MsgDBCacheManager.jsm",
+ PeriodicFilterManager: "resource:///modules/PeriodicFilterManager.jsm",
+ SessionStoreManager: "resource:///modules/SessionStoreManager.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let { PopupNotifications } = ChromeUtils.import(
+ "resource:///modules/GlobalPopupNotifications.jsm"
+ );
+ try {
+ // Hide all notifications while the URL is being edited and the address bar
+ // has focus, including the virtual focus in the results popup.
+ // We also have to hide notifications explicitly when the window is
+ // minimized because of the effects of the "noautohide" attribute on Linux.
+ // This can be removed once bug 545265 and bug 1320361 are fixed.
+ let shouldSuppress = () => window.windowState == window.STATE_MINIMIZED;
+ return new PopupNotifications(
+ document.getElementById("tabmail"),
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"),
+ { shouldSuppress }
+ );
+ } catch (ex) {
+ console.error(ex);
+ return null;
+ }
+});
+
+/**
+ * Gets the service pack and build information on Windows platforms. The initial version
+ * was copied from nsUpdateService.js.
+ *
+ * @returns An object containing the service pack major and minor versions, along with the
+ * build number.
+ */
+function getWindowsVersionInfo() {
+ const UNKNOWN_VERSION_INFO = {
+ servicePackMajor: null,
+ servicePackMinor: null,
+ buildNumber: null,
+ };
+
+ if (AppConstants.platform !== "win") {
+ return UNKNOWN_VERSION_INFO;
+ }
+
+ const BYTE = ctypes.uint8_t;
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const WCHAR = ctypes.char16_t;
+ const BOOL = ctypes.int;
+
+ // This structure is described at:
+ // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
+ const SZCSDVERSIONLENGTH = 128;
+ const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW", [
+ { dwOSVersionInfoSize: DWORD },
+ { dwMajorVersion: DWORD },
+ { dwMinorVersion: DWORD },
+ { dwBuildNumber: DWORD },
+ { dwPlatformId: DWORD },
+ { szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH) },
+ { wServicePackMajor: WORD },
+ { wServicePackMinor: WORD },
+ { wSuiteMask: WORD },
+ { wProductType: BYTE },
+ { wReserved: BYTE },
+ ]);
+
+ let kernel32 = ctypes.open("kernel32");
+ try {
+ let GetVersionEx = kernel32.declare(
+ "GetVersionExW",
+ ctypes.winapi_abi,
+ BOOL,
+ OSVERSIONINFOEXW.ptr
+ );
+ let winVer = OSVERSIONINFOEXW();
+ winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
+
+ if (0 === GetVersionEx(winVer.address())) {
+ throw new Error("Failure in GetVersionEx (returned 0)");
+ }
+
+ return {
+ servicePackMajor: winVer.wServicePackMajor,
+ servicePackMinor: winVer.wServicePackMinor,
+ buildNumber: winVer.dwBuildNumber,
+ };
+ } catch (e) {
+ return UNKNOWN_VERSION_INFO;
+ } finally {
+ kernel32.close();
+ }
+}
+
+/* This is where functions related to the 3 pane window are kept */
+
+// from MailNewsTypes.h
+var kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
+
+/**
+ * Tracks whether the right mouse button changed the selection or not. If the
+ * user right clicks on the selection, it stays the same. If they click outside
+ * of it, we alter the selection (but not the current index) to be the row they
+ * clicked on.
+ *
+ * The value of this variable is an object with "view" and "selection" keys
+ * and values. The view value is the view whose selection we saved off, and
+ * the selection value is the selection object we saved off.
+ */
+var gRightMouseButtonSavedSelection = null;
+var gNewAccountToLoad = null;
+
+// The object in charge of managing the mail summary pane
+var gSummaryFrameManager;
+
+/**
+ * Called on startup if there are no accounts.
+ */
+function verifyOpenAccountHubTab() {
+ let suppressDialogs = Services.prefs.getBoolPref(
+ "mail.provider.suppress_dialog_on_startup",
+ false
+ );
+
+ if (suppressDialogs) {
+ // Looks like we were in the middle of filling out an account form. We
+ // won't display the dialogs in that case.
+ Services.prefs.clearUserPref("mail.provider.suppress_dialog_on_startup");
+ loadPostAccountWizard();
+ return;
+ }
+
+ openAccountSetupTab();
+}
+
+let _resolveDelayedStartup;
+var delayedStartupPromise = new Promise(resolve => {
+ _resolveDelayedStartup = resolve;
+});
+
+var gMailInit = {
+ onBeforeInitialXULLayout() {
+ // Set a sane starting width/height for all resolutions on new profiles.
+ // Do this before the window loads.
+ if (!document.documentElement.hasAttribute("width")) {
+ const TARGET_WIDTH = 1280;
+ let defaultWidth = Math.min(screen.availWidth * 0.9, TARGET_WIDTH);
+ let defaultHeight = screen.availHeight;
+
+ document.documentElement.setAttribute("width", defaultWidth);
+ document.documentElement.setAttribute("height", defaultHeight);
+
+ // On small screens, default to maximized state.
+ if (defaultWidth < TARGET_WIDTH) {
+ document.documentElement.setAttribute("sizemode", "maximized");
+ }
+ // Make sure we're safe at the left/top edge of screen
+ document.documentElement.setAttribute("screenX", screen.availLeft);
+ document.documentElement.setAttribute("screenY", screen.availTop);
+ }
+
+ // Run menubar initialization first, to avoid TabsInTitlebar code picking
+ // up mutations from it and causing a reflow.
+ AutoHideMenubar.init();
+ TabsInTitlebar.init();
+
+ if (AppConstants.platform == "win") {
+ // On Win8 set an attribute when the window frame color is too dark for black text.
+ if (
+ window.matchMedia("(-moz-platform: windows-win8)").matches &&
+ window.matchMedia("(-moz-windows-default-theme)").matches
+ ) {
+ let { Windows8WindowFrameColor } = ChromeUtils.importESModule(
+ "resource:///modules/Windows8WindowFrameColor.sys.mjs"
+ );
+ let windowFrameColor = new Color(...Windows8WindowFrameColor.get());
+ // Default to black for foreground text.
+ if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
+ document.documentElement.setAttribute("darkwindowframe", "true");
+ }
+ } else if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+ // 17763 is the build number of Windows 10 version 1809
+ if (getWindowsVersionInfo().buildNumber < 17763) {
+ document.documentElement.setAttribute(
+ "always-use-accent-color-for-window-border",
+ ""
+ );
+ }
+ }
+ }
+
+ // Call this after we set attributes that might change toolbars' computed
+ // text color.
+ ToolbarIconColor.init();
+ },
+
+ /**
+ * Called on startup to initialize various parts of the main window.
+ * Most of this should be moved out into _delayedStartup or only
+ * initialized when needed.
+ */
+ onLoad() {
+ CreateMailWindowGlobals();
+
+ if (!Services.policies.isAllowed("devtools")) {
+ let devtoolsMenu = document.getElementById("devtoolsMenu");
+ if (devtoolsMenu) {
+ devtoolsMenu.hidden = true;
+ }
+ }
+
+ // - initialize tabmail system
+ // Do this before loadPostAccountWizard since that code selects the first
+ // folder for display, and we want gFolderDisplay setup and ready to handle
+ // that event chain.
+ // Also, we definitely need to register the tab type prior to the call to
+ // specialTabs.openSpecialTabsOnStartup below.
+ let tabmail = document.getElementById("tabmail");
+ if (tabmail) {
+ // mailTabType is defined in mailTabs.js
+ tabmail.registerTabType(mailTabType);
+ // glodaFacetTab* in glodaFacetTab.js
+ tabmail.registerTabType(glodaFacetTabType);
+ tabmail.registerTabMonitor(GlodaSearchBoxTabMonitor);
+ tabmail.openFirstTab();
+ }
+
+ // This also registers the contentTabType ("contentTab")
+ specialTabs.openSpecialTabsOnStartup();
+ tabmail.registerTabType(addressBookTabType);
+ tabmail.registerTabType(preferencesTabType);
+ // provisionerCheckoutTabType is defined in provisionerCheckout.js
+ tabmail.registerTabType(provisionerCheckoutTabType);
+
+ // Depending on the pref, hide/show the gloda toolbar search widgets.
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "gGlodaEnabled",
+ "mailnews.database.global.indexer.enabled",
+ true,
+ (pref, oldVal, newVal) => {
+ for (let widget of document.querySelectorAll(".gloda-search-widget")) {
+ widget.hidden = !newVal;
+ }
+ }
+ );
+ for (let widget of document.querySelectorAll(".gloda-search-widget")) {
+ widget.hidden = !this.gGlodaEnabled;
+ }
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ this._boundDelayedStartup = this._delayedStartup.bind(this);
+ window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+ // Listen for the messages sent to the main 3 pane window.
+ window.addEventListener("message", this._onMessageReceived);
+ },
+
+ _cancelDelayedStartup() {
+ window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
+ this._boundDelayedStartup = null;
+ },
+
+ /**
+ * Handle the messages sent via postMessage() method to the main 3 pane
+ * window.
+ *
+ * @param {Event} event - The message event.
+ */
+ _onMessageReceived(event) {
+ switch (event.data) {
+ case "account-created":
+ case "account-created-in-backend":
+ case "account-created-from-provisioner":
+ // Set the pref to false in case it was previously changed.
+ Services.prefs.setBoolPref("app.use_without_mail_account", false);
+ loadPostAccountWizard();
+
+ // Always update the mail UI to guarantee all the panes are visible even
+ // if the mail tab is not the currently active tab.
+ updateMailPaneUI();
+ break;
+
+ case "account-setup-closed":
+ // The user closed the account setup after a successful run. Make sure
+ // to focus on the primary mail tab.
+ switchToMailTab();
+ gSpacesToolbar.onLoad();
+ // Trigger the integration dialog if necessary.
+ showSystemIntegrationDialog();
+ break;
+
+ case "account-setup-dismissed":
+ // The user closed the account setup before completing it. Be sure to
+ // initialize the few important areas we need.
+ if (!gSpacesToolbar.isLoaded) {
+ loadPostAccountWizard();
+ }
+ break;
+
+ case "open-account-setup-tab":
+ openAccountSetupTab();
+ break;
+ default:
+ break;
+ }
+ },
+
+ /**
+ * Delayed startup happens after the first paint of the window. Anything
+ * that can be delayed until after paint, should be to help give the
+ * illusion that Thunderbird is starting faster.
+ *
+ * Note: this only runs for the main 3 pane window.
+ */
+ _delayedStartup() {
+ this._cancelDelayedStartup();
+
+ MailOfflineMgr.init();
+
+ BondOpenPGP.init();
+
+ PanelUI.init();
+ gExtensionsNotifications.init();
+
+ Services.search.init();
+
+ PeriodicFilterManager.setupFiltering();
+ msgDBCacheManager.init();
+
+ this.delayedStartupFinished = true;
+ _resolveDelayedStartup(window);
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
+
+ // Notify observer to resolve the browserStartupPromise, which is used for the
+ // delayed background startup of WebExtensions.
+ Services.obs.notifyObservers(window, "extensions-late-startup");
+
+ this._loadComponentsAtStartup();
+ },
+
+ /**
+ * Load all the necessary components to make Thunderbird usable before
+ * checking for existing accounts.
+ */
+ async _loadComponentsAtStartup() {
+ updateTroubleshootMenuItem();
+ // The calendar component needs to be loaded before restoring any tabs.
+ await loadCalendarComponent();
+
+ // Don't trigger the existing account verification if the user wants to use
+ // Thunderbird without an email account.
+ if (!Services.prefs.getBoolPref("app.use_without_mail_account", false)) {
+ // Load the Mail UI only if we already have at least one account configured
+ // otherwise the verifyExistingAccounts will trigger the account wizard.
+ if (verifyExistingAccounts()) {
+ switchToMailTab();
+ await loadPostAccountWizard();
+ }
+ } else {
+ // Run the tabs restore method here since we're skipping the loading of
+ // the Mail UI which would have taken care of this to properly handle
+ // opened folders or messages in tabs.
+ await atStartupRestoreTabs(false);
+ gSpacesToolbar.onLoad();
+ }
+
+ // Show the end of year donation appeal page.
+ if (this.shouldShowEOYDonationAppeal()) {
+ // Add a timeout to prevent opening the browser immediately at startup.
+ setTimeout(this.showEOYDonationAppeal, 2000);
+ }
+ },
+
+ /**
+ * Called by messenger.xhtml:onunload, the 3-pane window inside of tabs window.
+ * It's being unloaded! Right now!
+ */
+ onUnload() {
+ Services.obs.notifyObservers(window, "mail-unloading-messenger");
+
+ if (gRightMouseButtonSavedSelection) {
+ // Avoid possible cycle leaks.
+ gRightMouseButtonSavedSelection.view = null;
+ gRightMouseButtonSavedSelection = null;
+ }
+
+ SessionStoreManager.unloadingWindow(window);
+ TabsInTitlebar.uninit();
+ ToolbarIconColor.uninit();
+ gSpacesToolbar.onUnload();
+
+ document.getElementById("tabmail")._teardown();
+
+ OnMailWindowUnload();
+ },
+
+ /**
+ * Check if we can trigger the opening of the donation appeal page.
+ *
+ * @returns {boolean} - True if the donation appeal page should be opened.
+ */
+ shouldShowEOYDonationAppeal() {
+ let currentEOY = Services.prefs.getIntPref("app.donation.eoy.version", 1);
+ let viewedEOY = Services.prefs.getIntPref(
+ "app.donation.eoy.version.viewed",
+ 0
+ );
+
+ // True if the user never saw the donation appeal, this is not a new
+ // profile (since users are already prompted to donate after downloading),
+ // and we're not running tests.
+ return (
+ viewedEOY < currentEOY &&
+ !specialTabs.shouldShowPolicyNotification() &&
+ !Cu.isInAutomation
+ );
+ },
+
+ /**
+ * Open the end of year appeal in a new web browser page. We don't open this
+ * in a tab due to the complexity of the donation site, and we don't want to
+ * handle that inside Thunderbird.
+ */
+ showEOYDonationAppeal() {
+ let url = Services.prefs.getStringPref("app.donation.eoy.url");
+ let protocolSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ protocolSvc.loadURI(Services.io.newURI(url));
+
+ let currentEOY = Services.prefs.getIntPref("app.donation.eoy.version", 1);
+ Services.prefs.setIntPref("app.donation.eoy.version.viewed", currentEOY);
+ },
+};
+
+/**
+ * Called at startup to verify if we have ny existing account, even if invalid,
+ * and if not, it will trigger the Account Hub in a tab.
+ *
+ * @returns {boolean} - True if we have at least one existing account.
+ */
+function verifyExistingAccounts() {
+ try {
+ // Migrate quoting preferences from global to per account. This function
+ // returns true if it had to migrate, which we will use to mean this is a
+ // just migrated or new profile.
+ let newProfile = migrateGlobalQuotingPrefs(
+ MailServices.accounts.allIdentities
+ );
+
+ // If there are no accounts, or all accounts are "invalid" then kick off the
+ // account migration. Or if this is a new (to Mozilla) profile. MCD can set
+ // up accounts without the profile being used yet.
+ if (newProfile) {
+ // Check if MCD is configured. If not, say this is not a new profile so
+ // that we don't accidentally remigrate non MCD profiles.
+ var adminUrl = Services.prefs.getCharPref(
+ "autoadmin.global_config_url",
+ ""
+ );
+ if (!adminUrl) {
+ newProfile = false;
+ }
+ }
+
+ let accounts = MailServices.accounts.accounts;
+ let invalidAccounts = getInvalidAccounts(accounts);
+ // Trigger the new account configuration wizard only if we don't have any
+ // existing account, not even if we have at least one invalid account.
+ if (
+ (newProfile && !accounts.length) ||
+ accounts.length == invalidAccounts.length ||
+ (invalidAccounts.length > 0 &&
+ invalidAccounts.length == accounts.length &&
+ invalidAccounts[0])
+ ) {
+ verifyOpenAccountHubTab();
+ return false;
+ }
+
+ let localFoldersExists;
+ try {
+ localFoldersExists = MailServices.accounts.localFoldersServer;
+ } catch (ex) {
+ localFoldersExists = false;
+ }
+
+ // We didn't trigger the account configuration wizard, so we need to verify
+ // that local folders exists.
+ if (!localFoldersExists && requireLocalFoldersAccount()) {
+ MailServices.accounts.createLocalMailAccount();
+ }
+
+ return true;
+ } catch (ex) {
+ dump(`Error verifying accounts: ${ex}`);
+ return false;
+ }
+}
+
+/**
+ * Switch the view to the first Mail tab if the currently selected tab is not
+ * the first Mail tab.
+ */
+function switchToMailTab() {
+ let tabmail = document.getElementById("tabmail");
+ if (tabmail?.selectedTab.mode.name != "folder") {
+ tabmail.switchToTab(0);
+ }
+}
+
+/**
+ * Trigger the initialization of the entire UI. Called after the okCallback of
+ * the emailWizard during a first run, or directly from the accountProvisioner
+ * in case a user configures a new email account on first run.
+ */
+async function loadPostAccountWizard() {
+ InitMsgWindow();
+
+ MigrateJunkMailSettings();
+ MigrateFolderViews();
+ MigrateOpenMessageBehavior();
+
+ MailServices.accounts.setSpecialFolders();
+
+ try {
+ MailServices.accounts.loadVirtualFolders();
+ } catch (e) {
+ console.error(e);
+ }
+
+ // Init the mozINewMailListener service (MailNotificationManager) before
+ // any new mails are fetched.
+ // MailNotificationManager triggers mozINewMailNotificationService
+ // init as well.
+ Cc["@mozilla.org/mail/notification-manager;1"].getService(
+ Ci.mozINewMailListener
+ );
+
+ // Restore the previous folder selection before shutdown, or select the first
+ // inbox folder of a newly created account.
+ await selectFirstFolder();
+
+ gSpacesToolbar.onLoad();
+}
+
+/**
+ * Check if we need to show the system integration dialog before notifying the
+ * application that the startup process is completed.
+ */
+function showSystemIntegrationDialog() {
+ // Check the shell service.
+ let shellService;
+ try {
+ shellService = Cc["@mozilla.org/mail/shell-service;1"].getService(
+ Ci.nsIShellService
+ );
+ } catch (ex) {}
+ let defaultAccount = MailServices.accounts.defaultAccount;
+
+ // Load the search integration module.
+ let { SearchIntegration } = ChromeUtils.import(
+ "resource:///modules/SearchIntegration.jsm"
+ );
+
+ // Show the default client dialog only if
+ // EITHER: we have at least one account, and we aren't already the default
+ // for mail,
+ // OR: we have the search integration module, the OS version is suitable,
+ // and the first run hasn't already been completed.
+ // Needs to be shown outside the he normal load sequence so it doesn't appear
+ // before any other displays, in the wrong place of the screen.
+ if (
+ (shellService &&
+ defaultAccount &&
+ shellService.shouldCheckDefaultClient &&
+ !shellService.isDefaultClient(true, Ci.nsIShellService.MAIL)) ||
+ (SearchIntegration &&
+ !SearchIntegration.osVersionTooLow &&
+ !SearchIntegration.osComponentsNotRunning &&
+ !SearchIntegration.firstRunDone)
+ ) {
+ window.openDialog(
+ "chrome://messenger/content/systemIntegrationDialog.xhtml",
+ "SystemIntegration",
+ "modal,centerscreen,chrome,resizable=no"
+ );
+ // On Windows, there seems to be a delay between setting TB as the
+ // default client, and the isDefaultClient check succeeding.
+ if (shellService.isDefaultClient(true, Ci.nsIShellService.MAIL)) {
+ Services.obs.notifyObservers(window, "mail:setAsDefault");
+ }
+ }
+}
+
+/**
+ * Properly select the starting folder or message header if we have one.
+ */
+async function selectFirstFolder() {
+ let startFolderURI = null;
+ let startMsgHdr = null;
+
+ if ("arguments" in window && window.arguments.length > 0) {
+ let arg0 = window.arguments[0];
+ // If the argument is a string, it is folder URI.
+ if (typeof arg0 == "string") {
+ startFolderURI = arg0;
+ } else if (arg0) {
+ // arg0 is an object
+ if ("wrappedJSObject" in arg0 && arg0.wrappedJSObject) {
+ arg0 = arg0.wrappedJSObject;
+ }
+ startMsgHdr = "msgHdr" in arg0 ? arg0.msgHdr : null;
+ }
+ }
+
+ // Don't try to be smart with this because we need the loadStartFolder()
+ // method to run even if startFolderURI is null otherwise our UI won't
+ // properly restore.
+ if (startMsgHdr) {
+ await loadStartMsgHdr(startMsgHdr);
+ } else {
+ await loadStartFolder(startFolderURI);
+ }
+}
+
+function HandleAppCommandEvent(evt) {
+ evt.stopPropagation();
+ switch (evt.command) {
+ case "Back":
+ goDoCommand("cmd_goBack");
+ break;
+ case "Forward":
+ goDoCommand("cmd_goForward");
+ break;
+ case "Stop":
+ msgWindow.StopUrls();
+ break;
+ case "Bookmarks":
+ toAddressBook();
+ break;
+ case "Home":
+ case "Reload":
+ default:
+ break;
+ }
+}
+
+/**
+ * Called by the session store manager periodically and at shutdown to get
+ * the state of this window for persistence.
+ */
+function getWindowStateForSessionPersistence() {
+ let tabmail = document.getElementById("tabmail");
+ let tabsState = tabmail.persistTabs();
+ return { type: "3pane", tabs: tabsState };
+}
+
+/**
+ * Attempt to restore the previous tab states.
+ *
+ * @param {boolean} aDontRestoreFirstTab - If this is true, the first tab will
+ * not be restored, and will continue to retain focus at the end. This is
+ * needed if the window was opened with a folder or a message as an argument.
+ * @returns true if the restoration was successful, false otherwise.
+ */
+async function atStartupRestoreTabs(aDontRestoreFirstTab) {
+ let state = await SessionStoreManager.loadingWindow(window);
+ if (state) {
+ let tabsState = state.tabs;
+ let tabmail = document.getElementById("tabmail");
+ try {
+ tabmail.restoreTabs(tabsState, aDontRestoreFirstTab);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ // It's now safe to load extra Tabs.
+ loadExtraTabs();
+
+ // Note: The tabs have not finished loading at this point.
+ SessionStoreManager._restored = true;
+ Services.obs.notifyObservers(window, "mail-tabs-session-restored");
+
+ return !!state;
+}
+
+/**
+ * Loads and restores tabs upon opening a window by evaluating window.arguments[1].
+ *
+ * The type of the object is specified by it's action property. It can be
+ * either "restore" or "open". "restore" invokes tabmail.restoreTab() for each
+ * item in the tabs array. While "open" invokes tabmail.openTab() for each item.
+ *
+ * In case a tab can't be restored it will fail silently
+ *
+ * the object need at least the following properties:
+ *
+ * {
+ * action = "restore" | "open"
+ * tabs = [];
+ * }
+ *
+ */
+function loadExtraTabs() {
+ if (!("arguments" in window) || window.arguments.length < 2) {
+ return;
+ }
+
+ let tab = window.arguments[1];
+ if (!tab || typeof tab != "object") {
+ return;
+ }
+
+ if ("wrappedJSObject" in tab) {
+ tab = tab.wrappedJSObject;
+ }
+
+ let tabmail = document.getElementById("tabmail");
+
+ // we got no action, so suppose its "legacy" code
+ if (!("action" in tab)) {
+ if ("tabType" in tab) {
+ tabmail.openTab(tab.tabType, tab.tabParams);
+ }
+ return;
+ }
+
+ if (!("tabs" in tab)) {
+ return;
+ }
+
+ // this is used if a tab is detached to a new window.
+ if (tab.action == "restore") {
+ for (let i = 0; i < tab.tabs.length; i++) {
+ tabmail.restoreTab(tab.tabs[i]);
+ }
+
+ // we currently do not support opening in background or opening a
+ // special position. So select the last tab opened.
+ tabmail.switchToTab(tabmail.tabInfo[tabmail.tabInfo.length - 1]);
+ return;
+ }
+
+ if (tab.action == "open") {
+ for (let i = 0; i < tab.tabs.length; i++) {
+ if ("tabType" in tab.tabs[i]) {
+ tabmail.openTab(tab.tabs[i].tabType, tab.tabs[i].tabParams);
+ }
+ }
+ }
+}
+
+/**
+ * Loads the given message header at window open. Exactly one out of this and
+ * |loadStartFolder| should be called.
+ *
+ * @param aStartMsgHdr The message header to load at window open
+ */
+async function loadStartMsgHdr(aStartMsgHdr) {
+ // We'll just clobber the default tab
+ await atStartupRestoreTabs(true);
+
+ MsgDisplayMessageInFolderTab(aStartMsgHdr);
+}
+
+async function loadStartFolder(initialUri) {
+ var defaultServer = null;
+ var startFolder;
+ var isLoginAtStartUpEnabled = false;
+
+ // If a URI was explicitly specified, we'll just clobber the default tab
+ let loadFolder = !(await atStartupRestoreTabs(!!initialUri));
+
+ if (initialUri) {
+ loadFolder = true;
+ }
+
+ // First get default account
+ try {
+ if (initialUri) {
+ startFolder = MailUtils.getOrCreateFolder(initialUri);
+ } else {
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ if (!defaultAccount) {
+ return;
+ }
+
+ defaultServer = defaultAccount.incomingServer;
+ var rootMsgFolder = defaultServer.rootMsgFolder;
+
+ startFolder = rootMsgFolder;
+
+ // Enable check new mail once by turning checkmail pref 'on' to bring
+ // all users to one plane. This allows all users to go to Inbox. User can
+ // always go to server settings panel and turn off "Check for new mail at startup"
+ if (!Services.prefs.getBoolPref(kMailCheckOncePrefName)) {
+ Services.prefs.setBoolPref(kMailCheckOncePrefName, true);
+ defaultServer.loginAtStartUp = true;
+ }
+
+ // Get the user pref to see if the login at startup is enabled for default account
+ isLoginAtStartUpEnabled = defaultServer.loginAtStartUp;
+
+ // Get Inbox only if login at startup is enabled.
+ if (isLoginAtStartUpEnabled) {
+ // now find Inbox
+ var inboxFolder = rootMsgFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+ if (!inboxFolder) {
+ return;
+ }
+
+ startFolder = inboxFolder;
+ }
+ }
+
+ // it is possible we were given an initial uri and we need to subscribe or try to add
+ // the folder. i.e. the user just clicked on a news folder they aren't subscribed to from a browser
+ // the news url comes in here.
+
+ // Perform biff on the server to check for new mail, except for imap
+ // or a pop3 account that is deferred or deferred to,
+ // or the case where initialUri is non-null (non-startup)
+ if (
+ !initialUri &&
+ isLoginAtStartUpEnabled &&
+ !defaultServer.isDeferredTo &&
+ defaultServer.rootFolder == defaultServer.rootMsgFolder
+ ) {
+ defaultServer.performBiff(msgWindow);
+ }
+ if (loadFolder) {
+ let tab = document.getElementById("tabmail")?.tabInfo[0];
+ tab.chromeBrowser.addEventListener(
+ "load",
+ () => (tab.folder = startFolder),
+ true
+ );
+ }
+ } catch (ex) {
+ console.error(ex);
+ }
+
+ MsgGetMessagesForAllServers(defaultServer);
+
+ if (MailOfflineMgr.isOnline()) {
+ // Check if we shut down offline, and restarted online, in which case
+ // we may have offline events to playback. Since this is not a pref
+ // the user should set, it's not in mailnews.js, so we need a try catch.
+ let playbackOfflineEvents = Services.prefs.getBoolPref(
+ "mailnews.playback_offline",
+ false
+ );
+ if (playbackOfflineEvents) {
+ Services.prefs.setBoolPref("mailnews.playback_offline", false);
+ MailOfflineMgr.offlineManager.goOnline(false, true, msgWindow);
+ }
+
+ // If appropriate, send unsent messages. This may end up prompting the user,
+ // so we need to get it out of the flow of the normal load sequence.
+ setTimeout(function () {
+ if (MailOfflineMgr.shouldSendUnsentMessages()) {
+ SendUnsentMessages();
+ }
+ }, 0);
+ }
+}
+
+function OpenMessageInNewTab(msgHdr, tabParams = {}) {
+ if (!msgHdr) {
+ return;
+ }
+
+ if (tabParams.background === undefined) {
+ tabParams.background = Services.prefs.getBoolPref(
+ "mail.tabs.loadInBackground"
+ );
+ if (tabParams.event?.shiftKey) {
+ tabParams.background = !tabParams.background;
+ }
+ }
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.openTab("mailMessageTab", {
+ ...tabParams,
+ messageURI: msgHdr.folder.getUriForMsg(msgHdr),
+ });
+}
+
+function GetSelectedMsgFolders() {
+ let tabInfo = document.getElementById("tabmail").currentTabInfo;
+ if (tabInfo.mode.name == "mail3PaneTab") {
+ let folder = tabInfo.folder;
+ if (folder) {
+ return [folder];
+ }
+ }
+ return [];
+}
+
+function SelectFolder(folderUri) {
+ // TODO: Replace this.
+}
+
+function ReloadMessage() {}
+
+// Some of the per account junk mail settings have been
+// converted to global prefs. Let's try to migrate some
+// of those settings from the default account.
+function MigrateJunkMailSettings() {
+ var junkMailSettingsVersion = Services.prefs.getIntPref("mail.spam.version");
+ if (!junkMailSettingsVersion) {
+ // Get the default account, check to see if we have values for our
+ // globally migrated prefs.
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ if (defaultAccount) {
+ // we only care about
+ var prefix = "mail.server." + defaultAccount.incomingServer.key + ".";
+ if (Services.prefs.prefHasUserValue(prefix + "manualMark")) {
+ Services.prefs.setBoolPref(
+ "mail.spam.manualMark",
+ Services.prefs.getBoolPref(prefix + "manualMark")
+ );
+ }
+ if (Services.prefs.prefHasUserValue(prefix + "manualMarkMode")) {
+ Services.prefs.setIntPref(
+ "mail.spam.manualMarkMode",
+ Services.prefs.getIntPref(prefix + "manualMarkMode")
+ );
+ }
+ if (Services.prefs.prefHasUserValue(prefix + "spamLoggingEnabled")) {
+ Services.prefs.setBoolPref(
+ "mail.spam.logging.enabled",
+ Services.prefs.getBoolPref(prefix + "spamLoggingEnabled")
+ );
+ }
+ if (Services.prefs.prefHasUserValue(prefix + "markAsReadOnSpam")) {
+ Services.prefs.setBoolPref(
+ "mail.spam.markAsReadOnSpam",
+ Services.prefs.getBoolPref(prefix + "markAsReadOnSpam")
+ );
+ }
+ }
+ // bump the version so we don't bother doing this again.
+ Services.prefs.setIntPref("mail.spam.version", 1);
+ }
+}
+
+// The first time a user runs a build that supports folder views, pre-populate the favorite folders list
+// with the existing INBOX folders.
+function MigrateFolderViews() {
+ var folderViewsVersion = Services.prefs.getIntPref(
+ "mail.folder.views.version"
+ );
+ if (!folderViewsVersion) {
+ for (let server of MailServices.accounts.allServers) {
+ if (server) {
+ let inbox = MailUtils.getInboxFolder(server);
+ if (inbox) {
+ inbox.setFlag(Ci.nsMsgFolderFlags.Favorite);
+ }
+ }
+ }
+ Services.prefs.setIntPref("mail.folder.views.version", 1);
+ }
+}
+
+// Do a one-time migration of the old mailnews.reuse_message_window pref to the
+// newer mail.openMessageBehavior. This does the migration only if the old pref
+// is defined.
+function MigrateOpenMessageBehavior() {
+ let openMessageBehaviorVersion = Services.prefs.getIntPref(
+ "mail.openMessageBehavior.version"
+ );
+ if (!openMessageBehaviorVersion) {
+ // Don't touch this if it isn't defined
+ if (
+ Services.prefs.getPrefType("mailnews.reuse_message_window") ==
+ Ci.nsIPrefBranch.PREF_BOOL
+ ) {
+ if (Services.prefs.getBoolPref("mailnews.reuse_message_window")) {
+ Services.prefs.setIntPref(
+ "mail.openMessageBehavior",
+ MailConsts.OpenMessageBehavior.EXISTING_WINDOW
+ );
+ } else {
+ Services.prefs.setIntPref(
+ "mail.openMessageBehavior",
+ MailConsts.OpenMessageBehavior.NEW_TAB
+ );
+ }
+ }
+
+ Services.prefs.setIntPref("mail.openMessageBehavior.version", 1);
+ }
+}
+
+function messageFlavorDataProvider() {}
+
+messageFlavorDataProvider.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFlavorDataProvider"]),
+
+ getFlavorData(aTransferable, aFlavor, aData) {
+ if (aFlavor !== "application/x-moz-file-promise") {
+ return;
+ }
+ let fileUriPrimitive = {};
+ aTransferable.getTransferData(
+ "application/x-moz-file-promise-url",
+ fileUriPrimitive
+ );
+
+ let fileUriStr = fileUriPrimitive.value.QueryInterface(
+ Ci.nsISupportsString
+ );
+ let fileUri = Services.io.newURI(fileUriStr.data);
+ let fileUrl = fileUri.QueryInterface(Ci.nsIURL);
+ let fileName = fileUrl.fileName.replace(/(.{74}).*(.{10})$/u, "$1...$2");
+
+ let destDirPrimitive = {};
+ aTransferable.getTransferData(
+ "application/x-moz-file-promise-dir",
+ destDirPrimitive
+ );
+ let destDirectory = destDirPrimitive.value.QueryInterface(Ci.nsIFile);
+ let file = destDirectory.clone();
+ file.append(fileName);
+
+ let messageUriPrimitive = {};
+ aTransferable.getTransferData("text/x-moz-message", messageUriPrimitive);
+ let messageUri = messageUriPrimitive.value.QueryInterface(
+ Ci.nsISupportsString
+ );
+
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ messenger.saveAs(
+ messageUri.data,
+ true,
+ null,
+ decodeURIComponent(file.path),
+ true
+ );
+ },
+};
+
+var TabsInTitlebar = {
+ init() {
+ this._readPref();
+ Services.prefs.addObserver(this._drawInTitlePref, this);
+
+ window.addEventListener("resolutionchange", this);
+ window.addEventListener("resize", this);
+
+ this._initialized = true;
+ this.update();
+ },
+
+ allowedBy(condition, allow) {
+ if (allow) {
+ if (condition in this._disallowed) {
+ delete this._disallowed[condition];
+ this.update();
+ }
+ } else if (!(condition in this._disallowed)) {
+ this._disallowed[condition] = null;
+ this.update();
+ }
+ },
+
+ get systemSupported() {
+ let isSupported = false;
+ switch (AppConstants.MOZ_WIDGET_TOOLKIT) {
+ case "windows":
+ case "cocoa":
+ isSupported = true;
+ break;
+ case "gtk":
+ isSupported = window.matchMedia("(-moz-gtk-csd-available)");
+ break;
+ }
+ delete this.systemSupported;
+ return (this.systemSupported = isSupported);
+ },
+
+ get enabled() {
+ return document.documentElement.getAttribute("tabsintitlebar") == "true";
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed") {
+ this._readPref();
+ }
+ },
+
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "resolutionchange":
+ if (aEvent.target == window) {
+ this.update();
+ }
+ break;
+ case "resize":
+ // The spaces toolbar needs special styling for the fullscreen mode.
+ gSpacesToolbar.onWindowResize();
+ if (window.fullScreen || aEvent.target != window) {
+ break;
+ }
+ // We use resize events because the window is not ready after
+ // sizemodechange events. However, we only care about the event when
+ // the sizemode is different from the last time we updated the
+ // appearance of the tabs in the titlebar.
+ let sizemode = document.documentElement.getAttribute("sizemode");
+ if (this._lastSizeMode == sizemode) {
+ break;
+ }
+ let oldSizeMode = this._lastSizeMode;
+ this._lastSizeMode = sizemode;
+ // Don't update right now if we are leaving fullscreen, since the UI is
+ // still changing in the consequent "fullscreen" event. Code there will
+ // call this function again when everything is ready.
+ // See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
+ if (oldSizeMode == "fullscreen") {
+ break;
+ }
+ this.update();
+ break;
+ }
+ },
+
+ _initialized: false,
+ _disallowed: {},
+ _drawInTitlePref: "mail.tabs.drawInTitlebar",
+ _lastSizeMode: null,
+
+ _readPref() {
+ // check is only true when drawInTitlebar=true
+ let check = Services.prefs.getBoolPref(this._drawInTitlePref);
+ this.allowedBy("pref", check);
+ },
+
+ update() {
+ if (!this._initialized || window.fullScreen) {
+ return;
+ }
+
+ let allowed =
+ this.systemSupported && Object.keys(this._disallowed).length == 0;
+
+ if (
+ document.documentElement.getAttribute("chromehidden")?.includes("toolbar")
+ ) {
+ // Don't draw in titlebar in case of a popup window.
+ allowed = false;
+ }
+
+ if (allowed) {
+ document.documentElement.setAttribute("tabsintitlebar", "true");
+ if (AppConstants.platform == "macosx") {
+ document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
+ document.documentElement.removeAttribute("drawtitle");
+ } else {
+ document.documentElement.setAttribute("chromemargin", "0,2,2,2");
+ }
+ } else {
+ document.documentElement.removeAttribute("tabsintitlebar");
+ document.documentElement.removeAttribute("chromemargin");
+ if (AppConstants.platform == "macosx") {
+ document.documentElement.setAttribute("drawtitle", "true");
+ }
+ }
+ },
+
+ uninit() {
+ this._initialized = false;
+ Services.prefs.removeObserver(this._drawInTitlePref, this);
+ },
+};
+
+var BrowserAddonUI = {
+ async promptRemoveExtension(addon) {
+ let { name } = addon;
+ let [title, btnTitle] = await document.l10n.formatValues([
+ {
+ id: "addon-removal-title",
+ args: { name },
+ },
+ {
+ id: "addon-removal-confirmation-button",
+ },
+ ]);
+ let {
+ BUTTON_TITLE_IS_STRING: titleString,
+ BUTTON_TITLE_CANCEL: titleCancel,
+ BUTTON_POS_0,
+ BUTTON_POS_1,
+ confirmEx,
+ } = Services.prompt;
+ let btnFlags = BUTTON_POS_0 * titleString + BUTTON_POS_1 * titleCancel;
+ let message = null;
+
+ if (!Services.prefs.getBoolPref("prompts.windowPromptSubDialog", false)) {
+ message = await document.l10n.formatValue(
+ "addon-removal-confirmation-message",
+ {
+ name,
+ }
+ );
+ }
+
+ let checkboxState = { value: false };
+ let result = confirmEx(
+ window,
+ title,
+ message,
+ btnFlags,
+ btnTitle,
+ /* button1 */ null,
+ /* button2 */ null,
+ /* checkboxMessage */ null,
+ checkboxState
+ );
+
+ return { remove: result === 0, report: false };
+ },
+
+ async removeAddon(addonId) {
+ let addon = addonId && (await AddonManager.getAddonByID(addonId));
+ if (!addon || !(addon.permissions & AddonManager.PERM_CAN_UNINSTALL)) {
+ return;
+ }
+
+ let { remove, report } = await this.promptRemoveExtension(addon);
+
+ if (remove) {
+ await addon.uninstall(report);
+ }
+ },
+};