mirror of
https://github.com/ManchildProductions/binoc-central-mirror.git
synced 2026-06-11 12:18:33 +00:00
390 lines
16 KiB
JavaScript
390 lines
16 KiB
JavaScript
/* 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/. */
|
|
|
|
var EXPORTED_SYMBOLS = ["MailUtils"];
|
|
|
|
var Cc = Components.classes;
|
|
var Ci = Components.interfaces;
|
|
|
|
Components.utils.import("resource:///modules/iteratorUtils.jsm");
|
|
Components.utils.import("resource:///modules/MailConsts.js");
|
|
Components.utils.import("resource:///modules/mailServices.js");
|
|
Components.utils.import("resource://gre/modules/Services.jsm");
|
|
Components.utils.import("resource://gre/modules/PluralForm.jsm");
|
|
|
|
var MC = MailConsts;
|
|
|
|
/**
|
|
* This module has several utility functions for use by both core and
|
|
* third-party code. Some functions are aimed at code that doesn't have a
|
|
* window context, while others can be used anywhere.
|
|
*/
|
|
var MailUtils =
|
|
{
|
|
/**
|
|
* Discover all folders. This is useful during startup, when you have code
|
|
* that deals with folders and that executes before the main 3pane window is
|
|
* open (the folder tree wouldn't have been initialized yet).
|
|
*/
|
|
discoverFolders: function MailUtils_discoverFolders() {
|
|
let servers = MailServices.accounts.allServers;
|
|
for (let server in fixIterator(servers, Ci.nsIMsgIncomingServer)) {
|
|
// Bug 466311 Sometimes this can throw file not found, we're unsure
|
|
// why, but catch it and log the fact.
|
|
try {
|
|
server.rootFolder.subFolders;
|
|
}
|
|
catch (ex) {
|
|
Services.console.logStringMessage("Discovering folders for account failed with " +
|
|
"exception: " + ex);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the nsIMsgFolder corresponding to this file. This just looks at all
|
|
* folders and does a direct match.
|
|
*
|
|
* One of the places this is used is desktop search integration -- to open
|
|
* the search result corresponding to a mozeml/wdseml file, we need to figure
|
|
* out the folder using the file's path.
|
|
*
|
|
* @param aFile the nsILocalFile to convert to a folder
|
|
* @returns the nsIMsgFolder corresponding to aFile, or null if the folder
|
|
* isn't found
|
|
*/
|
|
getFolderForFileInProfile:
|
|
function MailUtils_getFolderForFileInProfile(aFile) {
|
|
let folders = MailServices.accounts.allFolders;
|
|
|
|
for (let folder in fixIterator(folders, Ci.nsIMsgFolder)) {
|
|
if (folder.filePath.equals(aFile))
|
|
return folder;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Get the nsIMsgFolder corresponding to this URI. This uses the RDF service
|
|
* to do the work.
|
|
*
|
|
* @param aFolderURI the URI to convert into a folder
|
|
* @param aCheckFolderAttributes whether to check that the folder either has
|
|
* a parent or isn't a server
|
|
* @returns the nsIMsgFolder corresponding to this URI, or null if
|
|
* aCheckFolderAttributes is true and the folder doesn't have a
|
|
* parent or is a server
|
|
*/
|
|
getFolderForURI: function MailUtils_getFolderForURI(aFolderURI,
|
|
aCheckFolderAttributes) {
|
|
let folder = null;
|
|
let rdfService = Cc['@mozilla.org/rdf/rdf-service;1']
|
|
.getService(Ci.nsIRDFService);
|
|
folder = rdfService.GetResource(aFolderURI);
|
|
// This is going to QI the folder to an nsIMsgFolder as well
|
|
if (folder && folder instanceof Ci.nsIMsgFolder) {
|
|
if (aCheckFolderAttributes && !(folder.parent || folder.isServer))
|
|
return null;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
|
|
return folder;
|
|
},
|
|
|
|
/**
|
|
* Display this message header in a new tab, a new window or an existing
|
|
* window, depending on the preference and whether a 3pane or standalone
|
|
* window is already open. This function should be called when you'd like to
|
|
* display a message to the user according to the pref set.
|
|
*
|
|
* @note Do not use this if you want to open multiple messages at once. Use
|
|
* |displayMessages| instead.
|
|
*
|
|
* @param aMsgHdr the message header to display
|
|
* @param [aViewWrapperToClone] a view wrapper to clone. If null or not
|
|
* given, the message header's folder's default
|
|
* view will be used
|
|
* @param [aTabmail] a tabmail element to use in case we need to open tabs.
|
|
* If null or not given:
|
|
* - if one or more 3pane windows are open, the most recent
|
|
* one's tabmail is used
|
|
* - if no 3pane windows are open, a standalone window is
|
|
* opened instead of a tab
|
|
*/
|
|
displayMessage: function MailUtils_displayMessage(aMsgHdr,
|
|
aViewWrapperToClone, aTabmail) {
|
|
this.displayMessages([aMsgHdr], aViewWrapperToClone, aTabmail);
|
|
},
|
|
|
|
/**
|
|
* Display the warning if the number of messages to be displayed is greater than
|
|
* the limit set in preferences.
|
|
* @param aNumMessages: number of messages to be displayed
|
|
* @param aConfirmTitle: title ID
|
|
* @param aConfirmMsg: message ID
|
|
* @param aLiitingPref: the name of the pref to retrieve the limit from
|
|
*/
|
|
confirmAction: function (aNumMessages, aConfirmTitle, aConfirmMsg, aLimitingPref) {
|
|
let openWarning = Services.prefs.getIntPref(aLimitingPref);
|
|
if ((openWarning > 1) && (aNumMessages >= openWarning)) {
|
|
let bundle = Services.strings.createBundle(
|
|
"chrome://messenger/locale/messenger.properties");
|
|
let title = bundle.GetStringFromName(aConfirmTitle);
|
|
let message = PluralForm.get(aNumMessages,
|
|
bundle.GetStringFromName(aConfirmMsg))
|
|
.replace("#1", aNumMessages);
|
|
if (!Services.prompt.confirm(null, title, message))
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* Display these message headers in new tabs, new windows or existing
|
|
* windows, depending on the preference, the number of messages, and whether
|
|
* a 3pane or standalone window is already open. This function should be
|
|
* called when you'd like to display multiple messages to the user according
|
|
* to the pref set.
|
|
*
|
|
* @param aMsgHdrs an array containing the message headers to display. The
|
|
* array should contain at least one message header
|
|
* @param [aViewWrapperToClone] a DB view wrapper to clone for each of the
|
|
* tabs or windows
|
|
* @param [aTabmail] a tabmail element to use in case we need to open tabs.
|
|
* If given, the window containing the tabmail is assumed
|
|
* to be in front. If null or not given:
|
|
* - if one or more 3pane windows are open, the most recent
|
|
* one's tabmail is used, and the window is brought to the
|
|
* front
|
|
* - if no 3pane windows are open, standalone windows are
|
|
* opened instead of tabs
|
|
*/
|
|
displayMessages: function MailUtils_displayMessages(aMsgHdrs,
|
|
aViewWrapperToClone, aTabmail) {
|
|
let openMessageBehavior = Services.prefs.getIntPref(
|
|
"mail.openMessageBehavior");
|
|
|
|
if (openMessageBehavior == MC.OpenMessageBehavior.NEW_WINDOW) {
|
|
this.openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone);
|
|
}
|
|
else if (openMessageBehavior == MC.OpenMessageBehavior.EXISTING_WINDOW) {
|
|
// Try reusing an existing window. If we can't, fall back to opening new
|
|
// windows
|
|
if (aMsgHdrs.length > 1 || !this.openMessageInExistingWindow(aMsgHdrs[0]))
|
|
this.openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone);
|
|
}
|
|
else if (openMessageBehavior == MC.OpenMessageBehavior.NEW_TAB) {
|
|
let mail3PaneWindow = null;
|
|
if (!aTabmail) {
|
|
// Try opening new tabs in a 3pane window
|
|
mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
|
|
if (mail3PaneWindow)
|
|
aTabmail = mail3PaneWindow.document.getElementById("tabmail");
|
|
}
|
|
|
|
if (aTabmail) {
|
|
if (this.confirmAction(aMsgHdrs.length, "openTabWarningTitle",
|
|
"openTabWarningConfirmation",
|
|
"mailnews.open_tab_warning"))
|
|
return;
|
|
for (let [i, msgHdr] of aMsgHdrs.entries())
|
|
// Open all the tabs in the background, except for the last one
|
|
aTabmail.openTab("message", {msgHdr: msgHdr,
|
|
viewWrapperToClone: aViewWrapperToClone,
|
|
background: (i < (aMsgHdrs.length - 1)),
|
|
disregardOpener: (aMsgHdrs.length > 1),
|
|
});
|
|
|
|
if (mail3PaneWindow)
|
|
mail3PaneWindow.focus();
|
|
}
|
|
else {
|
|
// We still haven't found a tabmail, so we'll need to open new windows
|
|
this.openMessagesInNewWindows(aMsgHdrs, aViewWrapperToClone);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Show this message in an existing window.
|
|
*
|
|
* @param aMsgHdr the message header to display
|
|
* @param [aViewWrapperToClone] a DB view wrapper to clone for the message
|
|
* window
|
|
* @returns true if an existing window was found and the message header was
|
|
* displayed, false otherwise
|
|
*/
|
|
openMessageInExistingWindow:
|
|
function MailUtils_openMessageInExistingWindow(aMsgHdr,
|
|
aViewWrapperToClone) {
|
|
let messageWindow = Services.wm.getMostRecentWindow("mail:messageWindow");
|
|
if (messageWindow) {
|
|
messageWindow.displayMessage(aMsgHdr, aViewWrapperToClone);
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Open a new standalone message window with this header.
|
|
*
|
|
* @param aMsgHdr the message header to display
|
|
* @param [aViewWrapperToClone] a DB view wrapper to clone for the message
|
|
* window
|
|
*/
|
|
openMessageInNewWindow:
|
|
function MailUtils_openMessageInNewWindow(aMsgHdr, aViewWrapperToClone) {
|
|
// It sucks that we have to go through XPCOM for this
|
|
let args = {msgHdr: aMsgHdr, viewWrapperToClone: aViewWrapperToClone};
|
|
args.wrappedJSObject = args;
|
|
|
|
Services.ww.openWindow(null,
|
|
"chrome://messenger/content/messageWindow.xul", "",
|
|
"all,chrome,dialog=no,status,toolbar", args);
|
|
},
|
|
|
|
/**
|
|
* Open new standalone message windows for these headers. This will prompt
|
|
* for confirmation if the number of windows to be opened is greater than the
|
|
* value of the mailnews.open_window_warning preference.
|
|
*
|
|
* @param aMsgHdrs an array containing the message headers to display
|
|
* @param [aViewWrapperToClone] a DB view wrapper to clone for each message
|
|
* window
|
|
*/
|
|
openMessagesInNewWindows:
|
|
function MailUtils_openMessagesInNewWindows(aMsgHdrs,
|
|
aViewWrapperToClone) {
|
|
if (this.confirmAction(aMsgHdrs.length, "openWindowWarningTitle",
|
|
"openWindowWarningConfirmation",
|
|
"mailnews.open_window_warning"))
|
|
return;
|
|
|
|
for (let msgHdr of aMsgHdrs)
|
|
this.openMessageInNewWindow(msgHdr, aViewWrapperToClone);
|
|
},
|
|
|
|
/**
|
|
* Display this message header in a folder tab in a 3pane window. This is
|
|
* useful when the message needs to be displayed in the context of its folder
|
|
* or thread.
|
|
*
|
|
* @param aMsgHdr the message header to display
|
|
*/
|
|
displayMessageInFolderTab: function MailUtils_displayMessageInFolderTab(
|
|
aMsgHdr) {
|
|
// Try opening new tabs in a 3pane window
|
|
let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
|
|
if (mail3PaneWindow) {
|
|
mail3PaneWindow.MsgDisplayMessageInFolderTab(aMsgHdr);
|
|
}
|
|
else {
|
|
let args = {msgHdr: aMsgHdr};
|
|
args.wrappedJSObject = args;
|
|
Services.ww.openWindow(null,
|
|
"chrome://messenger/content/", "",
|
|
"all,chrome,dialog=no,status,toolbar", args);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* The number of milliseconds to wait between loading of folders in
|
|
* |setStringPropertyOnFolderAndDescendents|. We wait at all because
|
|
* opening msf databases is a potentially expensive synchronous operation that
|
|
* can approach the order of a second in pathological cases like gmail's
|
|
* all mail folder.
|
|
*
|
|
* If we did not use a timer or otherwise spin the event loop we would
|
|
* completely lock up the UI. In theory we would still maintain some degree
|
|
* of UI responsiveness if we just used postMessage to break up our work so
|
|
* that the event loop still got a chance to run between our folder openings.
|
|
* The use of any delay between processing folders is to try and avoid causing
|
|
* system-wide interactivity problems from dominating the system's available
|
|
* disk seeks to such an extent that other applications start experiencing
|
|
* non-trivial I/O waits.
|
|
*
|
|
* The specific choice of delay remains an arbitrary one to maintain app
|
|
* and system responsiveness per the above while also processing as many
|
|
* folders as quickly as possible.
|
|
*
|
|
* This is exposed primarily to allow unit tests to set this to 0 to minimize
|
|
* throttling.
|
|
*/
|
|
INTER_FOLDER_PROCESSING_DELAY_MS: 10,
|
|
|
|
/**
|
|
* Set a string property on a folder and all of its descendents, taking care
|
|
* to avoid locking up the main thread and to avoid leaving folder databases
|
|
* open. To avoid locking up the main thread we operate in an asynchronous
|
|
* fashion; we invoke a callback when we have completed our work.
|
|
*
|
|
* Using this function will write the value into the folder cache
|
|
* (panacea.dat) as well as the folder itself. Hopefully you want this; if
|
|
* you do not, keep in mind that the only way to avoid that is to retrieve
|
|
* the nsIMsgDatabase and then the nsIDbFolderInfo. You would want to avoid
|
|
* that as much as possible because once those are exposed to you, XPConnect
|
|
* is going to hold onto them creating a situation where you are going to be
|
|
* in severe danger of extreme memory bloat unless you force garbage
|
|
* collections after every time you close a database.
|
|
*
|
|
* @param aPropertyName The name of the property to set.
|
|
* @param aPropertyValue The (string) value of the property to set.
|
|
* Alternately, you can pass a function that takes the nsIMsgFolder and
|
|
* returns a string value.
|
|
* @param aFolder The parent folder; we set the string property on it and all
|
|
* of its descendents.
|
|
* @param [aCallback] The optional callback to invoke once we finish our work.
|
|
* The callback is provided a boolean success value; true means we
|
|
* managed to set the values on all folders, false means we encountered a
|
|
* problem.
|
|
*/
|
|
setStringPropertyOnFolderAndDescendents:
|
|
function MailUtils_setStringPropertyOnFolderAndDescendents(aPropertyName,
|
|
aPropertyValue,
|
|
aFolder,
|
|
aCallback) {
|
|
// We need to add the base folder as it does not get added by ListDescendants.
|
|
let allFolders = toXPCOMArray([aFolder], Ci.nsIMutableArray);
|
|
// - get all the descendants
|
|
aFolder.ListDescendants(allFolders);
|
|
|
|
// - worker function
|
|
function* folder_string_setter_worker() {
|
|
for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) {
|
|
// set the property; this may open the database...
|
|
let value = (typeof aPropertyValue == "function" ?
|
|
aPropertyValue(folder) : aPropertyValue);
|
|
folder.setStringProperty(aPropertyName, value);
|
|
// force the reference to be forgotten.
|
|
folder.msgDatabase = null;
|
|
yield undefined;
|
|
}
|
|
}
|
|
let worker = folder_string_setter_worker();
|
|
|
|
// - driver logic
|
|
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
function folder_string_setter_driver() {
|
|
try {
|
|
if (worker.next().done) {
|
|
timer.cancel();
|
|
if (aCallback)
|
|
aCallback(true);
|
|
}
|
|
}
|
|
catch (ex) {
|
|
// Any type of exception kills the generator.
|
|
timer.cancel();
|
|
if (aCallback)
|
|
aCallback(false);
|
|
}
|
|
}
|
|
// make sure there is at least 100 ms of not us between doing things.
|
|
timer.initWithCallback(folder_string_setter_driver,
|
|
this.INTER_FOLDER_PROCESSING_DELAY_MS,
|
|
Ci.nsITimer.TYPE_REPEATING_SLACK);
|
|
},
|
|
};
|