/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //This file stores variables common to mail windows var messenger; var statusFeedback; var msgWindow; var msgComposeService; var accountManager; var RDF; var msgComposeType; var msgComposeFormat; var gMessengerBundle; var gBrandBundle; var accountCentralBox = null; var gDisableViewsSearch = null; var gAccountCentralLoaded = true; //End progress and Status variables var gOfflineManager; function OnMailWindowUnload() { RemoveMailOfflineObserver(); ClearPendingReadTimer(); var searchSession = GetSearchSession(); if (searchSession) { removeGlobalListeners(); if (gPreQuickSearchView) //close the cached pre quick search view gPreQuickSearchView.close(); } var dbview = GetDBView(); if (dbview) { dbview.close(); } MailServices.mailSession.RemoveFolderListener(folderListener); MailServices.mailSession.RemoveMsgWindow(msgWindow); messenger.setWindow(null, null); msgWindow.closeWindow(); msgWindow.msgHeaderSink = null; msgWindow.notificationCallbacks = null; gDBView = null; } /** * When copying/dragging, convert imap/mailbox URLs of images into data URLs so * that the images can be accessed in a paste elsewhere. */ function onCopyOrDragStart(e) { let browser = getBrowser(); if (!browser) { return; } let sourceDoc = browser.contentDocument; if (e.target.ownerDocument != sourceDoc) { // We're only interested if this is in the message content. return; } let imgMap = new Map(); // Mapping img.src -> dataURL. // For copy, the data of what is to be copied is not accessible at this point. // Figure out what images are a) part of the selection and b) visible in // the current document. If their source isn't http or data already, convert // them to data URLs. let selection = sourceDoc.getSelection(); let draggedImg = selection.isCollapsed ? e.target : null; for (let img of sourceDoc.images) { if (/^(https?|data):/.test(img.src)) { continue; } if (img.naturalWidth == 0) { // Broken/inaccessible image then... continue; } if (!draggedImg && !selection.containsNode(img, true)) { continue; } let style = window.getComputedStyle(img); if (style.display == "none" || style.visibility == "hidden") { continue; } // Do not convert if the image is specifically flagged to not snarf. if (img.getAttribute("moz-do-not-send") == "true") { continue; } // We don't need to wait for the image to load. If it isn't already loaded // in the source document, we wouldn't want it anyway. let canvas = sourceDoc.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height); let type = /\.jpe?g$/i.test(img.src) ? "image/jpg" : "image/png"; imgMap.set(img.src, canvas.toDataURL(type)); } if (imgMap.size == 0) { // Nothing that needs converting! return; } let clonedSelection = draggedImg ? draggedImg.cloneNode(false) : selection.getRangeAt(0).cloneContents(); let div = sourceDoc.createElement("div"); div.appendChild(clonedSelection); let images = div.querySelectorAll("img"); for (let img of images) { if (!imgMap.has(img.src)) { continue; } img.src = imgMap.get(img.src); } let html = div.innerHTML; let parserUtils = Cc["@mozilla.org/parserutils;1"] .getService(Ci.nsIParserUtils); let plain = parserUtils.convertToPlainText(html, Ci.nsIDocumentEncoder.OutputForPlainTextClipboardCopy, 0); // Copy operation. if ("clipboardData" in e) { e.clipboardData.setData("text/html", html); e.clipboardData.setData("text/plain", plain); e.preventDefault(); } // Drag operation. else if ("dataTransfer" in e) { e.dataTransfer.setData("text/html", html); e.dataTransfer.setData("text/plain", plain); } } function CreateMailWindowGlobals() { // Get the messenger instance. messenger = Cc["@mozilla.org/messenger;1"] .createInstance(Ci.nsIMessenger); // Create windows status feedback // set the JS implementation of status feedback before creating the c++ one.. window.MsgStatusFeedback = new nsMsgStatusFeedback(); // Double register the status feedback object as the xul browser window // implementation. window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow) .XULBrowserWindow = window.MsgStatusFeedback; statusFeedback = Cc["@mozilla.org/messenger/statusfeedback;1"] .createInstance(Ci.nsIMsgStatusFeedback); statusFeedback.setWrappedStatusFeedback(window.MsgStatusFeedback); window.MsgWindowCommands = new nsMsgWindowCommands(); //Create message window object msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"] .createInstance(Ci.nsIMsgWindow); msgComposeService = Cc['@mozilla.org/messengercompose;1'] .getService(Ci.nsIMsgComposeService); accountManager = MailServices.accounts; RDF = Cc['@mozilla.org/rdf/rdf-service;1'] .getService(Ci.nsIRDFService); msgComposeType = Ci.nsIMsgCompType; msgComposeFormat = Ci.nsIMsgCompFormat; gMessengerBundle = document.getElementById("bundle_messenger"); gBrandBundle = document.getElementById("bundle_brand"); msgWindow.notificationCallbacks = new nsMsgBadCertHandler(); } function InitMsgWindow() { msgWindow.windowCommands = new nsMsgWindowCommands(); // set the domWindow before setting the status feedback and header sink objects msgWindow.domWindow = window; msgWindow.statusFeedback = statusFeedback; msgWindow.msgHeaderSink = messageHeaderSink; MailServices.mailSession.AddMsgWindow(msgWindow); var messagepane = getMessageBrowser(); messagepane.docShell.allowAuth = false; messagepane.docShell.allowDNSPrefetch = false; msgWindow.rootDocShell.allowAuth = true; msgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL; // Ensure we don't load xul error pages into the main window msgWindow.rootDocShell.useErrorPages = false; document.addEventListener("copy", onCopyOrDragStart, true); document.addEventListener("dragstart", onCopyOrDragStart, true); } function messagePaneOnResize(event) { // scale any overflowing images var messagepane = getMessageBrowser(); var doc = messagepane.contentDocument; var imgs = doc.images; for (var img of imgs) { if (img.className == "moz-attached-image") { if (img.naturalWidth <= doc.body.clientWidth) { img.removeAttribute("isshrunk"); img.removeAttribute("overflowing"); } else if (img.hasAttribute("shrinktofit")) { img.setAttribute("isshrunk", "true"); img.removeAttribute("overflowing"); } else { img.setAttribute("overflowing", "true"); img.removeAttribute("isshrunk"); } } } } function messagePaneOnClick(event) { // if this is stand alone mail (no browser) // or this isn't a simple left click, do nothing, and let the normal code execute if (event.button != 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return contentAreaClick(event); // try to determine the href for what you are clicking on. // for example, it might be "" if you aren't left clicking on a link var ceParams = hrefAndLinkNodeForClickEvent(event); if (!ceParams && !event.button) { var target = event.target; // is this an image that we might want to scale? if (target instanceof Ci.nsIImageLoadingContent) { // make sure it loaded successfully var req = target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); if (!req || req.imageStatus & Ci.imgIRequest.STATUS_ERROR) return true; // is it an inline attachment? if (/^moz-attached-image/.test(target.className)) { if (target.hasAttribute("isshrunk")) { // currently shrunk to fit, so unshrink it target.removeAttribute("isshrunk"); target.removeAttribute("shrinktofit"); target.setAttribute("overflowing", "true"); } else if (target.hasAttribute("overflowing")) { // user wants to shrink now target.setAttribute("isshrunk", "true"); target.setAttribute("shrinktofit", "true"); target.removeAttribute("overflowing"); } } } return true; } var href = ceParams.href; // we know that http://, https://, ftp://, file://, chrome://, // resource://, and about, should load in a browser. but if // we don't have one of those (examples are mailto, imap, news, mailbox, snews, // nntp, ldap, and externally handled schemes like aim) we may or may not // want a browser window, in which case we return here and let the normal code // handle it var needABrowser = /(^http(s)?:|^ftp:|^file:|^chrome:|^resource:|^about:)/i; if (href.search(needABrowser) == -1) return true; // however, if the protocol should not be loaded internally, then we should // not put up a new browser window. we should just let the usual processing // take place. try { var extProtService = Cc["@mozilla.org/uriloader/external-protocol-service;1"] .getService(Ci.nsIExternalProtocolService); var scheme = href.substring(0, href.indexOf(":")); if (!extProtService.isExposedProtocol(scheme)) return true; } catch (ex) {} // ignore errors, and just assume that we can proceed. // if you get here, the user did a simple left click on a link // that we know should be in a browser window. // since we are in the message pane, send it to the top most browser window // (or open one) right away, instead of waiting for us to get some data and // determine the content type, and then open a browser window // we want to preventDefault, so that in // nsGenericHTMLElement::HandleDOMEventForAnchors(), we don't try to handle the click again event.preventDefault(); if (isPhishingURL(ceParams.linkNode, false, href)) return false; openAsExternal(href); return true; } // We're going to implement our status feedback for the mail window in JS now. // the following contains the implementation of our status feedback object function nsMsgStatusFeedback() { } nsMsgStatusFeedback.prototype = { // global variables for status / feedback information.... statusTextFld : null, statusBar : null, statusPanel : null, throbber : null, stopCmd : null, startTimeoutID : null, stopTimeoutID : null, pendingStartRequests : 0, meteorsSpinning : false, myDefaultStatus : "", ensureStatusFields : function() { if (!this.statusTextFld ) this.statusTextFld = document.getElementById("statusText"); if (!this.statusBar) this.statusBar = document.getElementById("statusbar-icon"); if (!this.statusPanel) this.statusPanel = document.getElementById("statusbar-progresspanel"); if (!this.throbber) this.throbber = document.getElementById("navigator-throbber"); if (!this.stopCmd) this.stopCmd = document.getElementById("cmd_stop"); }, // nsIXULBrowserWindow implementation setJSStatus : function(status) { if (status.length > 0) this.showStatusString(status); }, setOverLink : function(link, context) { this.ensureStatusFields(); this.statusTextFld.label = link; }, // Called before links are navigated to to allow us to retarget them if needed. onBeforeLinkTraversal: function(aOriginalTarget, aLinkURI, aLinkNode, aIsAppTab) { return aOriginalTarget; }, QueryInterface : function(iid) { if (iid.equals(Ci.nsIMsgStatusFeedback) || iid.equals(Ci.nsIXULBrowserWindow) || iid.equals(Ci.nsISupportsWeakReference) || iid.equals(Ci.nsISupports)) return this; throw Cr.NS_NOINTERFACE; }, // nsIMsgStatusFeedback implementation. showStatusString : function(statusText) { this.ensureStatusFields(); if ( !statusText.length ) statusText = this.myDefaultStatus; else this.myDefaultStatus = ""; this.statusTextFld.label = statusText; }, setStatusString : function(status) { if (status.length > 0) { this.myDefaultStatus = status; this.statusTextFld.label = status; } }, _startMeteors : function() { this.ensureStatusFields(); this.meteorsSpinning = true; this.startTimeoutID = null; // Show progress meter this.statusPanel.collapsed = false; // Turn progress meter on. this.statusBar.setAttribute("mode","undetermined"); // start the throbber if (this.throbber) this.throbber.setAttribute("busy", true); //turn on stop button and menu if (this.stopCmd) this.stopCmd.removeAttribute("disabled"); }, startMeteors : function() { this.pendingStartRequests++; // if we don't already have a start meteor timeout pending // and the meteors aren't spinning, then kick off a start if (!this.startTimeoutID && !this.meteorsSpinning && window.MsgStatusFeedback) this.startTimeoutID = setTimeout('window.MsgStatusFeedback._startMeteors();', 500); // since we are going to start up the throbber no sense in processing // a stop timeout... if (this.stopTimeoutID) { clearTimeout(this.stopTimeoutID); this.stopTimeoutID = null; } }, _stopMeteors : function() { this.ensureStatusFields(); this.showStatusString(this.myDefaultStatus); // stop the throbber if (this.throbber) this.throbber.setAttribute("busy", false); // Turn progress meter off. this.statusPanel.collapsed = true; this.statusBar.setAttribute("mode","normal"); this.statusBar.value = 0; // be sure to clear the progress bar this.statusBar.label = ""; if (this.stopCmd) this.stopCmd.setAttribute("disabled", "true"); this.meteorsSpinning = false; this.stopTimeoutID = null; }, stopMeteors : function() { if (this.pendingStartRequests > 0) this.pendingStartRequests--; // if we are going to be starting the meteors, cancel the start if (this.pendingStartRequests == 0 && this.startTimeoutID) { clearTimeout(this.startTimeoutID); this.startTimeoutID = null; } // if we have no more pending starts and we don't have a stop timeout already in progress // AND the meteors are currently running then fire a stop timeout to shut them down. if (this.pendingStartRequests == 0 && !this.stopTimeoutID) { if (this.meteorsSpinning && window.MsgStatusFeedback) this.stopTimeoutID = setTimeout('window.MsgStatusFeedback._stopMeteors();', 500); } }, showProgress : function(percentage) { this.ensureStatusFields(); if (percentage >= 0) { this.statusBar.setAttribute("mode", "normal"); this.statusBar.value = percentage; this.statusBar.label = Math.round(percentage) + "%"; } } } function nsMsgWindowCommands() { } nsMsgWindowCommands.prototype = { QueryInterface : function(iid) { if (iid.equals(Ci.nsIMsgWindowCommands) || iid.equals(Ci.nsISupports)) return this; throw Cr.NS_NOINTERFACE; }, selectFolder: function(folderUri) { gFolderTreeView.selectFolder(MailUtils.getFolderForURI(folderUri)); }, selectMessage: function(messageUri) { SelectMessage(messageUri); }, clearMsgPane: function() { if (gDBView) setTitleFromFolder(gDBView.msgFolder,null); else setTitleFromFolder(null,null); ClearMessagePane(); } } function StopUrls() { msgWindow.StopUrls(); } function loadStartPage() { try { gMessageNotificationBar.clearMsgNotifications(); var startpageenabled = Services.prefs.getBoolPref("mailnews.start_page.enabled"); if (startpageenabled) { var startpage = GetLocalizedStringPref("mailnews.start_page.url"); if (startpage) { GetMessagePaneFrame().location.href = startpage; //dump("start message pane with: " + startpage + "\n"); ClearMessageSelection(); } } } catch (ex) { dump("Error loading start page.\n"); return; } } // Given the server, open the twisty and the set the selection // on inbox of that server. // prompt if offline. function OpenInboxForServer(server) { ShowThreadPane(); gFolderTreeView.selectFolder(GetInboxFolder(server)); if (!Services.io.offline) { if (server.type != "imap") GetMessagesForInboxOnServer(server); } else if (DoGetNewMailWhenOffline()) { GetMessagesForInboxOnServer(server); } } function GetSearchSession() { if (("gSearchSession" in top) && gSearchSession) return gSearchSession; else return null; } function MailSetCharacterSet(aEvent) { if (aEvent.target.hasAttribute("charset")) { msgWindow.mailCharacterSet = aEvent.target.getAttribute("charset"); msgWindow.charsetOverride = true; } messenger.setDocumentCharset(msgWindow.mailCharacterSet); }