Коммит
c51b6f4722
|
@ -1 +1 @@
|
|||
0bb2a0ee37c1af1c76d758c1eee1d99b18924704
|
||||
b11a45aa831d50594e7f23968bda9bc010103ef1
|
|
@ -49,9 +49,9 @@ MOZ_ACTIVEX_SCRIPTING_SUPPORT=
|
|||
MOZ_INSTALLER=
|
||||
MOZ_MATHML=
|
||||
NECKO_DISK_CACHE=
|
||||
if test "$MOZILLA_BRANCH_VERSION" = "1.9.1"; then
|
||||
# MOZ_OJI is only required to be cleared for MOZILLA_1_9_1_BRANCH.
|
||||
# mozilla-central does not have this.
|
||||
MOZ_OJI=
|
||||
fi
|
||||
NECKO_COOKIES=
|
||||
MOZ_NO_XPCOM_OBSOLETE=1
|
||||
MOZ_EXTENSIONS_DEFAULT=
|
||||
|
|
|
@ -473,7 +473,6 @@ MOZ_TREE_FREETYPE = @MOZ_TREE_FREETYPE@
|
|||
MOZ_ENABLE_GTK2 = @MOZ_ENABLE_GTK2@
|
||||
MOZ_ENABLE_QT = @MOZ_ENABLE_QT@
|
||||
MOZ_ENABLE_PHOTON = @MOZ_ENABLE_PHOTON@
|
||||
MOZ_ENABLE_COCOA = @MOZ_ENABLE_COCOA@
|
||||
MOZ_ENABLE_XREMOTE = @MOZ_ENABLE_XREMOTE@
|
||||
|
||||
MOZ_GTK2_CFLAGS = @MOZ_GTK2_CFLAGS@
|
||||
|
|
|
@ -4835,7 +4835,6 @@ cairo-mac|cairo-cocoa)
|
|||
if test "$MOZ_WIDGET_TOOLKIT" = "cairo-cocoa"; then
|
||||
MOZ_WIDGET_TOOLKIT=cocoa
|
||||
AC_DEFINE(MOZ_WIDGET_COCOA)
|
||||
MOZ_ENABLE_COCOA=1
|
||||
else
|
||||
MOZ_WIDGET_TOOLKIT=mac
|
||||
fi
|
||||
|
@ -4950,7 +4949,6 @@ AC_SUBST(TK_LIBS)
|
|||
|
||||
AC_SUBST(MOZ_ENABLE_GTK2)
|
||||
AC_SUBST(MOZ_ENABLE_PHOTON)
|
||||
AC_SUBST(MOZ_ENABLE_COCOA)
|
||||
AC_SUBST(MOZ_ENABLE_QT)
|
||||
AC_SUBST(MOZ_ENABLE_XREMOTE)
|
||||
AC_SUBST(MOZ_GTK2_CFLAGS)
|
||||
|
|
|
@ -462,6 +462,7 @@ pref("mail.compose.attachment_reminder", true);
|
|||
// Words that should trigger a missing attachments warning.
|
||||
pref("mail.compose.attachment_reminder_keywords", "chrome://messenger/locale/messengercompose/composeMsgs.properties");
|
||||
|
||||
pref("browser.formfill.enable", true);
|
||||
// Override the all.js values so that unit tests pass and we get sane values.
|
||||
pref("browser.history_expire_days", 180);
|
||||
pref("browser.history_expire_days_min", 90);
|
||||
|
|
|
@ -121,8 +121,8 @@ function MailToolboxCustomizeDone(aEvent, customizePopupId)
|
|||
// make sure the folder location picker is initialized
|
||||
if (document.getElementById("folder-location-container"))
|
||||
{
|
||||
loadFolderViewForTree(gCurrentFolderView, document.getElementById('folderLocationPopup').tree);
|
||||
UpdateFolderLocationPicker(gFolderDisplay.displayedFolder);
|
||||
// XXX FIXME: used to call loadFolderViewForTree and UpdateFolderLocationPicker
|
||||
// but those were removed in changeset 1de58e1d7549
|
||||
}
|
||||
|
||||
gSearchInput = null;
|
||||
|
|
|
@ -238,11 +238,17 @@ function InitViewLayoutStyleMenu(event)
|
|||
layoutStyleMenuitem.setAttribute("checked", "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize (check) appropriate folder mode under the View | Folder menu.
|
||||
*/
|
||||
function InitViewFolderViewsMenu(event)
|
||||
{
|
||||
var layoutStyleMenuitem = event.target.childNodes[gCurrentFolderView];
|
||||
if (layoutStyleMenuitem)
|
||||
layoutStyleMenuitem.setAttribute("checked", "true");
|
||||
for (let i = 0; i < event.target.childNodes.length; i++) {
|
||||
if (event.target.childNodes[i].value == gFolderTreeView.mode) {
|
||||
event.target.childNodes[i].setAttribute("checked", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSortByMenuItemCheckState(id, value)
|
||||
|
@ -2283,21 +2289,16 @@ let mailTabType = {
|
|||
this.restoreFocus(aTab);
|
||||
},
|
||||
|
||||
supportsCommand: function(aTab, aCommand) {
|
||||
return DefaultController.supportsCommand(aCommand);
|
||||
},
|
||||
|
||||
isCommandEnabled: function(aTab, aCommand) {
|
||||
return DefaultController.isCommandEnabled(aCommand);
|
||||
},
|
||||
|
||||
doCommand: function(aTab, aCommand) {
|
||||
DefaultController.doCommand(aCommand, aTab);
|
||||
},
|
||||
|
||||
onEvent: function(aTab, aEvent) {
|
||||
DefaultController.onEvent(aEvent);
|
||||
}
|
||||
//
|
||||
// nsIController implementation
|
||||
//
|
||||
// We ignore the aTab parameter sent by tabmail when calling nsIController
|
||||
// stuff and just delegate the call to a default controller by using it as
|
||||
// our proto chain:
|
||||
// - "DefaultController" is the default controller of messenger.xul
|
||||
// - "MessageWindowController" is the default controller of messageWindow.xul
|
||||
__proto__: "DefaultController" in window && window.DefaultController ||
|
||||
"MessageWindowController" in window && window.MessageWindowController
|
||||
};
|
||||
/**
|
||||
* The glodaSearch tab mode has a UI widget outside of the mailTabType's
|
||||
|
@ -2449,7 +2450,9 @@ function MsgJunk()
|
|||
JunkSelectedMessages(!SelectedMessagesAreJunk());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the "mark as junk" button in the message header area.
|
||||
*/
|
||||
function UpdateJunkButton()
|
||||
{
|
||||
// The junk message should slave off the selected message, as the preview pane
|
||||
|
@ -2459,7 +2462,7 @@ function UpdateJunkButton()
|
|||
if (!hdr || gMessageDisplay.isDummy) // .eml file
|
||||
return;
|
||||
let junkScore = hdr.getStringProperty("junkscore");
|
||||
let hideJunk = (junkScore != "") && (junkScore != "0");
|
||||
let hideJunk = (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE);
|
||||
if (gFolderDisplay.selectedMessageIsNews)
|
||||
hideJunk = true;
|
||||
|
||||
|
@ -3055,18 +3058,18 @@ function HandleJunkStatusChanged(folder)
|
|||
// Only bother doing this if we are modifying the html for junk mail....
|
||||
if (sanitizeJunkMail)
|
||||
{
|
||||
var moveJunkMail = (folder && folder.server && folder.server.spamSettings) ?
|
||||
folder.server.spamSettings.manualMark : false;
|
||||
let junkScore = msgHdr.getStringProperty("junkscore");
|
||||
let isJunk = (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE);
|
||||
|
||||
var junkScore = msgHdr.getStringProperty("junkscore");
|
||||
var isJunk = (junkScore == "") || (junkScore == "0");
|
||||
// If the current row isn't going to change, reload to show sanitized or
|
||||
// unsanitized. Otherwise we wouldn't see the reloaded version anyway.
|
||||
|
||||
// We used to only reload the message if we were toggling the message
|
||||
// to NOT JUNK from junk but it can be useful to see the HTML in the
|
||||
// message get converted to sanitized form when a message is marked as
|
||||
// junk. Furthermore, if we are about to move the message that was just
|
||||
// marked as junk then don't bother reloading it.
|
||||
if (!(isJunk && moveJunkMail))
|
||||
// 1) When marking as non-junk, the msg would move back to the inbox.
|
||||
// 2) When marking as junk, the msg will move or delete, if manualMark is set.
|
||||
// 3) Marking as junk in the junk folder just changes the junk status.
|
||||
if ((!isJunk && folder.isSpecialFolder(Components.interfaces.nsMsgFolderFlags.Inbox)) ||
|
||||
(isJunk && !folder.server.spamSettings.manualMark) ||
|
||||
(isJunk && folder.isSpecialFolder(Components.interfaces.nsMsgFolderFlags.Junk)))
|
||||
ReloadMessage();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1029,19 +1029,28 @@
|
|||
</menupopup>
|
||||
</menu>
|
||||
<menu id="menu_FolderViews" label="&folderView.label;" accesskey="&folderView.accesskey;">
|
||||
<menupopup id="menu_FolderViewsPopup" onpopupshowing="InitViewFolderViewsMenu(event)">
|
||||
<menuitem id="menu_allFolders" label="&allFolders.label;" accesskey="&allFolders.accesskey;"
|
||||
<menupopup id="menu_FolderViewsPopup"
|
||||
onpopupshowing="InitViewFolderViewsMenu(event)">
|
||||
<menuitem id="menu_allFolders" value="all"
|
||||
label="&allFolders.label;"
|
||||
accesskey="&allFolders.accesskey;"
|
||||
type="radio" name="viewmessages"
|
||||
oncommand="gFolderTreeView.mode = 'all';"/>
|
||||
<menuitem id="menu_unreadFolders" label="&unreadFolders.label;" accesskey="&unreadFolders.accesskey;"
|
||||
oncommand="gFolderTreeView.mode = this.value;"/>
|
||||
<menuitem id="menu_unreadFolders"
|
||||
label="&unreadFolders.label;" value="unread"
|
||||
accesskey="&unreadFolders.accesskey;"
|
||||
type="radio" name="viewmessages"
|
||||
oncommand="gFolderTreeView.mode = 'unread';"/>
|
||||
<menuitem id="menu_favoriteFolders" label="&favoriteFolders.label;" accesskey="&favoriteFolders.accesskey;"
|
||||
oncommand="gFolderTreeView.mode = this.value;"/>
|
||||
<menuitem id="menu_favoriteFolders" value="favorite"
|
||||
label="&favoriteFolders.label;"
|
||||
accesskey="&favoriteFolders.accesskey;"
|
||||
type="radio" name="viewmessages"
|
||||
oncommand="gFolderTreeView.mode = 'favorite';"/>
|
||||
<menuitem id="menu_recentFolders" label="&recentFolders.label;" accesskey="&recentFolders.accesskey;"
|
||||
oncommand="gFolderTreeView.mode = this.value;"/>
|
||||
<menuitem id="menu_recentFolders" value="recent"
|
||||
label="&recentFolders.label;"
|
||||
accesskey="&recentFolders.accesskey;"
|
||||
type="radio" name="viewmessages"
|
||||
oncommand="gFolderTreeView.mode = 'recent';"/>
|
||||
oncommand="gFolderTreeView.mode = this.value;"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator id="viewSortMenuSeparator"/>
|
||||
|
|
|
@ -66,7 +66,6 @@ var gMessagePane;
|
|||
var gSearchInput;
|
||||
|
||||
var gThreadAndMessagePaneSplitter = null;
|
||||
var gCurrentFolderView;
|
||||
var gStartFolderUri = null;
|
||||
/**
|
||||
* Tracks whether the right mouse button changed the selection or not. If the
|
||||
|
|
|
@ -164,7 +164,7 @@ var specialTabs = {
|
|||
onTitleChanged: function onTitleChanged(aTab) {
|
||||
aTab.title = aTab.browser.contentDocument.title;
|
||||
},
|
||||
supportsCommand: function supportsCommand(aTab, aCommand) {
|
||||
supportsCommand: function supportsCommand(aCommand, aTab) {
|
||||
switch (aCommand) {
|
||||
case "cmd_fullZoomReduce":
|
||||
case "cmd_fullZoomEnlarge":
|
||||
|
@ -183,7 +183,7 @@ var specialTabs = {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
isCommandEnabled: function isCommandEnabled(aTab, aCommand) {
|
||||
isCommandEnabled: function isCommandEnabled(aCommand, aTab) {
|
||||
switch (aCommand) {
|
||||
case "cmd_fullZoomReduce":
|
||||
case "cmd_fullZoomEnlarge":
|
||||
|
@ -202,7 +202,7 @@ var specialTabs = {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
doCommand: function isCommandEnabled(aTab, aCommand) {
|
||||
doCommand: function isCommandEnabled(aCommand, aTab) {
|
||||
switch (aCommand) {
|
||||
case "cmd_fullZoomReduce":
|
||||
ZoomManager.reduce();
|
||||
|
|
|
@ -36,13 +36,25 @@
|
|||
-
|
||||
- ***** END LICENSE BLOCK ***** -->
|
||||
|
||||
<!DOCTYPE overlay [
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
]>
|
||||
|
||||
<overlay id="specialTabs"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<popupset id="mainPopupSet">
|
||||
<!-- for search and content formfill/pw manager -->
|
||||
<panel type="autocomplete" id="PopupAutoComplete" level="top"
|
||||
noautofocus="true" chromedir="&locale.dir;"/>
|
||||
</popupset>
|
||||
|
||||
<vbox id="contentTab" collapsed="true">
|
||||
<vbox flex="1">
|
||||
<notificationbox flex="1">
|
||||
<browser type="content-targetable" flex="1" disablehistory="true"/>
|
||||
<browser type="content-targetable" flex="1" disablehistory="true"
|
||||
autocompletepopup="PopupAutoComplete"/>
|
||||
</notificationbox>
|
||||
<findbar/>
|
||||
</vbox>
|
||||
|
|
|
@ -205,15 +205,15 @@
|
|||
- hint that the tab's title needs to be updated. This function should
|
||||
- update aTab.title if it can.
|
||||
- Mode definition functions to do with menu/toolbar commands:
|
||||
- * supportsCommand(aTab, aCommand): Called when a menu or toolbar needs to
|
||||
- * supportsCommand(aCommand, aTab): Called when a menu or toolbar needs to
|
||||
- be updated. Return true if you support that command in
|
||||
- isCommandEnabled and doCommand, return false otherwise.
|
||||
- * isCommandEnabled(aTab, aCommand): Called when a menu or toolbar needs
|
||||
- * isCommandEnabled(aCommand, aTab): Called when a menu or toolbar needs
|
||||
- to be updated. Return true if the command can be executed at the
|
||||
- current time, false otherwise.
|
||||
- * doCommand(aTab, aCommand): Called when a menu or toolbar command is to
|
||||
- * doCommand(aCommand, aTab): Called when a menu or toolbar command is to
|
||||
- be executed. Perform the action appropriate to the command.
|
||||
- * onEvent(aTab, aEvent): This can be used to handle different events on
|
||||
- * onEvent(aEvent, aTab): This can be used to handle different events on
|
||||
- the window.
|
||||
- * getBrowser(aTab): This function should return the browser element for
|
||||
- your tab if there is one (return null or don't define this function
|
||||
|
@ -973,7 +973,7 @@
|
|||
let supportsCommandFunc = tab.mode.supportsCommand ||
|
||||
tab.mode.tabType.supportsCommand;
|
||||
if (supportsCommandFunc)
|
||||
return supportsCommandFunc.call(tab.mode.tabType, tab, aCommand);
|
||||
return supportsCommandFunc.call(tab.mode.tabType, aCommand, tab);
|
||||
|
||||
return false;
|
||||
]]>
|
||||
|
@ -993,7 +993,7 @@
|
|||
let isCommandEnabledFunc = tab.mode.isCommandEnabled ||
|
||||
tab.mode.tabType.isCommandEnabled;
|
||||
if (isCommandEnabledFunc)
|
||||
return isCommandEnabledFunc.call(tab.mode.tabType, tab, aCommand);
|
||||
return isCommandEnabledFunc.call(tab.mode.tabType, aCommand, tab);
|
||||
|
||||
return false;
|
||||
]]>
|
||||
|
@ -1013,7 +1013,7 @@
|
|||
let doCommandFunc = tab.mode.doCommand ||
|
||||
tab.mode.tabType.doCommand;
|
||||
if (doCommandFunc)
|
||||
doCommandFunc.call(tab.mode.tabType, tab, aCommand);
|
||||
doCommandFunc.call(tab.mode.tabType, aCommand, tab);
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
@ -1031,7 +1031,7 @@
|
|||
let onEventFunc = tab.mode.onEvent ||
|
||||
tab.mode.tabType.onEvent;
|
||||
if (onEventFunc)
|
||||
return onEventFunc.call(tab.mode.tabType, tab, aEvent);
|
||||
return onEventFunc.call(tab.mode.tabType, aEvent, tab);
|
||||
|
||||
return false;
|
||||
]]>
|
||||
|
|
|
@ -409,19 +409,39 @@ let SearchSupport =
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!this._headerEnumerator)
|
||||
this._headerEnumerator = this._currentFolderToIndex.messages;
|
||||
|
||||
// iterate over the folder finding the next message to index
|
||||
let reindexTime = this._getLastReindexTime(this._currentFolderToIndex);
|
||||
this._log.debug("Reindex time for this folder is " + reindexTime);
|
||||
if (!this._headerEnumerator)
|
||||
{
|
||||
// we need to create search terms for messages to index
|
||||
let searchSession = Cc["@mozilla.org/messenger/searchSession;1"]
|
||||
.createInstance(Ci.nsIMsgSearchSession);
|
||||
let searchTerms = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
|
||||
searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail, this._currentFolderToIndex);
|
||||
let nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
|
||||
let nsMsgSearchOp = Ci.nsMsgSearchOp;
|
||||
// first term: (_hdrIndexProperty < reindexTime)
|
||||
let searchTerm = searchSession.createTerm();
|
||||
searchTerm.booleanAnd = false; // actually don't care here
|
||||
searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
|
||||
searchTerm.op = nsMsgSearchOp.IsLessThan;
|
||||
value = searchTerm.value;
|
||||
value.attrib = searchTerm.attrib;
|
||||
searchTerm.hdrProperty = this._hdrIndexedProperty;
|
||||
value.status = reindexTime;
|
||||
searchTerm.value = value;
|
||||
searchTerms.appendElement(searchTerm, false);
|
||||
this._headerEnumerator = this._currentFolderToIndex.msgDatabase
|
||||
.getFilterEnumerator(searchTerms);
|
||||
}
|
||||
|
||||
// iterate over the folder finding the next message to index
|
||||
while (this._headerEnumerator.hasMoreElements())
|
||||
{
|
||||
let msgHdr = this._headerEnumerator.getNext()
|
||||
.QueryInterface(Ci.nsIMsgDBHdr);
|
||||
|
||||
if (msgHdr.getUint32Property(this._hdrIndexedProperty) < reindexTime)
|
||||
{
|
||||
// Check if the file exists. If it does, then assume indexing to be
|
||||
// complete for this file
|
||||
if (this._getSupportFile(msgHdr).exists())
|
||||
|
@ -436,7 +456,6 @@ let SearchSupport =
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(ex) { this._log.debug("Error while finding next header: " + ex); }
|
||||
|
||||
// If we couldn't find any headers to index, null out the enumerator
|
||||
|
|
|
@ -41,9 +41,9 @@ MOZ_UPDATER=1
|
|||
MOZ_THUNDERBIRD=1
|
||||
MOZ_NO_ACTIVEX_SUPPORT=1
|
||||
MOZ_ACTIVEX_SCRIPTING_SUPPORT=
|
||||
if test "$MOZILLA_BRANCH_VERSION" = "1.9.1"; then
|
||||
# MOZ_OJI is only required to be cleared for MOZILLA_1_9_1_BRANCH.
|
||||
# mozilla-central does not have this.
|
||||
MOZ_OJI=
|
||||
fi
|
||||
NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource"
|
||||
MOZ_IMG_DECODERS_DEFAULT=`echo "$MOZ_IMG_DECODERS_DEFAULT" | sed "s/ xbm//"`
|
||||
MOZ_MAIL_NEWS=1
|
||||
|
|
|
@ -50,12 +50,21 @@ components/history.xpt
|
|||
components/jsconsole.xpt
|
||||
components/mailnews.xpt
|
||||
components/mozgnome.xpt
|
||||
#ifdef MOZILLA_1_9_1_BRANCH
|
||||
components/satchel.xpt
|
||||
#endif
|
||||
components/@DLL_PREFIX@myspell@DLL_SUFFIX@
|
||||
components/necko_data.xpt
|
||||
#ifdef MOZILLA_1_9_1_BRANCH
|
||||
components/GPSDGeolocationProvider.js
|
||||
#endif
|
||||
components/nsBackgroundUpdateService.js
|
||||
components/nsCloseAllWindows.js
|
||||
components/nsComposerCmdLineHandler.js
|
||||
components/nsDownloadProgressListener.js
|
||||
#ifdef MOZILLA_1_9_1_BRANCH
|
||||
components/nsFormAutoComplete.js
|
||||
#endif
|
||||
components/nsInterfaceInfoToIDL.js
|
||||
components/nsLDAPPrefsService.js
|
||||
#ifdef XP_WIN
|
||||
|
|
|
@ -283,6 +283,9 @@ bin\components\toolkitprofile.xpt
|
|||
bin\components\commandlines.xpt
|
||||
bin\components\chrome.xpt
|
||||
bin\components\nsDefaultCLH.js
|
||||
#ifndef MOZILLA_1_9_1_BRANCH
|
||||
bin\components\nsFormAutoComplete.js
|
||||
#endif
|
||||
|
||||
; rdf
|
||||
bin\components\rdf.xpt
|
||||
|
@ -318,6 +321,9 @@ bin\components\dom_xbl.xpt
|
|||
bin\components\dom_xul.xpt
|
||||
bin\components\dom_loadsave.xpt
|
||||
bin\components\NetworkGeolocationProvider.js
|
||||
#ifndef MOZILLA_1_9_1_BRANCH
|
||||
bin\components\GPSDGeolocationProvider.js
|
||||
#endif
|
||||
|
||||
; editor / composer for HTML compose
|
||||
bin\components\editor.xpt
|
||||
|
@ -414,6 +420,9 @@ bin\components\pluginGlue.js
|
|||
bin\components\txEXSLTRegExFunctions.js
|
||||
bin\components\feeds.xpt
|
||||
bin\components\saxparser.xpt
|
||||
#ifndef MOZILLA_1_9_1_BRANCH
|
||||
bin\components\satchel.xpt
|
||||
#endif
|
||||
bin\components\shistory.xpt
|
||||
bin\components\zipwriter.xpt
|
||||
bin\components\nsBadCertHandler.js
|
||||
|
|
|
@ -140,7 +140,6 @@ treechildren::-moz-tree-image(folderNameCol, specialFolder-Archive) {
|
|||
list-style-image: url("chrome://messenger/skin/icons/folder-blank.png");
|
||||
}
|
||||
|
||||
|
||||
/* ..... Shared folders .....
|
||||
|
||||
treechildren::-moz-tree-image(folderNameCol, imapShared-true) {
|
||||
|
@ -158,6 +157,13 @@ treechildren::-moz-tree-image(folderNameCol, specialFolder-Virtual) {
|
|||
list-style-image: url("chrome://messenger/skin/icons/folder-blank.png");
|
||||
}
|
||||
|
||||
/* ..... Newsgroup ..... */
|
||||
.tabmail-tab[type="folder"][ServerType="nntp"] > .tab-image-middle > .tab-icon > .tab-icon-image {
|
||||
background-image: url("chrome://messenger/skin/icons/folder-pane.png");
|
||||
background-position: left -208px;
|
||||
list-style-image: url("chrome://messenger/skin/icons/folder-blank.png");
|
||||
}
|
||||
|
||||
/* ..... Account nodes ..... */
|
||||
.tabmail-tab[type="folder"][IsServer="true"] > .tab-image-middle > .tab-icon > .tab-icon-image {
|
||||
-moz-margin-start: 0px;
|
||||
|
@ -193,13 +199,6 @@ treechildren::-moz-tree-image(folderNameCol, specialFolder-Virtual) {
|
|||
-moz-image-region: rect(0 80px 16px 64px);
|
||||
}
|
||||
|
||||
/* ..... Newsgroup ..... */
|
||||
.tabmail-tab[type="folder"][ServerType="nntp"] > .tab-image-middle > .tab-icon > .tab-icon-image {
|
||||
background-image: none;
|
||||
list-style-image: url("chrome://messenger/skin/icons/server.png");
|
||||
-moz-image-region: rect(0 160px 16px 144px);
|
||||
}
|
||||
|
||||
treechildren::-moz-tree-image(folderNameCol, specialFolder-Virtual, newMessages-true) {
|
||||
list-style-image: url("chrome://messenger/skin/icons/folder.png");
|
||||
-moz-image-region: rect(16px 176px 32px 160px);
|
||||
|
|
Двоичные данные
mail/themes/gnomestripe/mail/icons/folder-pane.png
Двоичные данные
mail/themes/gnomestripe/mail/icons/folder-pane.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 6.0 KiB После Ширина: | Высота: | Размер: 6.4 KiB |
|
@ -28,8 +28,18 @@ classic.jar:
|
|||
skin/classic/editor/icons/img-align-top.gif (editor/img-align-top.gif)
|
||||
% skin messenger classic/1.0 %skin/classic/messenger/ os=WINNT osversion<6
|
||||
% skin messenger classic/1.0 %skin/classic/messenger/ os!=WINNT
|
||||
# NOTE: If you add a new file here, you'll need to add it to the aero
|
||||
# section at the bottom of this file, too.
|
||||
|
||||
# ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
|
||||
# ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
|
||||
#
|
||||
# If you add a new file here, you'll need to add it to the aero section at the
|
||||
# bottom of this file, too. If you do not, people using Windows prior to Vista
|
||||
# will be able to enjoy your additions, but people using Vista and above will
|
||||
# not.
|
||||
#
|
||||
# ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
|
||||
# ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION! ATTENTION!
|
||||
|
||||
skin/classic/messenger/primaryToolbar.css (mail/primaryToolbar.css)
|
||||
skin/classic/messenger/accountCentral.css (mail/accountCentral.css)
|
||||
skin/classic/messenger/accountCreation.css (mail/accountCreation.css)
|
||||
|
@ -226,6 +236,8 @@ classic.jar:
|
|||
skin/classic/aero/messenger/accountCreation.css (mail/accountCreation.css)
|
||||
skin/classic/aero/messenger/accountManage.css (mail/accountManage.css)
|
||||
skin/classic/aero/messenger/accountWizard.css (mail/accountWizard.css)
|
||||
skin/classic/aero/messenger/section_collapsed.png (mail/section_collapsed.png)
|
||||
skin/classic/aero/messenger/section_expanded.png (mail/section_expanded.png)
|
||||
skin/classic/aero/messenger/messageHeader.css (mail/messageHeader.css)
|
||||
skin/classic/aero/messenger/messageBody.css (mail/messageBody.css)
|
||||
skin/classic/aero/messenger/messageQuotes.css (mail/messageQuotes.css)
|
||||
|
@ -285,6 +297,8 @@ classic.jar:
|
|||
skin/classic/aero/messenger/messengercompose/compose-toolbar.png (mail/compose/compose-toolbar.png)
|
||||
skin/classic/aero/messenger/messengercompose/compose-toolbar-small.png (mail/compose/compose-toolbar-small.png)
|
||||
skin/classic/aero/messenger/messengercompose/format-buttons.png (mail/compose/format-buttons-aero.png)
|
||||
skin/classic/aero/messenger/preferences/alwaysAsk.png (mail/preferences/alwaysAsk.png)
|
||||
skin/classic/aero/messenger/preferences/application.png (mail/preferences/application.png)
|
||||
* skin/classic/aero/messenger/preferences/preferences.css (mail/preferences/preferences-aero.css)
|
||||
skin/classic/aero/messenger/preferences/general.png (mail/preferences/general-aero.png)
|
||||
skin/classic/aero/messenger/preferences/display.png (mail/preferences/display-aero.png)
|
||||
|
@ -292,6 +306,8 @@ classic.jar:
|
|||
skin/classic/aero/messenger/preferences/junk.png (mail/preferences/junk-aero.png)
|
||||
skin/classic/aero/messenger/preferences/security.png (mail/preferences/security-aero.png)
|
||||
skin/classic/aero/messenger/preferences/attachments.png (mail/preferences/attachments-aero.png)
|
||||
skin/classic/aero/messenger/preferences/applications.css (mail/preferences/applications.css)
|
||||
skin/classic/aero/messenger/preferences/saveFile.png (mail/preferences/saveFile.png)
|
||||
skin/classic/aero/messenger/preferences/advanced.png (mail/preferences/advanced-aero.png)
|
||||
skin/classic/aero/messenger/preferences/background.png (mail/preferences/background.png)
|
||||
skin/classic/aero/messenger/preferences/hover.png (mail/preferences/hover.png)
|
||||
|
|
|
@ -151,7 +151,7 @@ function folderPropsOKButton()
|
|||
else
|
||||
gMsgFolder.clearFlag(nsMsgFolderFlags.CheckNew);
|
||||
|
||||
var retentionSettings = saveCommonRetentionSettings();
|
||||
var retentionSettings = saveCommonRetentionSettings(gMsgFolder.retentionSettings);
|
||||
retentionSettings.useServerDefaults = document.getElementById("retention.useDefault").checked;
|
||||
gMsgFolder.retentionSettings = retentionSettings;
|
||||
|
||||
|
|
|
@ -52,22 +52,18 @@ function initCommonRetentionSettings(retentionSettings)
|
|||
!retentionSettings.applyToFlaggedMessages;
|
||||
}
|
||||
|
||||
function saveCommonRetentionSettings()
|
||||
function saveCommonRetentionSettings(aRetentionSettings)
|
||||
{
|
||||
var retentionSettings =
|
||||
Components.classes["@mozilla.org/msgDatabase/retentionSettings;1"]
|
||||
.createInstance(Components.interfaces.nsIMsgRetentionSettings);
|
||||
aRetentionSettings.retainByPreference = document.getElementById("retention.keepMsg").value;
|
||||
|
||||
retentionSettings.retainByPreference = document.getElementById("retention.keepMsg").value;
|
||||
aRetentionSettings.daysToKeepHdrs = document.getElementById("retention.keepOldMsgMin").value;
|
||||
aRetentionSettings.numHeadersToKeep = document.getElementById("retention.keepNewMsgMin").value;
|
||||
aRetentionSettings.keepUnreadMessagesOnly = document.getElementById("retention.keepUnread").checked;
|
||||
|
||||
retentionSettings.daysToKeepHdrs = document.getElementById("retention.keepOldMsgMin").value;
|
||||
retentionSettings.numHeadersToKeep = document.getElementById("retention.keepNewMsgMin").value;
|
||||
retentionSettings.keepUnreadMessagesOnly = document.getElementById("retention.keepUnread").checked;
|
||||
|
||||
retentionSettings.applyToFlaggedMessages =
|
||||
aRetentionSettings.applyToFlaggedMessages =
|
||||
!document.getElementById("retention.applyToFlagged").checked;
|
||||
|
||||
return retentionSettings;
|
||||
return aRetentionSettings;
|
||||
}
|
||||
|
||||
function onCheckKeepMsg()
|
||||
|
|
|
@ -165,7 +165,7 @@ function onSave()
|
|||
gIncomingServer.limitOfflineMessageSize = document.getElementById("offline.notDownload").checked;
|
||||
gIncomingServer.maxMessageSize = document.getElementById("offline.notDownloadMin").value;
|
||||
|
||||
var retentionSettings = saveCommonRetentionSettings();
|
||||
var retentionSettings = saveCommonRetentionSettings(gIncomingServer.retentionSettings);
|
||||
|
||||
retentionSettings.daysToKeepBodies = document.getElementById("nntp.removeBodyMin").value;
|
||||
retentionSettings.cleanupBodiesByDays = document.getElementById("nntp.removeBody").checked;
|
||||
|
|
|
@ -52,7 +52,7 @@ interface nsIArray;
|
|||
* these two interfaces should be combined somehow.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(4dee6994-1a7d-4200-b9a7-b1fc54df3397)]
|
||||
[scriptable, uuid(eebbedae-ce43-4151-9798-3f1c6e5a5af0)]
|
||||
interface nsIMsgFolderListener : nsISupports {
|
||||
/**
|
||||
* Notified immediately after a message is added to a folder. This could be a new incoming
|
||||
|
|
|
@ -45,7 +45,7 @@ interface nsIArray;
|
|||
|
||||
typedef unsigned long msgFolderListenerFlag;
|
||||
|
||||
[scriptable, uuid(54900730-ba1f-4315-af44-cfbb2121d115)]
|
||||
[scriptable, uuid(e5d75176-a3d7-4f80-a1fe-b915a679ca53)]
|
||||
interface nsIMsgFolderNotificationService : nsISupports {
|
||||
/**
|
||||
* @name Notification flags
|
||||
|
|
|
@ -81,10 +81,14 @@ interface nsISpamSettings: nsISupports {
|
|||
attribute string whiteListAbURI;
|
||||
|
||||
/**
|
||||
* should we do something (move or delete)
|
||||
* when the user manually marks a message as junk?
|
||||
* Should we do something when the user manually marks a message as junk?
|
||||
*/
|
||||
readonly attribute boolean manualMark;
|
||||
|
||||
/**
|
||||
* With manualMark true, which action (move to the Junk folder, or delete)
|
||||
* should we take when the user marks a message as junk.
|
||||
*/
|
||||
readonly attribute long manualMarkMode;
|
||||
const long MANUAL_MARK_MODE_MOVE = 0;
|
||||
const long MANUAL_MARK_MODE_DELETE = 1;
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
/**
|
||||
* describes a custom term added to a message search or filter
|
||||
*/
|
||||
[scriptable,uuid(8DD6E135-6790-475e-9547-3C1408CF2F08)]
|
||||
[scriptable,uuid(925DB5AA-21AF-494c-8652-984BC7BAD13A)]
|
||||
interface nsIMsgSearchCustomTerm : nsISupports
|
||||
{
|
||||
/**
|
||||
|
@ -55,6 +55,9 @@ interface nsIMsgSearchCustomTerm : nsISupports
|
|||
/// name to display in term list. This should be localized. */
|
||||
readonly attribute AString name;
|
||||
|
||||
/// Does this term need the message body?
|
||||
readonly attribute boolean needsBody;
|
||||
|
||||
/**
|
||||
* Is this custom term enabled?
|
||||
*
|
||||
|
|
|
@ -600,8 +600,12 @@ nsMsgSearchValidityManager::InitOnlineMailFilterTable()
|
|||
|
||||
m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
|
||||
m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
|
||||
m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
|
||||
m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
|
||||
m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
|
||||
m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
|
||||
m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
|
||||
m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
|
||||
m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
|
||||
m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
|
||||
m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
|
||||
|
|
|
@ -195,6 +195,7 @@ nsMsgAccountManager::~nsMsgAccountManager()
|
|||
if (NS_SUCCEEDED(rv))
|
||||
{
|
||||
observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
|
||||
observerService->RemoveObserver(this, "quit-application-granted");
|
||||
observerService->RemoveObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC);
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +218,7 @@ nsresult nsMsgAccountManager::Init()
|
|||
if (NS_SUCCEEDED(rv))
|
||||
{
|
||||
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
|
||||
observerService->AddObserver(this, "quit-application" , PR_TRUE);
|
||||
observerService->AddObserver(this, "quit-application-granted" , PR_TRUE);
|
||||
observerService->AddObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC, PR_TRUE);
|
||||
observerService->AddObserver(this, "profile-before-change", PR_TRUE);
|
||||
}
|
||||
|
@ -293,13 +294,12 @@ NS_IMETHODIMP nsMsgAccountManager::Observe(nsISupports *aSubject, const char *aT
|
|||
Shutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!strcmp(aTopic,"quit-application"))
|
||||
if (!strcmp(aTopic, "quit-application-granted"))
|
||||
{
|
||||
m_shutdownInProgress = PR_TRUE;
|
||||
// CleanupOnExit will set m_shutdownInProgress to true.
|
||||
CleanupOnExit();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!strcmp(aTopic, ABOUT_TO_GO_OFFLINE_TOPIC))
|
||||
{
|
||||
nsAutoString dataString(NS_LITERAL_STRING("offline"));
|
||||
|
@ -1491,6 +1491,11 @@ nsMsgAccountManager::CloseCachedConnections()
|
|||
NS_IMETHODIMP
|
||||
nsMsgAccountManager::CleanupOnExit()
|
||||
{
|
||||
// This can get called multiple times, and potentially re-entrantly.
|
||||
// So add some protection against that.
|
||||
if (m_shutdownInProgress)
|
||||
return NS_OK;
|
||||
m_shutdownInProgress = PR_TRUE;
|
||||
m_incomingServers.Enumerate(hashCleanupOnExit, nsnull);
|
||||
// Try to do this early on in the shutdown process before
|
||||
// necko shuts itself down.
|
||||
|
|
|
@ -394,6 +394,10 @@ NS_IMETHODIMP nsMsgMailSession::AddMsgWindow(nsIMsgWindow *msgWindow)
|
|||
NS_IMETHODIMP nsMsgMailSession::RemoveMsgWindow(nsIMsgWindow *msgWindow)
|
||||
{
|
||||
mWindows.RemoveObject(msgWindow);
|
||||
// Mac keeps a hidden window open so the app doesn't shut down when
|
||||
// the last window is closed. So don't shutdown the account manager in that
|
||||
// case.
|
||||
#ifndef XP_MACOSX
|
||||
if (!mWindows.Count())
|
||||
{
|
||||
nsresult rv;
|
||||
|
@ -403,6 +407,7 @@ NS_IMETHODIMP nsMsgMailSession::RemoveMsgWindow(nsIMsgWindow *msgWindow)
|
|||
return rv;
|
||||
accountManager->CleanupOnExit();
|
||||
}
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@ nsIAtom* nsMsgDBFolder::kSynchronizeAtom=nsnull;
|
|||
nsIAtom* nsMsgDBFolder::kOpenAtom=nsnull;
|
||||
nsIAtom* nsMsgDBFolder::kIsDeferred=nsnull;
|
||||
nsIAtom* nsMsgDBFolder::kKeywords=nsnull;
|
||||
nsIAtom* nsMsgDBFolder::mFiltersAppliedAtom=nsnull;
|
||||
|
||||
nsICollation * nsMsgDBFolder::gCollationKeyGenerator = nsnull;
|
||||
|
||||
|
@ -174,7 +175,8 @@ const nsStaticAtom nsMsgDBFolder::folder_atoms[] = {
|
|||
{ "Synchronize", &nsMsgDBFolder::kSynchronizeAtom },
|
||||
{ "open", &nsMsgDBFolder::kOpenAtom },
|
||||
{ "isDeferred", &nsMsgDBFolder::kIsDeferred },
|
||||
{ "Keywords", &nsMsgDBFolder::kKeywords }
|
||||
{ "Keywords", &nsMsgDBFolder::kKeywords },
|
||||
{ "FiltersApplied", &nsMsgDBFolder::mFiltersAppliedAtom }
|
||||
};
|
||||
|
||||
nsMsgDBFolder::nsMsgDBFolder(void)
|
||||
|
@ -1703,7 +1705,8 @@ nsresult nsMsgDBFolder::CompactOfflineStore(nsIMsgWindow *inWindow, nsIUrlListen
|
|||
nsresult
|
||||
nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aWindow);
|
||||
// we don't check for null aWindow, because this routine can get called
|
||||
// in unit tests where we have no window. Just assume not OK if no window.
|
||||
PRBool prompt;
|
||||
nsresult rv = GetPromptPurgeThreshold(&prompt);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
@ -1795,7 +1798,7 @@ nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow)
|
|||
|
||||
PRBool askBeforePurge;
|
||||
branch->GetBoolPref(PREF_MAIL_PURGE_ASK, &askBeforePurge);
|
||||
if (askBeforePurge)
|
||||
if (askBeforePurge && aWindow)
|
||||
{
|
||||
nsCOMPtr <nsIStringBundle> bundle;
|
||||
rv = GetBaseStringBundle(getter_AddRefs(bundle));
|
||||
|
@ -1836,7 +1839,7 @@ nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow)
|
|||
}
|
||||
}
|
||||
else
|
||||
okToCompact = PR_TRUE;
|
||||
okToCompact = aWindow || !askBeforePurge;
|
||||
|
||||
if (okToCompact)
|
||||
{
|
||||
|
@ -2317,17 +2320,11 @@ nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow *aMsgWindow, PRBool *aFiltersRun)
|
|||
// if it's a news folder, then we really don't support junk in the ui
|
||||
// yet the legacy spamLevel seems to think we should analyze it.
|
||||
// Maybe we should upgrade that, but for now let's not analyze. We'll
|
||||
// use the preference "mail.filter_news_for_junk", and/or
|
||||
// let an extension set an inherited property if they really want us to
|
||||
// analyze this. We need that anyway to allow extension-based overrides.
|
||||
// When we finalize adding junk in news to core, we'll deal with the
|
||||
// spamLevel issue
|
||||
|
||||
PRBool filterNewsForJunk = PR_FALSE;
|
||||
nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
|
||||
if (prefBranch)
|
||||
prefBranch->GetBoolPref("mail.filter_news_for_junk", &filterNewsForJunk);
|
||||
|
||||
// if this is the junk folder, or the trash folder
|
||||
// don't analyze for spam, because we don't care
|
||||
//
|
||||
|
@ -2342,11 +2339,10 @@ nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow *aMsgWindow, PRBool *aFiltersRun)
|
|||
|
||||
PRBool filterForJunk = PR_TRUE;
|
||||
if (serverType.EqualsLiteral("rss") ||
|
||||
(mFlags & nsMsgFolderFlags::Newsgroup && !filterNewsForJunk) ||
|
||||
(mFlags & (nsMsgFolderFlags::Junk | nsMsgFolderFlags::Trash |
|
||||
nsMsgFolderFlags::SentMail | nsMsgFolderFlags::Queue |
|
||||
nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates |
|
||||
nsMsgFolderFlags::ImapPublic |
|
||||
nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::Newsgroup |
|
||||
nsMsgFolderFlags::ImapOtherUser) &&
|
||||
!(mFlags & nsMsgFolderFlags::Inbox)))
|
||||
filterForJunk = PR_FALSE;
|
||||
|
|
|
@ -198,6 +198,7 @@ protected:
|
|||
static NS_MSG_BASE_STATIC_MEMBER_(nsIAtom*) mDeleteOrMoveMsgCompletedAtom;
|
||||
static NS_MSG_BASE_STATIC_MEMBER_(nsIAtom*) mDeleteOrMoveMsgFailedAtom;
|
||||
static NS_MSG_BASE_STATIC_MEMBER_(nsIAtom*) mJunkStatusChangedAtom;
|
||||
static NS_MSG_BASE_STATIC_MEMBER_(nsIAtom*) mFiltersAppliedAtom;
|
||||
static NS_MSG_BASE_STATIC_MEMBER_(nsrefcnt) mInstanceCount;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -279,6 +279,14 @@ nsresult nsMsgProtocol::OpenFileSocket(nsIURI * aURL, PRUint32 aStartPosition, P
|
|||
return rv;
|
||||
}
|
||||
|
||||
nsresult nsMsgProtocol::GetTopmostMsgWindow(nsIMsgWindow **aWindow)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIMsgMailSession> mailSession(do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return mailSession->GetTopmostMsgWindow(aWindow);
|
||||
}
|
||||
|
||||
nsresult nsMsgProtocol::SetupTransportState()
|
||||
{
|
||||
if (!m_socketIsOpen && m_transport)
|
||||
|
|
|
@ -54,6 +54,8 @@
|
|||
#include "nsIAsyncOutputStream.h"
|
||||
#include "nsIAuthModule.h"
|
||||
|
||||
class nsIMsgWindow;
|
||||
|
||||
#define UNKNOWN_ERROR 101
|
||||
#define UNKNOWN_HOST_ERROR 102
|
||||
#define CONNECTION_REFUSED_ERROR 103
|
||||
|
@ -123,6 +125,8 @@ protected:
|
|||
nsresult GetFileFromURL(nsIURI * aURL, nsIFile **aResult);
|
||||
virtual nsresult OpenFileSocket(nsIURI * aURL, PRUint32 aStartPosition, PRInt32 aReadCount); // used to open a file socket connection
|
||||
|
||||
nsresult GetTopmostMsgWindow(nsIMsgWindow **aWindow);
|
||||
|
||||
// a Protocol typically overrides this method. They free any of their own connection state and then
|
||||
// they call up into the base class to free the generic connection objects
|
||||
virtual nsresult CloseSocket();
|
||||
|
|
|
@ -449,7 +449,8 @@ GlodaMessage.prototype = {
|
|||
_clone: function gloda_message_clone() {
|
||||
return new GlodaMessage(this._datastore, this._id, this._folderID,
|
||||
this._messageKey, this._conversationID, this._conversation, this._date,
|
||||
this._headerMessageID, this._deleted);
|
||||
this._headerMessageID, this._deleted, this._jsonText, this._notability,
|
||||
this._subject, this._indexedBodyText, this._attachmentNames);
|
||||
},
|
||||
|
||||
_ghost: function gloda_message_ghost() {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Andrew Sutherland <asutherland@asutherland.org>
|
||||
* Siddharth Agarwal <sid.bugzilla@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -53,52 +54,6 @@ Cu.import("resource://app/modules/gloda/datamodel.js");
|
|||
Cu.import("resource://app/modules/gloda/databind.js");
|
||||
Cu.import("resource://app/modules/gloda/collection.js");
|
||||
|
||||
let MBM_LOG = Log4Moz.repository.getLogger("gloda.ds.mbm");
|
||||
|
||||
/**
|
||||
* @class This callback handles processing the asynchronous query results of
|
||||
* GlodaDatastore.getMessagesByMessageID. Because that method is only
|
||||
* called as part of the indexing process, we are guaranteed that there will
|
||||
* be no real caching ramifications. Accordingly, we can also defer our cache
|
||||
* processing (via GlodaCollectionManager) until the query completes.
|
||||
*
|
||||
* @param aMsgIDToIndex Map from message-id to the desired
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MessagesByMessageIdCallback(aMsgIDToIndex, aResults,
|
||||
aCallback, aCallbackThis) {
|
||||
this.msgIDToIndex = aMsgIDToIndex;
|
||||
this.results = aResults;
|
||||
this.callback = aCallback;
|
||||
this.callbackThis = aCallbackThis;
|
||||
}
|
||||
|
||||
MessagesByMessageIdCallback.prototype = {
|
||||
onItemsAdded: function gloda_ds_mbmi_onItemsAdded(aItems, aCollection) {
|
||||
// just outright bail if we are shutdown
|
||||
if (GlodaDatastore.datastoreIsShutdown)
|
||||
return;
|
||||
|
||||
MBM_LOG.debug("getting results...");
|
||||
for each (let [, message] in Iterator(aItems)) {
|
||||
this.results[this.msgIDToIndex[message.headerMessageID]].push(message);
|
||||
}
|
||||
},
|
||||
onItemsModified: function () {},
|
||||
onItemsRemoved: function () {},
|
||||
onQueryCompleted: function gloda_ds_mbmi_onQueryCompleted(aCollection) {
|
||||
// just outright bail if we are shutdown
|
||||
if (GlodaDatastore.datastoreIsShutdown)
|
||||
return;
|
||||
|
||||
MBM_LOG.debug("query completed, notifying... " + this.results);
|
||||
// we no longer need to unify; it is done for us.
|
||||
|
||||
this.callback.call(this.callbackThis, this.results);
|
||||
}
|
||||
};
|
||||
|
||||
let PCH_LOG = Log4Moz.repository.getLogger("gloda.ds.pch");
|
||||
|
||||
function PostCommitHandler(aCallbacks) {
|
||||
|
@ -1888,23 +1843,36 @@ var GlodaDatastore = {
|
|||
this.asyncConnection.lastErrorString + " - " + ex);
|
||||
}
|
||||
|
||||
// we only create the full-text row if the body is non-null.
|
||||
// so, even though body might be null, we still want to create the
|
||||
// full-text search row
|
||||
if (aMessage._bodyLines) {
|
||||
// we create the full-text row for any message that isn't a ghost,
|
||||
// whether we have the body or not
|
||||
if (aMessage.folderID !== null)
|
||||
this._insertMessageText(aMessage);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts a full-text row. This should only be called if you're sure you want
|
||||
* to insert a row into the table.
|
||||
*/
|
||||
_insertMessageText: function gloda_ds__insertMessageText(aMessage) {
|
||||
if (aMessage._content && aMessage._content.hasContent())
|
||||
aMessage._indexedBodyText = aMessage._content.getContentString(true);
|
||||
else
|
||||
else if (aMessage._bodyLines)
|
||||
aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
|
||||
else
|
||||
aMessage._indexedBodyText = null;
|
||||
|
||||
let imts = this._insertMessageTextStatement;
|
||||
imts.bindInt64Parameter(0, aMessage.id);
|
||||
imts.bindStringParameter(1, aMessage._subject);
|
||||
if (aMessage._indexedBodyText == null)
|
||||
imts.bindNullParameter(2);
|
||||
else
|
||||
imts.bindStringParameter(2, aMessage._indexedBodyText);
|
||||
if (aMessage._attachmentNames === null)
|
||||
imts.bindNullParameter(3);
|
||||
else
|
||||
imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
|
||||
|
||||
imts.bindStringParameter(4, aMessage._indexAuthor);
|
||||
imts.bindStringParameter(5, aMessage._indexRecipients);
|
||||
|
||||
|
@ -1916,7 +1884,6 @@ var GlodaDatastore = {
|
|||
this.asyncConnection.lastError + ": " +
|
||||
this.asyncConnection.lastErrorString + " - " + ex);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get _updateMessageStatement() {
|
||||
|
@ -1933,10 +1900,24 @@ var GlodaDatastore = {
|
|||
return this._updateMessageStatement;
|
||||
},
|
||||
|
||||
get _updateMessageTextStatement() {
|
||||
let statement = this._createAsyncStatement(
|
||||
"UPDATE messagesText SET body = ?1, \
|
||||
attachmentNames = ?2 \
|
||||
WHERE docid = ?3");
|
||||
|
||||
this.__defineGetter__("_updateMessageTextStatement", function() statement);
|
||||
return this._updateMessageTextStatement;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the database row associated with the message. If aBody is supplied,
|
||||
* the associated full-text row is created; it is assumed that it did not
|
||||
* previously exist.
|
||||
* Update the database row associated with the message. If the message is
|
||||
* not a ghost and has _isNew defined, messagesText is affected.
|
||||
*
|
||||
* aMessage._isNew is currently equivalent to the fact that there is no
|
||||
* full-text row associated with this message, and we work with this
|
||||
* assumption here. Note that if aMessage._isNew is not defined, then
|
||||
* we don't do anything.
|
||||
*/
|
||||
updateMessage: function gloda_ds_updateMessage(aMessage) {
|
||||
let ums = this._updateMessageStatement;
|
||||
|
@ -1963,31 +1944,11 @@ var GlodaDatastore = {
|
|||
|
||||
ums.executeAsync(this.trackAsync());
|
||||
|
||||
if (aMessage._isNew && aMessage._bodyLines) {
|
||||
if (aMessage._content && aMessage._content.hasContent())
|
||||
aMessage._indexedBodyText = aMessage._content.getContentString(true);
|
||||
if (aMessage.folderID !== null) {
|
||||
if (aMessage._isNew === true)
|
||||
this._insertMessageText(aMessage);
|
||||
else
|
||||
aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
|
||||
|
||||
let imts = this._insertMessageTextStatement;
|
||||
imts.bindInt64Parameter(0, aMessage.id);
|
||||
imts.bindStringParameter(1, aMessage._subject);
|
||||
imts.bindStringParameter(2, aMessage._indexedBodyText);
|
||||
if (aMessage._attachmentNames === null)
|
||||
imts.bindNullParameter(3);
|
||||
else
|
||||
imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
|
||||
imts.bindStringParameter(4, aMessage._indexAuthor);
|
||||
imts.bindStringParameter(5, aMessage._indexRecipients);
|
||||
|
||||
try {
|
||||
imts.executeAsync(this.trackAsync());
|
||||
}
|
||||
catch(ex) {
|
||||
throw("error executing fulltext statement... " +
|
||||
this.asyncConnection.lastError + ": " +
|
||||
this.asyncConnection.lastErrorString + " - " + ex);
|
||||
}
|
||||
this._updateMessageText(aMessage);
|
||||
}
|
||||
|
||||
// In completely abstract theory, this is where we would call
|
||||
|
@ -1996,6 +1957,51 @@ var GlodaDatastore = {
|
|||
// handles it.)
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the full-text row associated with this message. This only performs
|
||||
* the UPDATE query if the indexed body text has changed, which means that if
|
||||
* the body hasn't changed but the attachments have, we don't update.
|
||||
*/
|
||||
_updateMessageText: function gloda_ds__updateMessageText(aMessage) {
|
||||
let newIndexedBodyText;
|
||||
if (aMessage._content && aMessage._content.hasContent())
|
||||
newIndexedBodyText = aMessage._content.getContentString(true);
|
||||
else if (aMessage._bodyLines)
|
||||
newIndexedBodyText = aMessage._bodyLines.join("\n");
|
||||
else
|
||||
newIndexedBodyText = null;
|
||||
|
||||
// If the body text matches, don't perform an update
|
||||
if (newIndexedBodyText == aMessage._indexedBodyText) {
|
||||
this._log.debug("in _updateMessageText, skipping update because body matches");
|
||||
return;
|
||||
}
|
||||
|
||||
aMessage._indexedBodyText = newIndexedBodyText;
|
||||
|
||||
let umts = this._updateMessageTextStatement;
|
||||
umts.bindInt64Parameter(2, aMessage.id);
|
||||
|
||||
if (aMessage._indexedBodyText == null)
|
||||
umts.bindNullParameter(0);
|
||||
else
|
||||
umts.bindStringParameter(0, aMessage._indexedBodyText);
|
||||
|
||||
if (aMessage._attachmentNames == null)
|
||||
umts.bindNullParameter(1);
|
||||
else
|
||||
umts.bindStringParameter(1, aMessage._attachmentNames.join("\n"));
|
||||
|
||||
try {
|
||||
umts.executeAsync(this.trackAsync());
|
||||
}
|
||||
catch(ex) {
|
||||
throw("error executing fulltext statement... " +
|
||||
this.asyncConnection.lastError + ": " +
|
||||
this.asyncConnection.lastErrorString + " - " + ex);
|
||||
}
|
||||
},
|
||||
|
||||
get _updateMessageLocationStatement() {
|
||||
let statement = this._createAsyncStatement(
|
||||
"UPDATE messages SET folderID = ?1, messageKey = ?2 WHERE id = ?3");
|
||||
|
@ -2212,49 +2218,6 @@ var GlodaDatastore = {
|
|||
return messageIDs;
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a list of Message-ID's, return a matching list of lists of messages
|
||||
* matching those Message-ID's. So if you pass an array with three
|
||||
* Message-ID's ["a", "b", "c"], you would get back an array containing
|
||||
* 3 lists, where the first list contains all the messages with a message-id
|
||||
* of "a", and so forth. The reason a list is returned rather than null/a
|
||||
* message is that we accept the reality that we have multiple copies of
|
||||
* messages with the same ID.
|
||||
* This call is asynchronous because it depends on previously created messages
|
||||
* to be reflected in our results, which requires us to execute on the async
|
||||
* thread where all our writes happen. This also turns out to be a
|
||||
* reasonable thing because we could imagine pathological cases where there
|
||||
* could be a lot of message-id's and/or a lot of messages with those
|
||||
* message-id's.
|
||||
*/
|
||||
getMessagesByMessageID: function gloda_ds_getMessagesByMessageID(aMessageIDs,
|
||||
aCallback, aCallbackThis) {
|
||||
let msgIDToIndex = {};
|
||||
let results = [];
|
||||
for (let iID = 0; iID < aMessageIDs.length; ++iID) {
|
||||
let msgID = aMessageIDs[iID];
|
||||
results.push([]);
|
||||
msgIDToIndex[msgID] = iID;
|
||||
}
|
||||
|
||||
// Unfortunately, IN doesn't work with statement binding mechanisms, and
|
||||
// a chain of ORed tests really can't be bound unless we create one per
|
||||
// value of N (seems silly).
|
||||
let quotedIDs = ["'" + msgID.replace("'", "''", "g") + "'" for each
|
||||
([i, msgID] in Iterator(aMessageIDs))]
|
||||
let sqlString = "SELECT * FROM messages WHERE headerMessageID IN (" +
|
||||
quotedIDs + ")";
|
||||
|
||||
let nounDef = GlodaMessage.prototype.NOUN_DEF;
|
||||
let listener = new MessagesByMessageIdCallback(msgIDToIndex, results,
|
||||
aCallback, aCallbackThis);
|
||||
// Use a null query because we don't want any update notifications about our
|
||||
// collection. They would just confuse and anger the listener.
|
||||
let query = new nounDef.nullQueryClass();
|
||||
return this._queryFromSQLString(sqlString, [], nounDef,
|
||||
query, listener);
|
||||
},
|
||||
|
||||
get _updateMessagesMarkDeletedByFolderID() {
|
||||
let statement = this._createAsyncStatement(
|
||||
"UPDATE messages SET folderID = NULL, messageKey = NULL, \
|
||||
|
@ -2763,8 +2726,12 @@ var GlodaDatastore = {
|
|||
// return. For example, in the case of messages, deleted or ghost
|
||||
// messages should not be returned by this query layer. We require
|
||||
// hand-rolled SQL to do that for now.
|
||||
let validityConstraintSuffix =
|
||||
nounDef.dbQueryValidityConstraintSuffix || "";
|
||||
let validityConstraintSuffix;
|
||||
if (nounDef.dbQueryValidityConstraintSuffix &&
|
||||
!aQuery.options.noDbQueryValidityConstraints)
|
||||
validityConstraintSuffix = nounDef.dbQueryValidityConstraintSuffix;
|
||||
else
|
||||
validityConstraintSuffix = "";
|
||||
|
||||
for (let iUnion = 0; iUnion < unionQueries.length; iUnion++) {
|
||||
let curQuery = unionQueries[iUnion];
|
||||
|
@ -2912,8 +2879,14 @@ var GlodaDatastore = {
|
|||
}
|
||||
|
||||
let sqlString = "SELECT * FROM " + nounDef.tableName;
|
||||
if (nounDef.dbQueryJoinMagic && !aQuery.options.noMagic)
|
||||
if (!aQuery.options.noMagic) {
|
||||
if (aQuery.options.noDbQueryValidityConstraints &&
|
||||
nounDef.dbQueryJoinMagicWithNoValidityConstraints)
|
||||
sqlString += nounDef.dbQueryJoinMagicWithNoValidityConstraints;
|
||||
else if (nounDef.dbQueryJoinMagic)
|
||||
sqlString += nounDef.dbQueryJoinMagic;
|
||||
}
|
||||
|
||||
if (whereClauses.length)
|
||||
sqlString += " WHERE (" + whereClauses.join(") OR (") + ")";
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ Cu.import("resource://app/modules/gloda/gloda.js");
|
|||
Cu.import("resource://app/modules/gloda/datastore.js");
|
||||
|
||||
Cu.import("resource://app/modules/gloda/noun_mimetype.js");
|
||||
|
||||
Cu.import("resource://app/modules/gloda/connotent.js");
|
||||
|
||||
/**
|
||||
* @namespace The Gloda Fundamental Attribute provider is a special attribute
|
||||
|
@ -100,6 +100,7 @@ var GlodaFundAttr = {
|
|||
_attrCc: null,
|
||||
_attrCcMe: null,
|
||||
_attrDate: null,
|
||||
_attrHeaderMessageID: null,
|
||||
|
||||
defineAttributes: function() {
|
||||
/* ***** Conversations ***** */
|
||||
|
@ -286,6 +287,19 @@ var GlodaFundAttr = {
|
|||
objectNoun: Gloda.NOUN_DATE,
|
||||
}); // tested-by: test_attributes_fundamental
|
||||
|
||||
// Header message ID.
|
||||
this._attrHeaderMessageID = Gloda.defineAttribute({
|
||||
provider: this,
|
||||
extensionName: Gloda.BUILT_IN,
|
||||
attributeType: Gloda.kAttrFundamental,
|
||||
attributeName: "headerMessageID",
|
||||
singular: true,
|
||||
special: Gloda.kSpecialColumn,
|
||||
specialColumnName: "headerMessageID",
|
||||
subjectNouns: [Gloda.NOUN_MESSAGE],
|
||||
objectNoun: Gloda.NOUN_STRING,
|
||||
}); // tested-by: test_attributes_fundamental
|
||||
|
||||
// Attachment MIME Types
|
||||
this._attrAttachmentTypes = Gloda.defineAttribute({
|
||||
provider: this,
|
||||
|
@ -432,6 +446,7 @@ var GlodaFundAttr = {
|
|||
aGlodaMessage.cc = ccIdentities;
|
||||
|
||||
// -- Attachments
|
||||
if (aMimeMsg) {
|
||||
let attachmentTypes = [];
|
||||
for each (let [, attachment] in Iterator(aMimeMsg.allAttachments)) {
|
||||
// We don't care about would-be attachments that are not user-intended
|
||||
|
@ -446,6 +461,7 @@ var GlodaFundAttr = {
|
|||
if (attachmentTypes.length) {
|
||||
aGlodaMessage.attachmentTypes = attachmentTypes;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: deal with mailing lists, including implicit-to. this will require
|
||||
// convincing the indexer to pass us in the previous message if it is
|
||||
|
@ -565,10 +581,13 @@ var GlodaFundAttr = {
|
|||
if (fromMeCc.length)
|
||||
aGlodaMessage.fromMeCc = fromMeCc;
|
||||
|
||||
if (aRawReps.bodyLines &&
|
||||
this.contentWhittle({}, aRawReps.bodyLines, aRawReps.content)) {
|
||||
// Content
|
||||
if (aRawReps.bodyLines) {
|
||||
aGlodaMessage._content = new GlodaContent();
|
||||
if (this.contentWhittle({}, aRawReps.bodyLines, aGlodaMessage._content)) {
|
||||
// we were going to do something here?
|
||||
}
|
||||
}
|
||||
|
||||
yield Gloda.kWorkDone;
|
||||
},
|
||||
|
|
|
@ -54,6 +54,49 @@ Cu.import("resource://app/modules/gloda/utils.js");
|
|||
|
||||
Cu.import("resource://app/modules/iteratorUtils.jsm");
|
||||
|
||||
let MBM_LOG = Log4Moz.repository.getLogger("gloda.NS.mbm");
|
||||
|
||||
/**
|
||||
* @class This callback handles processing the asynchronous query results of
|
||||
* Gloda.getMessagesByMessageID.
|
||||
*
|
||||
* @param aMsgIDToIndex Map from message-id to the desired
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MessagesByMessageIdCallback(aMsgIDToIndex, aResults,
|
||||
aCallback, aCallbackThis) {
|
||||
this.msgIDToIndex = aMsgIDToIndex;
|
||||
this.results = aResults;
|
||||
this.callback = aCallback;
|
||||
this.callbackThis = aCallbackThis;
|
||||
}
|
||||
|
||||
MessagesByMessageIdCallback.prototype = {
|
||||
onItemsAdded: function gloda_ds_mbmi_onItemsAdded(aItems, aCollection) {
|
||||
// just outright bail if we are shutdown
|
||||
if (GlodaDatastore.datastoreIsShutdown)
|
||||
return;
|
||||
|
||||
MBM_LOG.debug("getting results...");
|
||||
for each (let [, message] in Iterator(aItems)) {
|
||||
this.results[this.msgIDToIndex[message.headerMessageID]].push(message);
|
||||
}
|
||||
},
|
||||
onItemsModified: function () {},
|
||||
onItemsRemoved: function () {},
|
||||
onQueryCompleted: function gloda_ds_mbmi_onQueryCompleted(aCollection) {
|
||||
// just outright bail if we are shutdown
|
||||
if (GlodaDatastore.datastoreIsShutdown)
|
||||
return;
|
||||
|
||||
MBM_LOG.debug("query completed, notifying... " + this.results);
|
||||
// we no longer need to unify; it is done for us.
|
||||
|
||||
this.callback.call(this.callbackThis, this.results);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides the user-visible (and extension visible) global database
|
||||
* functionality. There is currently a dependency/ordering
|
||||
|
@ -326,6 +369,45 @@ var Gloda = {
|
|||
return query.getCollection(aListener, aData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a list of Message-ID's, return a matching list of lists of messages
|
||||
* matching those Message-ID's. So if you pass an array with three
|
||||
* Message-ID's ["a", "b", "c"], you would get back an array containing
|
||||
* 3 lists, where the first list contains all the messages with a message-id
|
||||
* of "a", and so forth. The reason a list is returned rather than null/a
|
||||
* message is that we accept the reality that we have multiple copies of
|
||||
* messages with the same ID.
|
||||
* This call is asynchronous because it depends on previously created messages
|
||||
* to be reflected in our results, which requires us to execute on the async
|
||||
* thread where all our writes happen. This also turns out to be a
|
||||
* reasonable thing because we could imagine pathological cases where there
|
||||
* could be a lot of message-id's and/or a lot of messages with those
|
||||
* message-id's.
|
||||
*/
|
||||
getMessagesByMessageID: function gloda_ns_getMessagesByMessageID(aMessageIDs,
|
||||
aCallback, aCallbackThis) {
|
||||
let msgIDToIndex = {};
|
||||
let results = [];
|
||||
for (let iID = 0; iID < aMessageIDs.length; ++iID) {
|
||||
let msgID = aMessageIDs[iID];
|
||||
results.push([]);
|
||||
msgIDToIndex[msgID] = iID;
|
||||
}
|
||||
|
||||
let quotedIDs = ["'" + msgID.replace("'", "''", "g") + "'" for each
|
||||
([i, msgID] in Iterator(aMessageIDs))];
|
||||
|
||||
let query = Gloda.newQuery(Gloda.NOUN_MESSAGE, {
|
||||
noDbQueryValidityConstraints: true,
|
||||
});
|
||||
query.headerMessageID.apply(query, quotedIDs);
|
||||
query.frozen = true;
|
||||
|
||||
let listener = new MessagesByMessageIdCallback(msgIDToIndex, results,
|
||||
aCallback, aCallbackThis);
|
||||
return query.getCollection(listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* @testpoint gloda.ns.getMessageContent
|
||||
*/
|
||||
|
@ -1077,6 +1159,10 @@ var Gloda = {
|
|||
dbAttribAdjuster: GlodaDatastore.adjustMessageAttributes,
|
||||
dbQueryValidityConstraintSuffix:
|
||||
" AND +deleted = 0 AND +folderID IS NOT NULL AND +messageKey IS NOT NULL",
|
||||
// This is what's used when we have no validity constraints, i.e. we allow
|
||||
// for ghost messages, which do not have a row in the messagesText table.
|
||||
dbQueryJoinMagicWithNoValidityConstraints:
|
||||
" LEFT JOIN messagesText ON messages.id = messagesText.rowid",
|
||||
objInsert: GlodaDatastore.insertMessage,
|
||||
objUpdate: GlodaDatastore.updateMessage,
|
||||
toParamAndValue: function(aMessage) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* Contributor(s):
|
||||
* Andrew Sutherland <asutherland@asutherland.org>
|
||||
* Kent James <kent@caspia.com>
|
||||
* Siddharth Agarwal <sid.bugzilla@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -243,7 +244,8 @@ IndexingJob.prototype = {
|
|||
* meta-data.
|
||||
*
|
||||
* Indexing Message Control
|
||||
* - We index IMAP messages that are offline. We index all local messages.
|
||||
* - We index the headers of all IMAP messages. We index the bodies of all IMAP
|
||||
* messages that are offline. We index all local messages.
|
||||
* We plan to avoid indexing news messages.
|
||||
* - We would like a way to express desires about indexing that either don't
|
||||
* confound offline storage with indexing, or actually allow some choice.
|
||||
|
@ -1061,8 +1063,8 @@ var GlodaIndexer = {
|
|||
// The basic search expression is:
|
||||
// ((GLODA_MESSAGE_ID_PROPERTY Is 0) || (GLODA_DIRTY_PROPERTY Isnt 0))
|
||||
// If the folder !isLocal we add the terms:
|
||||
// && (Status Is nsMsgMessageFlags.Offline)
|
||||
// && (Status Isnt nsMsgMessageFlags.Expunged)
|
||||
// - if the folder is offline -- && (Status Is nsMsgMessageFlags.Offline)
|
||||
// - && (Status Isnt nsMsgMessageFlags.Expunged)
|
||||
|
||||
let searchSession = Cc["@mozilla.org/messenger/searchSession;1"]
|
||||
.createInstance(Ci.nsIMsgSearchSession);
|
||||
|
@ -1081,7 +1083,7 @@ var GlodaIndexer = {
|
|||
searchTerm.beginsGrouping = true;
|
||||
searchTerm.attrib = nsMsgSearchAttrib.Uint32HdrProperty;
|
||||
searchTerm.op = nsMsgSearchOp.Is;
|
||||
value = searchTerm.value;
|
||||
let value = searchTerm.value;
|
||||
value.attrib = searchTerm.attrib;
|
||||
value.status = 0;
|
||||
searchTerm.value = value;
|
||||
|
@ -1103,6 +1105,8 @@ var GlodaIndexer = {
|
|||
|
||||
if (!isLocal)
|
||||
{
|
||||
// If the folder is offline, then the message should be too
|
||||
if (this._indexingFolder.flags & Ci.nsMsgFolderFlags.Offline) {
|
||||
// third term: && Status Is nsMsgMessageFlags.Offline
|
||||
searchTerm = searchSession.createTerm();
|
||||
searchTerm.booleanAnd = true;
|
||||
|
@ -1113,6 +1117,7 @@ var GlodaIndexer = {
|
|||
value.status = Ci.nsMsgMessageFlags.Offline;
|
||||
searchTerm.value = value;
|
||||
searchTerms.appendElement(searchTerm, false);
|
||||
}
|
||||
|
||||
// fourth term: && Status Isnt nsMsgMessageFlags.Expunged
|
||||
searchTerm = searchSession.createTerm();
|
||||
|
@ -1652,10 +1657,10 @@ var GlodaIndexer = {
|
|||
Ci.nsIMsgFolder);
|
||||
if (!this.shouldIndexFolder(folder))
|
||||
continue;
|
||||
// we could also check nsMsgFolderFlags.Mail conceivably...
|
||||
let isLocal = folder instanceof Ci.nsIMsgLocalMailFolder;
|
||||
// we only index local folders or IMAP folders that are marked offline
|
||||
if (!isLocal && !(folder.flags & Ci.nsMsgFolderFlags.Offline))
|
||||
|
||||
// we only index local or IMAP folders
|
||||
if (!(folder instanceof Ci.nsIMsgLocalMailFolder) &&
|
||||
!(folder instanceof Ci.nsIMsgImapMailFolder))
|
||||
continue;
|
||||
|
||||
let glodaFolder = Gloda.getFolderForFolder(folder);
|
||||
|
@ -1734,7 +1739,6 @@ var GlodaIndexer = {
|
|||
// don't do something. so we will yield with kWorkSync for every block.
|
||||
const HEADER_CHECK_BLOCK_SIZE = 25;
|
||||
|
||||
let isLocal = this._indexingFolder instanceof Ci.nsIMsgLocalMailFolder;
|
||||
// we can safely presume if we are here that this folder has been selected
|
||||
// for offline processing...
|
||||
|
||||
|
@ -1835,8 +1839,6 @@ var GlodaIndexer = {
|
|||
// if we are already in the correct folder, our "get in the folder" clause
|
||||
// will not execute, so we need to make sure this value is accurate in
|
||||
// that case. (and we want to avoid multiple checks...)
|
||||
let folderIsLocal =
|
||||
this._indexingFolder instanceof Ci.nsIMsgLocalMailFolder;
|
||||
for (; aJob.offset < aJob.items.length; aJob.offset++) {
|
||||
let item = aJob.items[aJob.offset];
|
||||
// item is either [folder ID, message key] or
|
||||
|
@ -1850,9 +1852,6 @@ var GlodaIndexer = {
|
|||
// stay out of folders we should not be in!
|
||||
if (!this.shouldIndexFolder(this._indexingFolder))
|
||||
continue;
|
||||
|
||||
folderIsLocal =
|
||||
this._indexingFolder instanceof Ci.nsIMsgLocalMailFolder;
|
||||
}
|
||||
|
||||
let msgHdr;
|
||||
|
@ -1863,12 +1862,9 @@ var GlodaIndexer = {
|
|||
// TODO fixme to not assume singular message-id's.
|
||||
msgHdr = this._indexingDatabase.getMsgHdrForMessageID(item[1]);
|
||||
|
||||
// it needs a header, the header needs to not be expunged, plus, the
|
||||
// message needs to be considered offline.
|
||||
// it needs a header, and the header needs to not be expunged.
|
||||
if (msgHdr &&
|
||||
!(msgHdr.flags & Components.interfaces.nsMsgMessageFlags.Expunged) &&
|
||||
(folderIsLocal ||
|
||||
(msgHdr.flags & Components.interfaces.nsMsgMessageFlags.Offline)))
|
||||
!(msgHdr.flags & Components.interfaces.nsMsgMessageFlags.Expunged))
|
||||
yield this._callbackHandle.pushAndGo(this._indexMessage(msgHdr,
|
||||
this._callbackHandle));
|
||||
else
|
||||
|
@ -2069,9 +2065,16 @@ var GlodaIndexer = {
|
|||
let msgFolder = aMsgHdr.folder;
|
||||
if (!this.indexer.shouldIndexFolder(msgFolder))
|
||||
return;
|
||||
|
||||
// Make sure the message is eligible for indexing.
|
||||
// We index local messages, IMAP messages that are offline, and IMAP
|
||||
// messages that aren't offline but whose folders aren't offline either
|
||||
let isFolderLocal = msgFolder instanceof Ci.nsIMsgLocalMailFolder;
|
||||
if (!isFolderLocal && !(msgFolder.flags&Ci.nsMsgFolderFlags.Offline))
|
||||
if (!isFolderLocal) {
|
||||
if (!(aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) &&
|
||||
(msgFolder.flags & Ci.nsMsgFolderFlags.Offline))
|
||||
return;
|
||||
}
|
||||
|
||||
// mark the folder dirty so we know to look in it, but there is no need
|
||||
// to mark the message because it will lack a gloda-id anyways.
|
||||
|
@ -2379,9 +2382,16 @@ var GlodaIndexer = {
|
|||
let msgFolder = aMsgHdr.folder;
|
||||
if (!this.indexer.shouldIndexFolder(msgFolder))
|
||||
return;
|
||||
|
||||
// Make sure the message is eligible for indexing.
|
||||
// We index local messages, IMAP messages that are offline, and IMAP
|
||||
// messages that aren't offline but whose folders aren't offline either
|
||||
let isFolderLocal = msgFolder instanceof Ci.nsIMsgLocalMailFolder;
|
||||
if (!isFolderLocal && !(msgFolder.flags&Ci.nsMsgFolderFlags.Offline))
|
||||
if (!isFolderLocal) {
|
||||
if (!(aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) &&
|
||||
(msgFolder.flags & Ci.nsMsgFolderFlags.Offline))
|
||||
return;
|
||||
}
|
||||
|
||||
// mark the message as dirty
|
||||
// (We could check for the presence of the gloda message id property
|
||||
|
@ -2399,13 +2409,15 @@ var GlodaIndexer = {
|
|||
}
|
||||
// only queue the message if we haven't overflowed our event-driven budget
|
||||
if (this.indexer._pendingAddJob.items.length <
|
||||
this.indexer._indexMaxEventQueueMessages)
|
||||
this.indexer._indexMaxEventQueueMessages) {
|
||||
this.indexer._pendingAddJob.items.push(
|
||||
[GlodaDatastore._mapFolder(msgFolder).id,
|
||||
aMsgHdr.messageKey]);
|
||||
else
|
||||
this.indexer.indexingSweepNeeded = true;
|
||||
this.indexer.indexing = true;
|
||||
}
|
||||
else {
|
||||
this.indexer.indexingSweepNeeded = true;
|
||||
}
|
||||
},
|
||||
|
||||
OnItemAdded: function gloda_indexer_OnItemAdded(aParentItem, aItem) {
|
||||
|
@ -2485,13 +2497,27 @@ var GlodaIndexer = {
|
|||
|
||||
_indexMessage: function gloda_indexMessage(aMsgHdr, aCallbackHandle) {
|
||||
let logDebug = this._log.level <= Log4Moz.Level.Debug;
|
||||
|
||||
if (logDebug)
|
||||
this._log.debug("*** Indexing message: " + aMsgHdr.messageKey + " : " +
|
||||
aMsgHdr.subject);
|
||||
|
||||
// If the message is offline, then get the message body as well
|
||||
let isMsgOffline = false;
|
||||
let aMimeMsg;
|
||||
if ((aMsgHdr.flags & Ci.nsMsgMessageFlags.Offline) ||
|
||||
(aMsgHdr.folder instanceof Ci.nsIMsgLocalMailFolder)) {
|
||||
isMsgOffline = true;
|
||||
MsgHdrToMimeMessage(aMsgHdr, aCallbackHandle.callbackThis,
|
||||
aCallbackHandle.callback);
|
||||
let [,aMimeMsg] = yield this.kWorkAsync;
|
||||
[,aMimeMsg] = yield this.kWorkAsync;
|
||||
}
|
||||
else {
|
||||
if (logDebug)
|
||||
this._log.debug(" * Message is not offline -- only headers indexed");
|
||||
}
|
||||
|
||||
if (logDebug)
|
||||
this._log.debug(" * Got message, subject " + aMsgHdr.subject);
|
||||
|
||||
if (this._unitTestSuperVerbose) {
|
||||
if (aMimeMsg)
|
||||
|
@ -2511,7 +2537,7 @@ var GlodaIndexer = {
|
|||
// also see if we already know about the message...
|
||||
references.push(aMsgHdr.messageId);
|
||||
|
||||
this._datastore.getMessagesByMessageID(references, aCallbackHandle.callback,
|
||||
Gloda.getMessagesByMessageID(references, aCallbackHandle.callback,
|
||||
aCallbackHandle.callbackThis);
|
||||
// (ancestorLists has a direct correspondence to the message ids)
|
||||
let ancestorLists = yield this.kWorkAsync;
|
||||
|
@ -2668,7 +2694,7 @@ var GlodaIndexer = {
|
|||
let bodyPlain = aMimeMsg.coerceBodyToPlaintext(aMsgHdr.folder);
|
||||
if (bodyPlain) {
|
||||
curMsg._bodyLines = bodyPlain.split(/\r?\n/);
|
||||
curMsg._content = new GlodaContent();
|
||||
// curMsg._content gets set by fundattr.js
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2676,20 +2702,20 @@ var GlodaIndexer = {
|
|||
curMsg._isNew = true;
|
||||
// curMsg._indexedBodyText is set by GlodaDatastore.insertMessage or
|
||||
// GlodaDatastore.updateMessage
|
||||
}
|
||||
|
||||
curMsg._subject = aMsgHdr.mime2DecodedSubject;
|
||||
curMsg._attachmentNames = attachmentNames;
|
||||
|
||||
// curMsg._indexAuthor gets set by fundattr.js
|
||||
// curMsg._indexRecipients gets set by fundattr.js
|
||||
}
|
||||
|
||||
// zero the notability so everything in grokNounItem can just increment
|
||||
curMsg.notability = 0;
|
||||
|
||||
yield aCallbackHandle.pushAndGo(
|
||||
Gloda.grokNounItem(curMsg,
|
||||
{header: aMsgHdr, mime: aMimeMsg,
|
||||
bodyLines: curMsg._bodyLines, content: curMsg._content},
|
||||
{header: aMsgHdr, mime: aMimeMsg, bodyLines: curMsg._bodyLines},
|
||||
isConceptuallyNew, isRecordNew,
|
||||
aCallbackHandle));
|
||||
|
||||
|
|
|
@ -73,6 +73,9 @@ Cu.import("resource://app/modules/gloda/datastore.js");
|
|||
* this makes things more readable but is unlikely to improve
|
||||
* performance. (Namely, my use of 'offsets' for full-text stuff
|
||||
* ends up in the EXPLAIN plan twice despite this.)
|
||||
* - noDbQueryValidityConstraints: Indicates that any validity constraints
|
||||
* should be ignored. This should be used when you need to get every
|
||||
* match regardless of whether it's valid.
|
||||
*
|
||||
* @property _owner The query instance that holds the list of unions...
|
||||
* @property _constraints A list of (lists of OR constraints) that are ANDed
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
* Contributor(s):
|
||||
* Andrew Sutherland <asutherland@asutherland.org>
|
||||
* Siddharth Agarwal <sid.bugzilla@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -35,8 +36,9 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// -- Pull in the POP3 fake-server / local account helper code
|
||||
load("../../test_mailnewslocal/unit/head_maillocal.js");
|
||||
// Import the main scripts that mailnews tests need to set up and tear down
|
||||
load("../../mailnews/resources/mailDirService.js");
|
||||
load("../../mailnews/resources/mailTestUtils.js");
|
||||
|
||||
/**
|
||||
* Create a 'me' identity of "me@localhost" for the benefit of Gloda. At the
|
||||
|
@ -217,6 +219,8 @@ const INJECT_FAKE_SERVER = 1;
|
|||
const INJECT_MBOX = 2;
|
||||
/** Inject messages using addMessage. */
|
||||
const INJECT_ADDMESSAGE = 3;
|
||||
/** Inject messages using the IMAP fakeserver. */
|
||||
const INJECT_IMAP_FAKE_SERVER = 4;
|
||||
|
||||
/**
|
||||
* Convert a list of synthetic messages to a form appropriate to feed to the
|
||||
|
@ -227,25 +231,7 @@ function _synthMessagesToFakeRep(aSynthMessages) {
|
|||
(msg in aSynthMessages)];
|
||||
}
|
||||
|
||||
function imsInit() {
|
||||
let ims = indexMessageState;
|
||||
|
||||
if (!ims.inited) {
|
||||
// Disable new mail notifications
|
||||
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
|
||||
prefSvc.setBoolPref("mail.biff.play_sound", false);
|
||||
prefSvc.setBoolPref("mail.biff.show_alert", false);
|
||||
prefSvc.setBoolPref("mail.biff.show_tray_icon", false);
|
||||
prefSvc.setBoolPref("mail.biff.animate_dock_icon", false);
|
||||
|
||||
Gloda.addIndexerListener(messageIndexerListener.onIndexNotification);
|
||||
ims.catchAllCollection = Gloda._wildcardCollection(Gloda.NOUN_MESSAGE);
|
||||
ims.catchAllCollection.listener = messageCollectionListener;
|
||||
|
||||
// Make the indexer be more verbose about indexing for us...
|
||||
GlodaIndexer._unitTestSuperVerbose = true;
|
||||
function lobotomizeAdaptiveIndexer() {
|
||||
// The indexer doesn't need to worry about load; zero his rescheduling time.
|
||||
GlodaIndexer._INDEX_INTERVAL = 0;
|
||||
|
||||
|
@ -267,8 +253,34 @@ function imsInit() {
|
|||
GlodaIndexer._CPU_TARGET_INDEX_TIME_IDLE = 10000;
|
||||
GlodaIndexer._CPU_IS_BUSY_TIME = 10000;
|
||||
GlodaIndexer._PAUSE_LATE_IS_BUSY_TIME = 10000;
|
||||
}
|
||||
|
||||
function imsInit() {
|
||||
dump("Initializing index message state\n");
|
||||
let ims = indexMessageState;
|
||||
|
||||
if (!ims.inited) {
|
||||
// Disable new mail notifications
|
||||
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
|
||||
prefSvc.setBoolPref("mail.biff.play_sound", false);
|
||||
prefSvc.setBoolPref("mail.biff.show_alert", false);
|
||||
prefSvc.setBoolPref("mail.biff.show_tray_icon", false);
|
||||
prefSvc.setBoolPref("mail.biff.animate_dock_icon", false);
|
||||
|
||||
Gloda.addIndexerListener(messageIndexerListener.onIndexNotification);
|
||||
ims.catchAllCollection = Gloda._wildcardCollection(Gloda.NOUN_MESSAGE);
|
||||
ims.catchAllCollection.listener = messageCollectionListener;
|
||||
|
||||
// Make the indexer be more verbose about indexing for us...
|
||||
GlodaIndexer._unitTestSuperVerbose = true;
|
||||
|
||||
lobotomizeAdaptiveIndexer();
|
||||
|
||||
if (ims.injectMechanism == INJECT_FAKE_SERVER) {
|
||||
// -- Pull in the POP3 fake-server / local account helper code
|
||||
load("../../test_mailnewslocal/unit/head_maillocal.js");
|
||||
// set up POP3 fakeserver to feed things in...
|
||||
[ims.daemon, ims.server] = setupServerDaemon();
|
||||
// (this will call loadLocalMailAccount())
|
||||
|
@ -285,6 +297,42 @@ function imsInit() {
|
|||
// we need an inbox
|
||||
loadLocalMailAccount();
|
||||
}
|
||||
else if (ims.injectMechanism == INJECT_IMAP_FAKE_SERVER) {
|
||||
// Pull in the IMAP fake server code
|
||||
load("../../test_imap/unit/head_server.js");
|
||||
|
||||
// set up IMAP fakeserver and incoming server
|
||||
ims.daemon = new imapDaemon();
|
||||
ims.server = makeServer(ims.daemon, "");
|
||||
ims.incomingServer = createLocalIMAPServer();
|
||||
// we need a local account for the IMAP server to have its sent messages in
|
||||
loadLocalMailAccount();
|
||||
|
||||
// We need an identity so that updateFolder doesn't fail
|
||||
let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
|
||||
.getService(Ci.nsIMsgAccountManager);
|
||||
let localAccount = acctMgr.createAccount();
|
||||
let identity = acctMgr.createIdentity();
|
||||
localAccount.addIdentity(identity);
|
||||
localAccount.defaultIdentity = identity;
|
||||
localAccount.incomingServer = gLocalIncomingServer;
|
||||
acctMgr.defaultAccount = localAccount;
|
||||
|
||||
// Let's also have another account, using the same identity
|
||||
let imapAccount = acctMgr.createAccount();
|
||||
imapAccount.addIdentity(identity);
|
||||
imapAccount.defaultIdentity = identity;
|
||||
imapAccount.incomingServer = ims.incomingServer;
|
||||
|
||||
// The server doesn't support more than one connection
|
||||
prefSvc.setIntPref("mail.server.server1.max_cached_connections", 1);
|
||||
// We aren't interested in downloading messages automatically
|
||||
prefSvc.setBoolPref("mail.server.server1.download_on_biff", false);
|
||||
|
||||
// Set the inbox to not be offline
|
||||
ims.imapInbox = ims.incomingServer.rootFolder.getChildNamed("Inbox");
|
||||
ims.imapInbox.flags &= ~Ci.nsMsgFolderFlags.Offline;
|
||||
}
|
||||
|
||||
ims.inited = true;
|
||||
}
|
||||
|
@ -312,16 +360,11 @@ function imsInit() {
|
|||
*/
|
||||
function indexMessages(aSynthMessages, aVerifier, aOnDone) {
|
||||
let ims = indexMessageState;
|
||||
|
||||
ims.inputMessages = aSynthMessages;
|
||||
ims.glodaMessages = [];
|
||||
ims.verifier = aVerifier;
|
||||
ims.previousValue = undefined;
|
||||
ims.onDone = aOnDone;
|
||||
ims.expectMessages(aSynthMessages, aVerifier, aOnDone);
|
||||
|
||||
if (ims.injectMechanism == INJECT_FAKE_SERVER) {
|
||||
ims.daemon.setMessages(_synthMessagesToFakeRep(aSynthMessages));
|
||||
do_timeout(0, "driveFakeServer();");
|
||||
do_timeout(0, "drivePOP3FakeServer();");
|
||||
}
|
||||
else if (ims.injectMechanism == INJECT_MBOX) {
|
||||
ims.mboxName = "injecty" + ims.nextMboxNumber++;
|
||||
|
@ -344,7 +387,20 @@ function indexMessages(aSynthMessages, aVerifier, aOnDone) {
|
|||
localFolder.addMessage(msg.toMboxString());
|
||||
}
|
||||
}
|
||||
else if (ims.injectMechanism == INJECT_IMAP_FAKE_SERVER) {
|
||||
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
let serverInbox = ims.daemon.getMailbox("INBOX");
|
||||
|
||||
for (let [, msg] in Iterator(aSynthMessages)) {
|
||||
// Generate a URI out of the message
|
||||
let URI = ioService.newURI("data:text/plain;base64," + btoa(msg.toMessageString()), null, null);
|
||||
// Add it to the server
|
||||
serverInbox.addMessage(new imapMessage(URI.spec, serverInbox.uidnext++, []));
|
||||
}
|
||||
// Time to do stuff with the fakeserver
|
||||
driveIMAPFakeServer();
|
||||
}
|
||||
}
|
||||
|
||||
function injectMessagesUsing(aInjectMechanism) {
|
||||
|
@ -354,6 +410,8 @@ function injectMessagesUsing(aInjectMechanism) {
|
|||
var indexMessageState = {
|
||||
/** have we been initialized (hooked listeners, etc.) */
|
||||
inited: false,
|
||||
/** whether we're due for any index notifications */
|
||||
expectingIndexNotifications: false,
|
||||
/** our catch-all message collection that nets us all messages passing by */
|
||||
catchAllCollection: null,
|
||||
/** the set of synthetic messages passed in to indexMessages */
|
||||
|
@ -370,20 +428,49 @@ var indexMessageState = {
|
|||
injectMechanism: INJECT_ADDMESSAGE,
|
||||
|
||||
/* === Fake Server State === */
|
||||
/** nsMailServer instance with POP3_RFC1939 handler */
|
||||
/** nsMailServer instance (if POP3, with POP3_RFC1939 handler) */
|
||||
server: null,
|
||||
serverStarted: false,
|
||||
/** pop3Daemon instance */
|
||||
/** pop3Daemon/imapDaemon instance */
|
||||
daemon: null,
|
||||
/** incoming pop3 server */
|
||||
/** incoming pop3/imap server */
|
||||
incomingServer: null,
|
||||
/** pop3 service */
|
||||
/** pop3 service (not used for imap) */
|
||||
pop3Service: null,
|
||||
/** IMAP inbox */
|
||||
imapInbox: null,
|
||||
|
||||
/* === MBox Injection State === */
|
||||
nextMboxNumber: 0,
|
||||
mboxName: null,
|
||||
|
||||
/**
|
||||
* Sets up messages to expect index notifications for.
|
||||
*
|
||||
* @param aSynthMessages The synthetic messages to expect notifications
|
||||
* for. We currently don't do anything with these other than count them,
|
||||
* so pass whatever you want and it will be the 'source message' (1st
|
||||
* argument) to your verifier function.
|
||||
* @param aVerifier The function to call to verify that the indexing had the
|
||||
* desired result. Takes arguments aSynthMessage (the synthetic message
|
||||
* just indexed), aGlodaMessage (the gloda message representation of the
|
||||
* indexed message), and aPreviousResult (the value last returned by the
|
||||
* verifier function for this given set of messages, or undefined if it is
|
||||
* the first message.)
|
||||
* @param aOnDone The function to call when we complete processing this set of
|
||||
* messages.
|
||||
*/
|
||||
expectMessages: function indexMessageState_expectMessages(aSynthMessages, aVerifier,
|
||||
aOnDone) {
|
||||
dump("^^^ setting up " + aSynthMessages.length + " message(s) to expect\n");
|
||||
this.inputMessages = aSynthMessages;
|
||||
this.expectingIndexNotifications = true;
|
||||
this.glodaMessages = [];
|
||||
this.verifier = aVerifier;
|
||||
this.previousValue = undefined;
|
||||
this.onDone = aOnDone;
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener to handle the completion of the POP3 message retrieval (one way or
|
||||
* the other.)
|
||||
|
@ -418,38 +505,12 @@ var indexMessageState = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Indicate that we should expect some modified messages to be indexed.
|
||||
*
|
||||
* @param aMessages The messages that will be modified and we should expect
|
||||
* notifications about. We currently don't do anything with these other than
|
||||
* count them, so pass whatever you want and it will be the 'source message'
|
||||
* (1st argument) to your verifier function.
|
||||
* @param aVerifier See indexMessage's aVerifier argument.
|
||||
* @param aDone The (optional) callback to call on completion.
|
||||
* Perform POP3 mail fetching, seeing it through to completion.
|
||||
*/
|
||||
function expectModifiedMessages(aMessages, aVerifier, aOnDone) {
|
||||
function drivePOP3FakeServer() {
|
||||
let ims = indexMessageState;
|
||||
|
||||
ims.inputMessages = aMessages;
|
||||
ims.glodaMessages = [];
|
||||
ims.verifier = aVerifier;
|
||||
ims.previousValue = undefined;
|
||||
ims.onDone = aOnDone;
|
||||
|
||||
// we don't actually need to do anything. the caller is going to be
|
||||
// triggering a notification which will spur the indexer into action. the
|
||||
// indexer uses its own scheduling mechanism to drive itself, so as long
|
||||
// as an event loop is active, we're good.
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the mail fetching, seeing it through to completion.
|
||||
*/
|
||||
function driveFakeServer() {
|
||||
let ims = indexMessageState;
|
||||
dump(">>> enter driveFakeServer\n");
|
||||
dump(">>> enter drivePOP3FakeServer\n");
|
||||
// Handle the server in a try/catch/finally loop so that we always will stop
|
||||
// the server if something fails.
|
||||
try {
|
||||
|
@ -480,15 +541,48 @@ dump(">>> enter driveFakeServer\n");
|
|||
while (thread.hasPendingEvents())
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
dump("<<< exit driveFakeServer\n");
|
||||
dump("<<< exit drivePOP3FakeServer\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an IMAP mail fetch, seeing it through to completion
|
||||
*/
|
||||
function driveIMAPFakeServer() {
|
||||
dump(">>> enter driveIMAPFakeServer\n");
|
||||
let ims = indexMessageState;
|
||||
// Handle the server in a try/catch/finally loop so that we always will stop
|
||||
// the server if something fails.
|
||||
try {
|
||||
dump(" resetting fake server\n");
|
||||
ims.server.resetTest();
|
||||
|
||||
// Update the inbox
|
||||
dump(" issuing updateFolder\n");
|
||||
ims.imapInbox.updateFolder(null);
|
||||
// performTest won't work here because that seemingly blocks until the
|
||||
// socket is closed, which is something undesirable here
|
||||
}
|
||||
catch (e) {
|
||||
ims.server.stop();
|
||||
do_throw(e);
|
||||
}
|
||||
finally {
|
||||
dump(" draining events\n");
|
||||
let thread = gThreadManager.currentThread;
|
||||
while (thread.hasPendingEvents())
|
||||
thread.processNextEvent(true);
|
||||
}
|
||||
dump("<<< exit driveIMAPFakeServer\n");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tear down the fake server. This is very important to avoid things getting
|
||||
* upset during shutdown. (Namely, XPConnect will get mad about running in
|
||||
* a context without "Components" defined.)
|
||||
*/
|
||||
function killFakeServer() {
|
||||
dump("Killing fake server\n");
|
||||
let ims = indexMessageState;
|
||||
|
||||
ims.incomingServer.closeCachedConnections();
|
||||
|
@ -499,6 +593,8 @@ function killFakeServer() {
|
|||
var thread = gThreadManager.currentThread;
|
||||
while (thread.hasPendingEvents())
|
||||
thread.processNextEvent(true);
|
||||
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -549,30 +645,46 @@ var messageIndexerListener = {
|
|||
callbackOnDone: null,
|
||||
onIndexNotification: function(aStatus, aPrettyName, aJobIndex, aJobTotal,
|
||||
aJobItemIndex, aJobItemGoal) {
|
||||
dump("((( Index listener notified! aStatus = " + aStatus + "\n");
|
||||
// Ignore moving/removing notifications
|
||||
if (aStatus == Gloda.kIndexerMoving || aStatus == Gloda.kIndexerRemoving)
|
||||
return;
|
||||
|
||||
let ims = indexMessageState;
|
||||
// If we shouldn't be receiving notifications and we receive one with aStatus
|
||||
// != kIndexerIdle. throw.
|
||||
if (!ims.expectingIndexNotifications) {
|
||||
if (aStatus == Gloda.kIndexerIdle) {
|
||||
dump("((( Ignoring indexing notification since it's just kIndexerIdle.\n");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
do_throw("Exception during index notification -- we weren't " +
|
||||
"expecting one.");
|
||||
}
|
||||
}
|
||||
|
||||
// we only care if indexing has just completed...
|
||||
if (!GlodaIndexer.indexing) {
|
||||
if (aStatus == Gloda.kIndexerIdle) {
|
||||
if (messageIndexerListener.callbackOnDone) {
|
||||
let callback = messageIndexerListener.callbackOnDone;
|
||||
messageIndexerListener.callbackOnDone = null;
|
||||
callback();
|
||||
}
|
||||
|
||||
let ims = indexMessageState;
|
||||
|
||||
// this is just the synthetic notification if inputMessages is null
|
||||
if (ims.inputMessages === null) {
|
||||
dump("((( ignoring indexing notification, assuming synthetic " +
|
||||
"notification.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// if we haven't seen all the messages we should see, assume that the
|
||||
// rest are on their way, and are just coming in a subsequent job...
|
||||
// (Also, the first time we register our listener, we will get a synthetic
|
||||
// idle status; at least if the indexer is idle.)
|
||||
if (ims.glodaMessages.length < ims.inputMessages.length) {
|
||||
let glodaLen = ims.glodaMessages.length, inputLen =
|
||||
ims.inputMessages.length;
|
||||
if (glodaLen < inputLen) {
|
||||
dump("((( indexing is no longer indexing, but we're still expecting " +
|
||||
inputLen + " - " + glodaLen + " = " + (inputLen - glodaLen) +
|
||||
" more results, ignoring.\n");
|
||||
// If we're running IMAP, then update the folder once more
|
||||
if (ims.imapInbox)
|
||||
ims.imapInbox.updateFolder(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -580,6 +692,8 @@ var messageIndexerListener = {
|
|||
" messages) about to verify: " +
|
||||
(ims.verifier ? ims.verifier.name : "none") + " and complete: " +
|
||||
(ims.onDone ? ims.onDone.name : "none") + "\n");
|
||||
// If we're verifying messages, we shouldn't be expecting them
|
||||
ims.expectingIndexNotifications = false;
|
||||
|
||||
// call the verifier. (we expect them to generate an exception if the
|
||||
// verification fails, using do_check_*/do_throw; we don't care about
|
||||
|
@ -600,6 +714,7 @@ var messageIndexerListener = {
|
|||
}
|
||||
}
|
||||
|
||||
dump("((( Verification complete\n");
|
||||
if (ims.onDone) {
|
||||
try {
|
||||
ims.onDone();
|
||||
|
@ -770,9 +885,8 @@ function twiddleAndTest(aSynthMsg, aActionsAndTests) {
|
|||
// the underlying nsIMsgDBHdr should exist at this point...
|
||||
do_check_neq(gmsg.folderMessage, null);
|
||||
// prepare
|
||||
expectModifiedMessages([gmsg.folderMessage], verify_next_attr);
|
||||
indexMessageState.expectMessages([gmsg.folderMessage], verify_next_attr);
|
||||
// tell the function to perform its mutation to the desired state
|
||||
dump("twiddling: " + twiddleFunc.name + ": " + desiredState + "\n");
|
||||
twiddleFunc(gmsg.folderMessage, desiredState);
|
||||
}
|
||||
function verify_next_attr(smsg, gmsg) {
|
||||
|
@ -1021,8 +1135,11 @@ function _gh_test_iterator() {
|
|||
}
|
||||
}
|
||||
|
||||
if (indexMessageState.injectMechanism == INJECT_FAKE_SERVER) {
|
||||
killFakeServer();
|
||||
if (indexMessageState.injectMechanism == INJECT_FAKE_SERVER ||
|
||||
indexMessageState.injectMechanism == INJECT_IMAP_FAKE_SERVER) {
|
||||
do_test_pending();
|
||||
// Give a bit of time for any remaining notifications to go through.
|
||||
do_timeout(500, "killFakeServer()");
|
||||
}
|
||||
|
||||
do_test_finished();
|
||||
|
@ -1072,15 +1189,23 @@ function parameterizeTest(aTestFunc, aParameters) {
|
|||
*
|
||||
* @param aTests A list of test functions to call.
|
||||
* @param aLongestTestRunTimeConceivableInSecs Optional parameter
|
||||
* @param aDontInitIMS Optional parameter. If this is specified then the index
|
||||
* message state is not initialized
|
||||
*/
|
||||
function glodaHelperRunTests(aTests, aLongestTestRunTimeConceivableInSecs) {
|
||||
function glodaHelperRunTests(aTests, aLongestTestRunTimeConceivableInSecs,
|
||||
aDontInitIMS) {
|
||||
if (aLongestTestRunTimeConceivableInSecs == null)
|
||||
aLongestTestRunTimeConceivableInSecs =
|
||||
DEFAULT_LONGEST_TEST_RUN_CONCEIVABLE_SECS;
|
||||
do_timeout(aLongestTestRunTimeConceivableInSecs * 1000,
|
||||
"do_throw('Timeout running test, and we want you to have the log.');");
|
||||
|
||||
if (!aDontInitIMS)
|
||||
imsInit();
|
||||
// even if we don't want to init IMS, we want to avoid anything adaptive
|
||||
// happening.
|
||||
else
|
||||
lobotomizeAdaptiveIndexer();
|
||||
glodaHelperTests = aTests;
|
||||
glodaHelperIterator = _gh_test_iterator();
|
||||
next_test();
|
||||
|
@ -1135,3 +1260,19 @@ function nukeGlodaCachesAndCollections() {
|
|||
GlodaCollectionManager.defineCache(cache._nounDef, cache._maxCacheSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an IMAP folder, marks it offline and downloads its messages.
|
||||
*
|
||||
* @param aFolder an IMAP message folder
|
||||
* @param aSynthMessages see indexMessageState.expectMessages
|
||||
* @param aVerifier see indexMessageState.expectMessages
|
||||
* @param aDone see indexMessageState.expectMessages
|
||||
*/
|
||||
function imapDownloadAllMessages(aFolder, aSynthMessages, aVerifier, aDone) {
|
||||
// Let the message state know that we're expecting messages
|
||||
indexMessageState.expectMessages(aSynthMessages, aVerifier, aDone);
|
||||
aFolder.setFlag(Ci.nsMsgFolderFlags.Offline);
|
||||
aFolder.downloadAllForOffline(null, null);
|
||||
// The indexer listener is going to call aDone, so our job is done here
|
||||
}
|
||||
|
|
|
@ -97,6 +97,17 @@ var messageInfos = [
|
|||
|
||||
/* ===== Tests ===== */
|
||||
|
||||
/**
|
||||
* Hooks for pre/post setup message, used for the IMAP tests.
|
||||
*/
|
||||
var pre_inject_message_hook = function default_pre_inject_message_hook() {
|
||||
next_test();
|
||||
};
|
||||
|
||||
var post_inject_message_hook = function default_post_inject_message_hook() {
|
||||
next_test();
|
||||
};
|
||||
|
||||
function setup_create_message(info) {
|
||||
info.body = {body: [tupe[1] for each
|
||||
([, tupe] in Iterator(info.bode))].join("\r\n")};
|
||||
|
@ -125,10 +136,12 @@ function glodaInfoStasher(aSynthMessage, aGlodaMessage) {
|
|||
/**
|
||||
* Actually inject all the messages we created above.
|
||||
*/
|
||||
var gSynMessages;
|
||||
|
||||
function setup_inject_messages() {
|
||||
let synMessages = [info._synMsg for each
|
||||
gSynMessages = [info._synMsg for each
|
||||
([, info] in Iterator(messageInfos))];
|
||||
indexMessages(synMessages, glodaInfoStasher, next_test);
|
||||
indexMessages(gSynMessages, glodaInfoStasher, next_test);
|
||||
}
|
||||
|
||||
function test_stream_message(info) {
|
||||
|
@ -166,11 +179,15 @@ function verify_message_content(aInfo, aSynMsg, aGlodaMsg, aMsgHdr, aMimeMsg) {
|
|||
|
||||
var tests = [
|
||||
parameterizeTest(setup_create_message, messageInfos),
|
||||
function pre_inject_message() { pre_inject_message_hook(); },
|
||||
setup_inject_messages,
|
||||
function post_inject_message() { post_inject_message_hook(); },
|
||||
// disable_index_notifications,
|
||||
parameterizeTest(test_stream_message, messageInfos),
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
glodaHelperRunTests(tests);
|
||||
}
|
||||
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests the operation of the GlodaContent (in connotent.js) and its exposure
|
||||
* via Gloda.getMessageContent for IMAP messages that are originally offline.
|
||||
*/
|
||||
|
||||
load("test_gloda_content.js");
|
||||
|
||||
/**
|
||||
* Set the imap folder to offline before adding the messages.
|
||||
*/
|
||||
var pre_inject_message_hook = function imap_pre_inject_message_hook() {
|
||||
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
|
||||
next_test();
|
||||
};
|
||||
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests the operation of the GlodaContent (in connotent.js) and its exposure
|
||||
* via Gloda.getMessageContent for IMAP messages that were not originally
|
||||
* offline, but were later made offline.
|
||||
*/
|
||||
|
||||
load("test_gloda_content.js");
|
||||
|
||||
/**
|
||||
* Set the imap folder to offline after adding the messages, then force a
|
||||
* download of all messages.
|
||||
*/
|
||||
var post_inject_message_hook = function imap_post_inject_message_hook() {
|
||||
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages,
|
||||
glodaInfoStasher, next_test);
|
||||
};
|
||||
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -10,6 +10,9 @@
|
|||
load("../../mailnews/resources/messageGenerator.js");
|
||||
load("resources/glodaTestHelper.js");
|
||||
|
||||
// Whether we can expect fulltext results
|
||||
var expectFulltextResults = true;
|
||||
|
||||
// Create a message generator
|
||||
var msgGen = new MessageGenerator();
|
||||
// Create a message scenario generator using that message generator
|
||||
|
@ -17,13 +20,25 @@ var scenarios = new MessageScenarioFactory(msgGen);
|
|||
|
||||
/* ===== Threading / Conversation Grouping ===== */
|
||||
|
||||
var gSynMessages = [];
|
||||
function allMessageInSameConversation(aSynthMessage, aGlodaMessage, aConvID) {
|
||||
if (aConvID === undefined)
|
||||
return aGlodaMessage.conversationID;
|
||||
do_check_eq(aConvID, aGlodaMessage.conversationID);
|
||||
// Cheat and stash the synthetic message (we need them for one of the IMAP
|
||||
// tests)
|
||||
gSynMessages.push(aSynthMessage);
|
||||
return aConvID;
|
||||
}
|
||||
|
||||
// These are overridden by the IMAP tests as needed
|
||||
var pre_test_threading_hook = function default_pre_test_threading_hook() {
|
||||
next_test();
|
||||
};
|
||||
var post_test_threading_hook = function default_post_test_threading_hook() {
|
||||
next_test();
|
||||
};
|
||||
|
||||
/**
|
||||
* Test our conversation/threading logic in the straight-forward direct
|
||||
* reply case, the missing intermediary case, and the siblings with missing
|
||||
|
@ -72,12 +87,17 @@ function test_attributes_fundamental() {
|
|||
indexMessages([smsg], verify_attributes_fundamental, next_test);
|
||||
}
|
||||
|
||||
// Overridden by test_index_imap_mesasges
|
||||
var get_expected_folder_URI = function local_get_expected_folder_URI() {
|
||||
return gLocalInboxFolder.URI;
|
||||
};
|
||||
|
||||
function verify_attributes_fundamental(smsg, gmsg) {
|
||||
try {
|
||||
// save off the message id for test_attributes_fundamental_from_disk
|
||||
fundamentalGlodaMessageId = gmsg.id;
|
||||
|
||||
do_check_eq(gmsg.folderURI, gLocalInboxFolder.URI);
|
||||
do_check_eq(gmsg.folderURI, get_expected_folder_URI());
|
||||
|
||||
// -- subject
|
||||
do_check_eq(smsg.subject, gmsg.conversation.subject);
|
||||
|
@ -98,12 +118,22 @@ function verify_attributes_fundamental(smsg, gmsg) {
|
|||
// date
|
||||
do_check_eq(smsg.date.valueOf(), gmsg.date.valueOf());
|
||||
|
||||
// -- attachments
|
||||
// -- message ID
|
||||
do_check_eq(smsg.messageId, gmsg.headerMessageID);
|
||||
|
||||
// -- attachments. We won't have these if we don't have fulltext results
|
||||
if (expectFulltextResults) {
|
||||
do_check_eq(gmsg.attachmentTypes.length, 1);
|
||||
do_check_eq(gmsg.attachmentTypes[0], "text/plain");
|
||||
do_check_eq(gmsg.attachmentNames.length, 1);
|
||||
do_check_eq(gmsg.attachmentNames[0], "bob.txt");
|
||||
}
|
||||
else {
|
||||
// Make sure we don't actually get attachments!
|
||||
do_check_eq(gmsg.attachmentTypes, null);
|
||||
do_check_eq(gmsg.attachmentNames, null);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
// print out some info on the various states of the messages...
|
||||
dump("***** FUNDAMENTAL ATTRIBUTE NON-MATCH\n");
|
||||
|
@ -229,7 +259,9 @@ function test_message_deletion() {
|
|||
|
||||
|
||||
var tests = [
|
||||
function pre_test_threading() { pre_test_threading_hook(); },
|
||||
test_threading,
|
||||
function post_test_threading() { post_test_threading_hook(); },
|
||||
test_attributes_fundamental,
|
||||
test_attributes_fundamental_from_disk,
|
||||
test_attributes_explicit,
|
||||
|
|
|
@ -14,22 +14,35 @@ var scenarios = new MessageScenarioFactory(msgGen);
|
|||
/**
|
||||
* Provide a bunch of messages to be indexed.
|
||||
*/
|
||||
var gSynMessages;
|
||||
function test_index_a_bunch() {
|
||||
// 4-children-per, 3-deep = 21
|
||||
// 6-children-per, 3 deep = 43
|
||||
// 7-children-per, 3-deep = 57
|
||||
// 4-children-per, 4-deep = 85
|
||||
// 4-children-per, 5-deep pyramid = 341
|
||||
// 5-children-per, 5-deep pyramid = 781
|
||||
// 4-children-per, 6-deep pyramid = 1365 messages
|
||||
let messages = scenarios.fullPyramid(4, 3);
|
||||
gSynMessages = scenarios.fullPyramid(6, 3);
|
||||
// we have no need to verify.
|
||||
indexMessages(messages, null, next_test);
|
||||
indexMessages(gSynMessages, null, next_test);
|
||||
}
|
||||
|
||||
var pre_test_hook = function default_pre_test_hook() {
|
||||
next_test();
|
||||
};
|
||||
var post_test_hook = function default_post_test_hook() {
|
||||
next_test();
|
||||
};
|
||||
|
||||
var tests = [
|
||||
function pre_test() { pre_test_hook(); },
|
||||
test_index_a_bunch,
|
||||
function post_test() { post_test_hook(); },
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
glodaHelperRunTests(tests);
|
||||
}
|
||||
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests how well gloda indexes IMAP messages that aren't offline.
|
||||
*/
|
||||
|
||||
// Most of the definitions are common, so just re-use those
|
||||
load("test_index_messages.js");
|
||||
|
||||
var get_expected_folder_URI = function imap_get_expected_folder_URI() {
|
||||
return indexMessageState.imapInbox.URI;
|
||||
};
|
||||
|
||||
var expectFulltextResults = false;
|
||||
|
||||
// Switch to the IMAP fake server
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,10 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests how well gloda indexes IMAP messages that aren't offline in bulk.
|
||||
*/
|
||||
|
||||
// The definitions are common, so just re-use those
|
||||
load("test_index_messages_in_bulk.js");
|
||||
|
||||
// Switch to the IMAP fake server
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests how well gloda indexes IMAP messages that are offline from the start.
|
||||
*/
|
||||
|
||||
// Most of the definitions are common, so just re-use those
|
||||
load("test_index_messages.js");
|
||||
|
||||
var get_expected_folder_URI = function imap_get_expected_folder_URI() {
|
||||
return indexMessageState.imapInbox.URI;
|
||||
};
|
||||
|
||||
var pre_test_threading_hook = function imap_pre_test_threading_hook() {
|
||||
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
|
||||
next_test();
|
||||
};
|
||||
|
||||
// Switch to the IMAP fake server
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,15 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests how well gloda indexes IMAP messages that aren't offline in bulk.
|
||||
*/
|
||||
|
||||
// The definitions are common, so just re-use those
|
||||
load("test_index_messages_in_bulk.js");
|
||||
|
||||
var pre_test_hook = function imap_pre_test_hook() {
|
||||
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
|
||||
next_test();
|
||||
};
|
||||
|
||||
// Switch to the IMAP fake server
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,21 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests how well gloda indexes IMAP messages that are not offline at first, but
|
||||
* are made offline later.
|
||||
*/
|
||||
|
||||
// Most of the definitions are common, so just re-use those
|
||||
load("test_index_messages.js");
|
||||
|
||||
var get_expected_folder_URI = function imap_get_expected_folder_URI() {
|
||||
return indexMessageState.imapInbox.URI;
|
||||
};
|
||||
|
||||
var post_test_threading_hook = function imap_post_test_threading_hook() {
|
||||
// We aren't concerned about verification here, so just pass in null
|
||||
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages, null,
|
||||
next_test);
|
||||
};
|
||||
|
||||
// Switch to the IMAP fake server
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Tests how well gloda indexes IMAP messages that aren't offline in bulk.
|
||||
*/
|
||||
|
||||
// The definitions are common, so just re-use those
|
||||
load("test_index_messages_in_bulk.js");
|
||||
|
||||
var post_test_hook = function imap_post_test_hook() {
|
||||
// We're not verifying anything
|
||||
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages,
|
||||
null, next_test);
|
||||
};
|
||||
|
||||
// Switch to the IMAP fake server
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -241,7 +241,22 @@ function setup_search_ranking_idiom() {
|
|||
new Widget(1, origin, "", 0, "bar baz", "bar baz bar bar"), // 6 + 0
|
||||
new Widget(0, origin, "", 1, "bar baz", "bar baz bar bar") // 7 + 0
|
||||
];
|
||||
runOnIndexingComplete(next_test);
|
||||
|
||||
let indexingInProgress = false;
|
||||
|
||||
// Since we don't use the message indexer listener any more in this test, we
|
||||
// need to add our own listener.
|
||||
function genericIndexerCallback(aStatus) {
|
||||
// If indexingInProgress is false, we've received the synthetic
|
||||
// notification, so ignore it
|
||||
if (indexingInProgress && aStatus == Gloda.kIndexerIdle) {
|
||||
// We're done, so remove ourselves and move to the next test
|
||||
Gloda.removeIndexerListener(genericIndexerCallback);
|
||||
next_test();
|
||||
}
|
||||
}
|
||||
Gloda.addIndexerListener(genericIndexerCallback);
|
||||
indexingInProgress = true;
|
||||
GenericIndexer.indexNewObjects(fooWidgets.concat(barBazWidgets));
|
||||
}
|
||||
|
||||
|
@ -317,7 +332,6 @@ var tests = [
|
|||
];
|
||||
|
||||
function run_test() {
|
||||
// use mbox injection so we get multiple folders...
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
glodaHelperRunTests(tests);
|
||||
// Don't initialize the index message state
|
||||
glodaHelperRunTests(tests, null, true);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,12 @@ load("resources/glodaTestHelper.js");
|
|||
var msgGen = new MessageGenerator();
|
||||
// Create a message scenario generator using that message generator
|
||||
var scenarios = new MessageScenarioFactory(msgGen);
|
||||
// Whether we're using a single folder to test. We need to skip a few tests if
|
||||
// we're doing so
|
||||
var singleFolder = false;
|
||||
// Whether we expect fulltext results. IMAP folders that are offline shouldn't
|
||||
// have their bodies indexed.
|
||||
var expectFulltextResults = true;
|
||||
|
||||
/* ===== Populate ===== */
|
||||
var world = {
|
||||
|
@ -201,6 +207,15 @@ function glodaInfoStasher(aSynthMessage, aGlodaMessage) {
|
|||
world.glodaFolders.push(aGlodaMessage.folder);
|
||||
}
|
||||
|
||||
// We override these for the IMAP tests
|
||||
var pre_setup_populate_hook = function default_pre_setup_populate_hook() {
|
||||
next_test();
|
||||
};
|
||||
var post_setup_populate_hook = function default_post_setup_populate_hook() {
|
||||
next_test();
|
||||
};
|
||||
|
||||
var gSynMessages = [];
|
||||
// first, we must populate our message store with delicious messages.
|
||||
function setup_populate() {
|
||||
world.glodaHolderCollection = Gloda.explicitCollection(Gloda.NOUN_MESSAGE,
|
||||
|
@ -216,13 +231,22 @@ function setup_populate() {
|
|||
world.glodaConversationIds.push(null);
|
||||
}
|
||||
|
||||
indexMessages(generateFolderMessages(), glodaInfoStasher,
|
||||
setup_populate_phase_two);
|
||||
let messages = generateFolderMessages();
|
||||
gSynMessages = gSynMessages.concat(messages);
|
||||
indexMessages(messages, glodaInfoStasher, setup_populate_phase_two);
|
||||
}
|
||||
|
||||
function setup_populate_phase_two() {
|
||||
// If we have one folder, we don't attempt to populate the other one
|
||||
if (singleFolder) {
|
||||
next_test();
|
||||
}
|
||||
else {
|
||||
world.phase++;
|
||||
indexMessages(generateFolderMessages(), glodaInfoStasher, next_test);
|
||||
let messages = generateFolderMessages();
|
||||
gSynMessages = gSynMessages.concat(messages);
|
||||
indexMessages(messages, glodaInfoStasher, next_test);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Non-text queries ===== */
|
||||
|
@ -294,6 +318,12 @@ var ts_folderCollections = [];
|
|||
* @tests gloda.datastore.sqlgen.kConstraintIn
|
||||
*/
|
||||
function test_query_messages_by_folder() {
|
||||
// If we have one folder to test with, we can't do this test more times
|
||||
if (singleFolder && ts_folderNum >= 1) {
|
||||
next_test();
|
||||
return;
|
||||
}
|
||||
|
||||
let folderNum = ts_folderNum++;
|
||||
let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
|
||||
query.folder(world.glodaFolders[folderNum]);
|
||||
|
@ -307,6 +337,8 @@ function test_query_messages_by_folder() {
|
|||
* @tests gloda.query.test.kConstraintIn
|
||||
*/
|
||||
function test_query_messages_by_folder_nonmatches() {
|
||||
// No can do with one folder
|
||||
if (!singleFolder)
|
||||
verify_nonMatches(ts_folderQueries, ts_folderCollections);
|
||||
next_test();
|
||||
}
|
||||
|
@ -458,7 +490,8 @@ function test_query_messages_by_body_text() {
|
|||
let convBodyTerm = uniqueTermGenerator(
|
||||
UNIQUE_OFFSET_BODY + UNIQUE_OFFSET_CONV + convNum);
|
||||
query.bodyMatches(convBodyTerm);
|
||||
queryExpect(query, world.conversationLists[convNum]); // calls next_test
|
||||
queryExpect(query, expectFulltextResults ? world.conversationLists[convNum] :
|
||||
[]); // calls next_test
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -473,7 +506,8 @@ function test_query_messages_by_attachment_names() {
|
|||
let convUniqueAttachment = uniqueTermGenerator(
|
||||
UNIQUE_OFFSET_ATTACHMENT + UNIQUE_OFFSET_CONV + convNum);
|
||||
query.attachmentNamesMatch(convUniqueAttachment);
|
||||
queryExpect(query, world.conversationLists[convNum]); // calls next_test
|
||||
queryExpect(query, expectFulltextResults ? world.conversationLists[convNum] :
|
||||
[]); // calls next_test
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -595,7 +629,9 @@ function test_query_identities_by_kind_and_value_nonmatches() {
|
|||
/* ===== Driver ===== */
|
||||
|
||||
var tests = [
|
||||
function pre_setup_populate() { pre_setup_populate_hook(); },
|
||||
setup_populate,
|
||||
function post_setup_populate() { post_setup_populate_hook(); },
|
||||
test_query_messages_by_conversation,
|
||||
test_query_messages_by_conversation,
|
||||
test_query_messages_by_conversation_nonmatches,
|
||||
|
@ -629,7 +665,8 @@ var tests = [
|
|||
];
|
||||
|
||||
function run_test() {
|
||||
// use mbox injection so we get multiple folders...
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
glodaHelperRunTests(tests);
|
||||
}
|
||||
|
||||
// use mbox injection so we get multiple folders...
|
||||
injectMessagesUsing(INJECT_MBOX);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Test query support for IMAP messages that aren't offline.
|
||||
*/
|
||||
load("test_query_messages.js");
|
||||
|
||||
var expectFulltextResults = false;
|
||||
// TODO: Make this use multiple folders, like the local folders test
|
||||
var singleFolder = true;
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,16 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Test query support for IMAP messages that were offline before they were
|
||||
* indexed.
|
||||
*/
|
||||
load("test_query_messages.js");
|
||||
|
||||
// Set the inbox to offline before proceeding
|
||||
var pre_setup_populate_hook = function imap_pre_setup_populate_hook() {
|
||||
indexMessageState.imapInbox.setFlag(Ci.nsMsgFolderFlags.Offline);
|
||||
next_test();
|
||||
};
|
||||
|
||||
// TODO: Make this use multiple folders, like the local folders test
|
||||
var singleFolder = true;
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -0,0 +1,19 @@
|
|||
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/**
|
||||
* Test query support for IMAP messages that were indexed, then made available
|
||||
* offline.
|
||||
*/
|
||||
load("test_query_messages.js");
|
||||
|
||||
/**
|
||||
* Set the imap folder to offline after adding the messages, then force a
|
||||
* download of all messages.
|
||||
*/
|
||||
var post_setup_populate_hook = function imap_post_setup_populate_hook() {
|
||||
imapDownloadAllMessages(indexMessageState.imapInbox, gSynMessages, null,
|
||||
next_test);
|
||||
};
|
||||
|
||||
// TODO: Make this use multiple folders, like the local folders test
|
||||
var singleFolder = true;
|
||||
injectMessagesUsing(INJECT_IMAP_FAKE_SERVER);
|
|
@ -242,7 +242,7 @@ interface nsIMsgDBService : nsISupports
|
|||
nsIMsgDatabase cachedDBForFolder(in nsIMsgFolder aFolder);
|
||||
};
|
||||
|
||||
[scriptable, uuid(51fd11c9-5be6-4cad-af6b-fb18d6232be4)]
|
||||
[scriptable, uuid(231E3473-DA59-4d18-AD54-F5A9D24AE3FC)]
|
||||
interface nsIMsgDatabase : nsIDBChangeAnnouncer {
|
||||
/**
|
||||
* Opens a database folder.
|
||||
|
@ -343,6 +343,14 @@ interface nsIMsgDatabase : nsIDBChangeAnnouncer {
|
|||
|
||||
void MarkHdrMarked(in nsIMsgDBHdr msgHdr, in boolean mark,
|
||||
in nsIDBChangeListener instigator);
|
||||
/**
|
||||
* Remove the new status from a message.
|
||||
*
|
||||
* @param aMsgHdr The database reference header for the message
|
||||
* @param aInstigator Reference to original calling object
|
||||
*/
|
||||
void MarkHdrNotNew(in nsIMsgDBHdr aMsgHdr,
|
||||
in nsIDBChangeListener aInstigator);
|
||||
|
||||
// MDN support
|
||||
void MarkMDNNeeded(in nsMsgKey key, in boolean bNeeded,
|
||||
|
@ -407,7 +415,7 @@ interface nsIMsgDatabase : nsIDBChangeAnnouncer {
|
|||
in nsIDBChangeListener instigator);
|
||||
void SetLabel(in nsMsgKey key, in nsMsgLabelValue label);
|
||||
void setStringProperty(in nsMsgKey aKey, in string aProperty, in string aValue);
|
||||
/*
|
||||
/**
|
||||
* Set the value of a string property in a message header
|
||||
*
|
||||
* @param msgHdr Header of the message whose property will be changed
|
||||
|
@ -416,6 +424,16 @@ interface nsIMsgDatabase : nsIDBChangeAnnouncer {
|
|||
*/
|
||||
void setStringPropertyByHdr(in nsIMsgDBHdr msgHdr, in string aProperty, in string aValue);
|
||||
|
||||
/**
|
||||
* Set the value of a uint32 property in a message header.
|
||||
*
|
||||
* @param aMsgHdr header of the message whose property will be changed
|
||||
* @param aProperty the property to change
|
||||
* @param aValue new value for the property
|
||||
*/
|
||||
void setUint32PropertyByHdr(in nsIMsgDBHdr aMsgHdr,
|
||||
in string aProperty, in unsigned long aValue);
|
||||
|
||||
void MarkImapDeleted(in nsMsgKey key, in boolean deleted,
|
||||
in nsIDBChangeListener instigator);
|
||||
|
||||
|
|
|
@ -404,7 +404,6 @@ protected:
|
|||
nsMsgRetainByPreference m_retainByPreference;
|
||||
PRUint32 m_daysToKeepHdrs;
|
||||
PRUint32 m_numHeadersToKeep;
|
||||
PRUint32 m_keepUnreadMessagesProp;
|
||||
PRBool m_keepUnreadMessagesOnly;
|
||||
PRBool m_useServerDefaults;
|
||||
PRBool m_cleanupBodiesByDays;
|
||||
|
|
|
@ -681,7 +681,20 @@ NS_IMETHODIMP nsMsgDatabase::NotifyHdrChangeAll(nsIMsgDBHdr *aHdrChanged,
|
|||
PRUint32 aNewFlags,
|
||||
nsIDBChangeListener *aInstigator)
|
||||
{
|
||||
NOTIFY_LISTENERS(OnHdrFlagsChanged, (aHdrChanged, aOldFlags, aNewFlags, aInstigator));
|
||||
// We will only notify the change if the header exists in the database.
|
||||
// This allows database functions to be usable in both the case where the
|
||||
// header is in the db, or the header is not so no notifications should be
|
||||
// given.
|
||||
nsMsgKey key;
|
||||
PRBool inDb = PR_FALSE;
|
||||
if (aHdrChanged)
|
||||
{
|
||||
aHdrChanged->GetMessageKey(&key);
|
||||
ContainsKey(key, &inDb);
|
||||
}
|
||||
if (inDb)
|
||||
NOTIFY_LISTENERS(OnHdrFlagsChanged,
|
||||
(aHdrChanged, aOldFlags, aNewFlags, aInstigator));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2186,6 +2199,59 @@ NS_IMETHODIMP nsMsgDatabase::SetStringPropertyByHdr(nsIMsgDBHdr *msgHdr, const c
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMsgDatabase::SetUint32PropertyByHdr(nsIMsgDBHdr *aMsgHdr,
|
||||
const char *aProperty,
|
||||
PRUint32 aValue)
|
||||
{
|
||||
// If no change to this property, bail out.
|
||||
PRUint32 oldValue;
|
||||
nsresult rv = aMsgHdr->GetUint32Property(aProperty, &oldValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (oldValue == aValue)
|
||||
return NS_OK;
|
||||
|
||||
// Don't do notifications if message not yet added to database.
|
||||
PRBool notify = PR_TRUE;
|
||||
nsMsgKey key = nsMsgKey_None;
|
||||
aMsgHdr->GetMessageKey(&key);
|
||||
ContainsKey(key, ¬ify);
|
||||
|
||||
// Precall OnHdrPropertyChanged to store prechange status.
|
||||
nsTArray<PRUint32> statusArray(m_ChangeListeners.Length());
|
||||
PRUint32 status;
|
||||
nsCOMPtr<nsIDBChangeListener> listener;
|
||||
if (notify)
|
||||
{
|
||||
nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
|
||||
while (listeners.HasMore())
|
||||
{
|
||||
listener = listeners.GetNext();
|
||||
listener->OnHdrPropertyChanged(aMsgHdr, PR_TRUE, &status, nsnull);
|
||||
// Ignore errors, but append element to keep arrays in sync.
|
||||
statusArray.AppendElement(status);
|
||||
}
|
||||
}
|
||||
|
||||
rv = aMsgHdr->SetUint32Property(aProperty, aValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Postcall OnHdrPropertyChanged to process the change.
|
||||
if (notify)
|
||||
{
|
||||
nsTObserverArray<nsCOMPtr<nsIDBChangeListener> >::ForwardIterator listeners(m_ChangeListeners);
|
||||
for (PRUint32 i = 0; listeners.HasMore(); i++)
|
||||
{
|
||||
listener = listeners.GetNext();
|
||||
status = statusArray[i];
|
||||
listener->OnHdrPropertyChanged(aMsgHdr, PR_FALSE, &status, nsnull);
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgDatabase::SetLabel(nsMsgKey key, nsMsgLabelValue label)
|
||||
{
|
||||
nsresult rv;
|
||||
|
@ -2372,6 +2438,16 @@ NS_IMETHODIMP nsMsgDatabase::MarkHdrMarked(nsIMsgDBHdr *msgHdr, PRBool mark,
|
|||
return SetMsgHdrFlag(msgHdr, mark, nsMsgMessageFlags::Marked, instigator);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsMsgDatabase::MarkHdrNotNew(nsIMsgDBHdr *aMsgHdr,
|
||||
nsIDBChangeListener *aInstigator)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aMsgHdr);
|
||||
nsMsgKey msgKey;
|
||||
aMsgHdr->GetMessageKey(&msgKey);
|
||||
m_newSet.RemoveElement(msgKey);
|
||||
return SetMsgHdrFlag(aMsgHdr, PR_FALSE, nsMsgMessageFlags::New, aInstigator);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsMsgDatabase::MarkAllRead(nsTArray<nsMsgKey> *thoseMarked)
|
||||
{
|
||||
|
@ -5030,7 +5106,16 @@ nsresult nsMsgDatabase::PurgeExcessMessages(PRUint32 numHeadersToKeep,
|
|||
|
||||
NS_IMPL_ISUPPORTS1(nsMsgRetentionSettings, nsIMsgRetentionSettings)
|
||||
|
||||
// Initialise the member variables to resonable defaults.
|
||||
nsMsgRetentionSettings::nsMsgRetentionSettings()
|
||||
: m_retainByPreference(1),
|
||||
m_daysToKeepHdrs(0),
|
||||
m_numHeadersToKeep(0),
|
||||
m_keepUnreadMessagesOnly(PR_FALSE),
|
||||
m_useServerDefaults(PR_TRUE),
|
||||
m_cleanupBodiesByDays(PR_FALSE),
|
||||
m_daysToKeepBodies(0),
|
||||
m_applyToFlaggedMessages(PR_FALSE)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -411,6 +411,18 @@ nsImapIncomingServer::GetImapConnectionAndLoadUrl(nsIEventTarget * aClientEventT
|
|||
nsIImapUrl* aImapUrl,
|
||||
nsISupports* aConsumer)
|
||||
{
|
||||
// if we're shutting down, and not running the kinds of urls we run at
|
||||
// shutdown, then this should fail because running urls during
|
||||
// shutdown will very likely fail and potentially hang.
|
||||
if (m_shuttingDown)
|
||||
{
|
||||
nsImapAction imapAction;
|
||||
aImapUrl->GetImapAction(&imapAction);
|
||||
if (imapAction != nsIImapUrl::nsImapExpungeFolder &&
|
||||
imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
|
||||
imapAction != nsIImapUrl::nsImapDeleteFolder)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr <nsIImapProtocol> aProtocol;
|
||||
|
||||
|
@ -2911,7 +2923,25 @@ NS_IMETHODIMP
|
|||
nsImapIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(filterScope);
|
||||
*filterScope = nsMsgSearchScope::onlineMailFilter;
|
||||
// If the inbox is enabled for offline use, then use the offline filter
|
||||
// scope, else use the online filter scope.
|
||||
//
|
||||
// XXX We use the same scope for all folders with the same incoming server,
|
||||
// yet it is possible to set the offline flag separately for each folder.
|
||||
// Manual filters could perhaps check the offline status of each folder,
|
||||
// though it's hard to see how to make that work since we only store filters
|
||||
// per server.
|
||||
//
|
||||
nsCOMPtr<nsIMsgFolder> rootMsgFolder;
|
||||
nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIMsgFolder> offlineInboxMsgFolder;
|
||||
rv = rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox |
|
||||
nsMsgFolderFlags::Offline,
|
||||
getter_AddRefs(offlineInboxMsgFolder));
|
||||
|
||||
*filterScope = offlineInboxMsgFolder ? nsMsgSearchScope::offlineMailFilter
|
||||
: nsMsgSearchScope::onlineMailFilter;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,8 @@
|
|||
#include "nsEscape.h"
|
||||
#include "nsIDOMWindowInternal.h"
|
||||
#include "nsIMsgFilter.h"
|
||||
#include "nsIMsgFilterService.h"
|
||||
#include "nsIMsgSearchCustomTerm.h"
|
||||
#include "nsImapMoveCoalescer.h"
|
||||
#include "nsIPrompt.h"
|
||||
#include "nsIPromptService.h"
|
||||
|
@ -222,7 +224,8 @@ nsImapMailFolder::nsImapMailFolder() :
|
|||
m_downloadingFolderForOfflineUse(PR_FALSE),
|
||||
m_folderQuotaUsedKB(0),
|
||||
m_folderQuotaMaxKB(0),
|
||||
m_applyIncomingFilters(PR_FALSE)
|
||||
m_applyIncomingFilters(PR_FALSE),
|
||||
m_filterListRequiresBody(PR_FALSE)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsImapMailFolder); // double count these for now.
|
||||
|
||||
|
@ -726,6 +729,68 @@ NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(nsIMsgWindow *aMsgWindo
|
|||
rv = server->ConfigureTemporaryFilters(m_filterList);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// If a body filter is enabled for an offline folder, delay the filter
|
||||
// application until after message has been downloaded.
|
||||
m_filterListRequiresBody = PR_FALSE;
|
||||
|
||||
if (mFlags & nsMsgFolderFlags::Offline)
|
||||
{
|
||||
nsCOMPtr<nsIMsgFilterService> filterService =
|
||||
do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
|
||||
PRUint32 filterCount = 0;
|
||||
m_filterList->GetFilterCount(&filterCount);
|
||||
for (PRUint32 index = 0;
|
||||
index < filterCount && !m_filterListRequiresBody;
|
||||
++index)
|
||||
{
|
||||
nsCOMPtr<nsIMsgFilter> filter;
|
||||
m_filterList->GetFilterAt(index, getter_AddRefs(filter));
|
||||
if (!filter)
|
||||
continue;
|
||||
nsMsgFilterTypeType filterType;
|
||||
filter->GetFilterType(&filterType);
|
||||
if (!(filterType & nsMsgFilterType::Incoming))
|
||||
continue;
|
||||
PRBool enabled = PR_FALSE;
|
||||
filter->GetEnabled(&enabled);
|
||||
if (!enabled)
|
||||
continue;
|
||||
nsCOMPtr<nsISupportsArray> searchTerms;
|
||||
PRUint32 numSearchTerms = 0;
|
||||
filter->GetSearchTerms(getter_AddRefs(searchTerms));
|
||||
if (searchTerms)
|
||||
searchTerms->Count(&numSearchTerms);
|
||||
for (PRUint32 termIndex = 0;
|
||||
termIndex < numSearchTerms && !m_filterListRequiresBody;
|
||||
termIndex++)
|
||||
{
|
||||
nsCOMPtr<nsIMsgSearchTerm> term;
|
||||
rv = searchTerms->QueryElementAt(termIndex,
|
||||
NS_GET_IID(nsIMsgSearchTerm),
|
||||
getter_AddRefs(term));
|
||||
nsMsgSearchAttribValue attrib;
|
||||
rv = term->GetAttrib(&attrib);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (attrib == nsMsgSearchAttrib::Body)
|
||||
m_filterListRequiresBody = PR_TRUE;
|
||||
else if (attrib == nsMsgSearchAttrib::Custom)
|
||||
{
|
||||
nsCAutoString customId;
|
||||
rv = term->GetCustomId(customId);
|
||||
nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
|
||||
if (NS_SUCCEEDED(rv) && filterService)
|
||||
rv = filterService->GetCustomTerm(customId,
|
||||
getter_AddRefs(customTerm));
|
||||
PRBool needsBody = PR_FALSE;
|
||||
if (NS_SUCCEEDED(rv))
|
||||
rv = customTerm->GetNeedsBody(&needsBody);
|
||||
if (NS_SUCCEEDED(rv) && needsBody)
|
||||
m_filterListRequiresBody = PR_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectFolder = PR_TRUE;
|
||||
|
@ -3051,13 +3116,15 @@ nsresult nsImapMailFolder::NormalEndHeaderParseStream(nsIImapProtocol *aProtocol
|
|||
}
|
||||
rv = m_msgParser->GetAllHeaders(&headers, &headersSize);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter)
|
||||
if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter &&
|
||||
!m_filterListRequiresBody)
|
||||
{
|
||||
if (m_filterList)
|
||||
{
|
||||
GetMoveCoalescer(); // not sure why we're doing this here.
|
||||
m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr, this, mDatabase,
|
||||
headers, headersSize, this, msgWindow, nsnull);
|
||||
NotifyFolderEvent(mFiltersAppliedAtom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3264,6 +3331,20 @@ NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key)
|
|||
|
||||
NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindow *msgWindow, PRBool *applyMore)
|
||||
{
|
||||
//
|
||||
// This routine is called indirectly from ApplyFiltersToHdr in two
|
||||
// circumstances, controlled by m_filterListRequiresBody:
|
||||
//
|
||||
// If false, after headers are parsed in NormalEndHeaderParseStream.
|
||||
// If true, after the message body is downloaded in NormalEndMsgWriteStream.
|
||||
//
|
||||
// In NormalEndHeaderParseStream, the message has not been added to the
|
||||
// database, and it is important that database notifications and count
|
||||
// updates do not occur. In NormalEndMsgWriteStream, the message has been
|
||||
// added to the database, and database notifications and count updates
|
||||
// should be performed.
|
||||
//
|
||||
|
||||
NS_ENSURE_ARG_POINTER(applyMore);
|
||||
|
||||
nsMsgRuleActionType actionType;
|
||||
|
@ -3277,7 +3358,9 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
#endif
|
||||
|
||||
nsCOMPtr<nsIMsgDBHdr> msgHdr;
|
||||
if (m_msgParser)
|
||||
if (m_filterListRequiresBody)
|
||||
GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
|
||||
else if (m_msgParser)
|
||||
m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
|
||||
if (!msgHdr)
|
||||
return NS_ERROR_NULL_POINTER; //fatal error, cannot apply filters
|
||||
|
@ -3339,7 +3422,8 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
}
|
||||
else // (!deleteToTrash)
|
||||
{
|
||||
msgHdr->OrFlags(nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted, &newFlags);
|
||||
mDatabase->MarkHdrRead(msgHdr, PR_TRUE, nsnull);
|
||||
mDatabase->MarkImapDeleted(msgKey, PR_TRUE, nsnull);
|
||||
StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, PR_TRUE,
|
||||
&msgKey, 1, nsnull);
|
||||
m_msgMovedByFilter = PR_TRUE; // this will prevent us from adding the header to the db.
|
||||
|
@ -3359,8 +3443,8 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
|
||||
if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
|
||||
{
|
||||
msgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::MDNReportNeeded);
|
||||
msgHdr->OrFlags(nsMsgMessageFlags::MDNReportSent, &newFlags);
|
||||
mDatabase->MarkMDNNeeded(msgKey, PR_FALSE, nsnull);
|
||||
mDatabase->MarkMDNSent(msgKey, PR_TRUE, nsnull);
|
||||
}
|
||||
nsresult err = MoveIncorporatedMessage(msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
|
||||
if (NS_SUCCEEDED(err))
|
||||
|
@ -3383,8 +3467,8 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
msgHdr->GetFlags(&msgFlags);
|
||||
if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
|
||||
{
|
||||
msgHdr->SetFlags(msgFlags & ~nsMsgMessageFlags::MDNReportNeeded);
|
||||
msgHdr->OrFlags(nsMsgMessageFlags::MDNReportSent, &newFlags);
|
||||
mDatabase->MarkMDNNeeded(msgKey, PR_FALSE, nsnull);
|
||||
mDatabase->MarkMDNSent(msgKey, PR_TRUE, nsnull);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
|
||||
|
@ -3418,26 +3502,56 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
}
|
||||
break;
|
||||
case nsMsgFilterAction::KillThread:
|
||||
case nsMsgFilterAction::WatchThread:
|
||||
{
|
||||
nsCOMPtr <nsIMsgThread> msgThread;
|
||||
nsMsgKey threadKey;
|
||||
mDatabase->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread));
|
||||
if (msgThread)
|
||||
{
|
||||
msgThread->GetThreadKey(&threadKey);
|
||||
if (actionType == nsMsgFilterAction::KillThread)
|
||||
mDatabase->MarkThreadIgnored(msgThread, threadKey, PR_TRUE, nsnull);
|
||||
else
|
||||
mDatabase->MarkThreadWatched(msgThread, threadKey, PR_TRUE, nsnull);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actionType == nsMsgFilterAction::KillThread)
|
||||
msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
|
||||
else
|
||||
msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Watched);
|
||||
}
|
||||
if (actionType == nsMsgFilterAction::KillThread)
|
||||
{
|
||||
mDatabase->MarkHdrRead(msgHdr, PR_TRUE, nsnull);
|
||||
StoreImapFlags(kImapMsgSeenFlag, PR_TRUE, &msgKey, 1, nsnull);
|
||||
msgIsNew = PR_FALSE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case nsMsgFilterAction::KillSubthread:
|
||||
msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
|
||||
break;
|
||||
case nsMsgFilterAction::WatchThread:
|
||||
msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
|
||||
{
|
||||
mDatabase->MarkHeaderKilled(msgHdr, PR_TRUE, nsnull);
|
||||
mDatabase->MarkHdrRead(msgHdr, PR_TRUE, nsnull);
|
||||
StoreImapFlags(kImapMsgSeenFlag, PR_TRUE, &msgKey, 1, nsnull);
|
||||
msgIsNew = PR_FALSE;
|
||||
}
|
||||
break;
|
||||
case nsMsgFilterAction::ChangePriority:
|
||||
{
|
||||
nsMsgPriorityValue filterPriority;
|
||||
nsMsgPriorityValue filterPriority; // a PRInt32
|
||||
filterAction->GetPriority(&filterPriority);
|
||||
msgHdr->SetPriority(filterPriority);
|
||||
mDatabase->SetUint32PropertyByHdr(msgHdr, "priority",
|
||||
static_cast<PRUint32>(filterPriority));
|
||||
}
|
||||
break;
|
||||
case nsMsgFilterAction::Label:
|
||||
{
|
||||
nsMsgLabelValue filterLabel;
|
||||
filterAction->GetLabel(&filterLabel);
|
||||
msgHdr->SetLabel(filterLabel);
|
||||
mDatabase->SetUint32PropertyByHdr(msgHdr, "label",
|
||||
static_cast<PRUint32>(filterLabel));
|
||||
StoreImapFlags((filterLabel << 9), PR_TRUE, &msgKey, 1, nsnull);
|
||||
}
|
||||
break;
|
||||
|
@ -3469,8 +3583,28 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
NS_ASSERTION(keysToClassify, "error getting key bucket");
|
||||
if (keysToClassify)
|
||||
keysToClassify->AppendElement(msgKey);
|
||||
if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE)
|
||||
if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE)
|
||||
{
|
||||
msgIsNew = PR_FALSE;
|
||||
mDatabase->MarkHdrNotNew(msgHdr, nsnull);
|
||||
// nsMsgDBFolder::SendFlagNotifications by the call to
|
||||
// SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
|
||||
// only if the message is also read and database notifications
|
||||
// are active, but we are not going to mark it read in this
|
||||
// action, preferring to leave the choice to the user.
|
||||
// So correct numNewMessages.
|
||||
if (m_filterListRequiresBody)
|
||||
{
|
||||
msgHdr->GetFlags(&msgFlags);
|
||||
if (!(msgFlags & nsMsgMessageFlags::Read))
|
||||
{
|
||||
PRInt32 numNewMessages;
|
||||
GetNumNewMessages(PR_FALSE, &numNewMessages);
|
||||
SetNumNewMessages(--numNewMessages);
|
||||
SetHasNewMessages(numNewMessages != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -3529,6 +3663,10 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
|
||||
customAction->Apply(messageArray, value, nsnull,
|
||||
nsMsgFilterType::InboxRule, msgWindow);
|
||||
// allow custom action to affect new
|
||||
msgHdr->GetFlags(&msgFlags);
|
||||
if (!(msgFlags & nsMsgMessageFlags::New))
|
||||
msgIsNew = PR_FALSE;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -3548,7 +3686,13 @@ NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter, nsIMsgWindo
|
|||
{
|
||||
PRInt32 numNewMessages;
|
||||
GetNumNewMessages(PR_FALSE, &numNewMessages);
|
||||
SetNumNewMessages(numNewMessages - 1);
|
||||
// When database notifications are active, new counts will be reset
|
||||
// to zero in nsMsgDBFolder::SendFlagNotifications by the call to
|
||||
// SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
|
||||
if (!m_filterListRequiresBody)
|
||||
SetNumNewMessages(--numNewMessages);
|
||||
if (mDatabase)
|
||||
mDatabase->MarkHdrNotNew(msgHdr, nsnull);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -4297,6 +4441,61 @@ nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage,
|
|||
if (m_offlineHeader)
|
||||
EndNewOfflineMessage();
|
||||
m_curMsgUid = uidOfMessage;
|
||||
|
||||
// Apply filter now if it needed a body
|
||||
if (m_filterListRequiresBody)
|
||||
{
|
||||
if (m_filterList)
|
||||
{
|
||||
nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
|
||||
GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr));
|
||||
GetMoveCoalescer();
|
||||
nsCOMPtr<nsIMsgWindow> msgWindow;
|
||||
if (imapUrl)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
|
||||
msgUrl = do_QueryInterface(imapUrl, &rv);
|
||||
if (msgUrl && NS_SUCCEEDED(rv))
|
||||
msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
|
||||
}
|
||||
m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
|
||||
this, mDatabase, nsnull, nsnull, this,
|
||||
msgWindow, nsnull);
|
||||
NotifyFolderEvent(mFiltersAppliedAtom);
|
||||
}
|
||||
// Process filter plugins and other items normally done at the end of
|
||||
// HeaderFetchCompleted.
|
||||
PRBool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
|
||||
PlaybackCoalescedOperations();
|
||||
|
||||
PRBool filtersRun;
|
||||
CallFilterPlugins(nsnull, &filtersRun);
|
||||
PRInt32 numNewBiffMsgs = 0;
|
||||
if (m_performingBiff)
|
||||
GetNumNewMessages(PR_FALSE, &numNewBiffMsgs);
|
||||
|
||||
if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
|
||||
(!pendingMoves || !ShowPreviewText()))
|
||||
{
|
||||
// If we are performing biff for this folder, tell the
|
||||
// stand-alone biff about the new high water mark
|
||||
// We must ensure that the server knows that we are performing biff.
|
||||
// Otherwise the stand-alone biff won't fire.
|
||||
nsCOMPtr<nsIMsgIncomingServer> server;
|
||||
if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
|
||||
server->SetPerformingBiff(PR_TRUE);
|
||||
|
||||
SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
|
||||
if (server)
|
||||
server->SetPerformingBiff(PR_FALSE);
|
||||
m_performingBiff = PR_FALSE;
|
||||
}
|
||||
|
||||
if (m_filterList)
|
||||
(void)m_filterList->FlushLogIfNecessary();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -5370,6 +5569,8 @@ nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol)
|
|||
{
|
||||
imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
|
||||
imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders);
|
||||
if (m_filterListRequiresBody)
|
||||
autoDownloadNewHeaders = PR_TRUE;
|
||||
}
|
||||
PRBool notifiedBodies = PR_FALSE;
|
||||
if (m_downloadingFolderForOfflineUse || autoSyncOfflineStores ||
|
||||
|
@ -5410,14 +5611,14 @@ nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol)
|
|||
}
|
||||
}
|
||||
|
||||
// delay calling plugins if filter application is also delayed
|
||||
if (!m_filterListRequiresBody)
|
||||
{
|
||||
PRBool filtersRun;
|
||||
CallFilterPlugins(msgWindow, &filtersRun);
|
||||
if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
|
||||
(!pendingMoves || !ShowPreviewText()))
|
||||
{
|
||||
if (!pendingMoves)
|
||||
SetHasNewMessages(PR_TRUE);
|
||||
|
||||
// If we are performing biff for this folder, tell the
|
||||
// stand-alone biff about the new high water mark
|
||||
// We must ensure that the server knows that we are performing biff.
|
||||
|
@ -5434,6 +5635,7 @@ nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol)
|
|||
|
||||
if (m_filterList)
|
||||
(void)m_filterList->FlushLogIfNecessary();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -507,6 +507,7 @@ protected:
|
|||
|
||||
// offline imap support
|
||||
PRBool m_downloadingFolderForOfflineUse;
|
||||
PRBool m_filterListRequiresBody;
|
||||
|
||||
// auto-sync (preemptive download) support
|
||||
nsRefPtr<nsAutoSyncState> m_autoSyncStateObj;
|
||||
|
|
|
@ -769,6 +769,8 @@ nsresult nsImapProtocol::SetupWithUrl(nsIURI * aURL, nsISupports* aConsumer)
|
|||
m_mockChannel->GetChannelContext(getter_AddRefs(m_channelContext));
|
||||
nsCOMPtr<nsIMsgWindow> msgWindow;
|
||||
GetMsgWindow(getter_AddRefs(msgWindow));
|
||||
if (!msgWindow)
|
||||
GetTopmostMsgWindow(getter_AddRefs(msgWindow));
|
||||
if (msgWindow)
|
||||
{
|
||||
nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor;
|
||||
|
|
|
@ -0,0 +1,774 @@
|
|||
/*
|
||||
* This file tests imap filter actions, particularly as affected by the
|
||||
* addition of body searches in bug 127250. Actions that involves sending
|
||||
* mail are not tested. The tests check various counts, and the effects
|
||||
* on the message database of the filters. Effects on IMAP server
|
||||
* flags, if any, are not tested.
|
||||
*
|
||||
* Original author: Kent James <kent@caspia.com>
|
||||
* adapted from test_localToImapFilter.js
|
||||
*/
|
||||
|
||||
Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/folderUtils.jsm");
|
||||
|
||||
const nsMsgSearchScope = Ci.nsMsgSearchScope;
|
||||
const nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
|
||||
const nsMsgSearchOp = Ci.nsMsgSearchOp;
|
||||
const Is = nsMsgSearchOp.Is;
|
||||
const Contains = nsMsgSearchOp.Contains;
|
||||
const Subject = nsMsgSearchAttrib.Subject;
|
||||
const Body = nsMsgSearchAttrib.Body;
|
||||
|
||||
// Globals
|
||||
var gIMAPDaemon; // the imap fake server daemon
|
||||
var gServer; // the imap fake server
|
||||
var gIMAPIncomingServer; // nsIMsgIncomingServer for the imap server
|
||||
var gRootFolder; // root message folder for the imap server
|
||||
var gIMAPInbox; // imap inbox message folder
|
||||
var gIMAPTrashFolder; // imap trash message folder
|
||||
var gSubfolder; // a local message folder used as a target for moves and copies
|
||||
var gLastKey; // the last message key
|
||||
var gFilter; // a message filter with a subject search
|
||||
var gAction; // current message action (reused)
|
||||
var gBodyFilter; // a message filter with a body search
|
||||
var gInboxListener; // database listener object
|
||||
var gContinueListener; // what listener is used to continue the test?
|
||||
var gHeader; // the current message db header
|
||||
var gChecks; // the function that will be used to check the results of the filter
|
||||
var gInboxCount; // the previous number of messages in the Inbox
|
||||
var gSubfolderCount; // the previous number of messages in the subfolder
|
||||
var gMoveCallbackCount; // the number of callbacks from the move listener
|
||||
const gMessage = "draft1"; // message file used as the test message
|
||||
|
||||
// subject of the test message
|
||||
const gMessageSubject = "Hello, did you receive my bugmail?";
|
||||
|
||||
// a string in the body of the test message
|
||||
const gMessageInBody = "an HTML message";
|
||||
|
||||
// various object references
|
||||
const gCopyService = Cc["@mozilla.org/messenger/messagecopyservice;1"]
|
||||
.getService(Ci.nsIMsgCopyService);
|
||||
const gDbService = Components.classes["@mozilla.org/msgDatabase/msgDBService;1"]
|
||||
.getService(Components.interfaces.nsIMsgDBService);
|
||||
const gIMAPService = Cc["@mozilla.org/messenger/messageservice;1?type=imap"]
|
||||
.getService(Ci.nsIMsgMessageService);
|
||||
const gMailSession = Cc["@mozilla.org/messenger/services/session;1"]
|
||||
.getService(Ci.nsIMsgMailSession);
|
||||
const kFiltersAppliedAtom = Cc["@mozilla.org/atom-service;1"]
|
||||
.getService(Ci.nsIAtomService)
|
||||
.getAtom("FiltersApplied");
|
||||
const kDeleteOrMoveMsgCompleted = Cc["@mozilla.org/atom-service;1"]
|
||||
.getService(Ci.nsIAtomService)
|
||||
.getAtom("DeleteOrMoveMsgCompleted");
|
||||
|
||||
// Definition of tests. The test function name is the filter action
|
||||
// being tested, with "Body" appended to tests that use delayed
|
||||
// application of filters due to a body search
|
||||
const gTestArray =
|
||||
[ // The initial tests do not result in new messages added.
|
||||
function MoveToFolder() {
|
||||
gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
|
||||
gAction.targetFolderUri = gSubfolder.URI;
|
||||
gChecks = function checkMoveToFolder() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
do_check_eq(gSubfolderCount + 1, folderCount(gSubfolder));
|
||||
// no net messages were added to the inbox
|
||||
do_check_eq(gInboxCount, folderCount(gIMAPInbox));
|
||||
}
|
||||
gInboxCount = folderCount(gIMAPInbox);
|
||||
gSubfolderCount = folderCount(gSubfolder);
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function MoveToFolderBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
|
||||
gAction.targetFolderUri = gSubfolder.URI;
|
||||
gChecks = function checkMoveToFolderBody() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
do_check_eq(gSubfolderCount + 1, folderCount(gSubfolder));
|
||||
// no net messsages were added to the inbox
|
||||
do_check_eq(gInboxCount, folderCount(gIMAPInbox));
|
||||
}
|
||||
gInboxCount = folderCount(gIMAPInbox);
|
||||
gSubfolderCount = folderCount(gSubfolder);
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function MarkRead() {
|
||||
gAction.type = Ci.nsMsgFilterAction.MarkRead;
|
||||
gChecks = function checks() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
do_check_true(gHeader.isRead);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function MarkReadBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.MarkRead;
|
||||
gChecks = function checkMarkRead() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
do_check_true(gHeader.isRead);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
|
||||
},
|
||||
function KillThread() {
|
||||
gAction.type = Ci.nsMsgFilterAction.KillThread;
|
||||
gChecks = function checkKillThread() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
let thread = db().GetThreadContainingMsgHdr(gHeader);
|
||||
do_check_neq(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function KillThreadBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.KillThread;
|
||||
gChecks = function checkKillThread() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
let thread = db().GetThreadContainingMsgHdr(gHeader);
|
||||
do_check_neq(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function KillSubthread() {
|
||||
gAction.type = Ci.nsMsgFilterAction.KillSubthread;
|
||||
gChecks = function checkKillSubthread() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
do_check_neq(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function KillSubthreadBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.KillSubthread;
|
||||
gChecks = function checkKillSubthreadBody() {
|
||||
testCounts(false, 0, 0, 0);
|
||||
do_check_neq(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
// this tests for marking message as junk
|
||||
function JunkScore() {
|
||||
gAction.type = Ci.nsMsgFilterAction.JunkScore;
|
||||
gAction.junkScore = 100;
|
||||
gChecks = function checkJunkScore() {
|
||||
// marking as junk resets new but not unread
|
||||
testCounts(false, 1, 0, 0);
|
||||
do_check_eq(gHeader.getStringProperty("junkscore"), "100");
|
||||
do_check_eq(gHeader.getStringProperty("junkscoreorigin"), "filter");
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
// this tests for marking message as junk
|
||||
function JunkScoreBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.JunkScore;
|
||||
gAction.junkScore = 100;
|
||||
gChecks = function checkJunkScoreBody() {
|
||||
// marking as junk resets new but not unread
|
||||
testCounts(false, 1, 0, 0);
|
||||
do_check_eq(gHeader.getStringProperty("junkscore"), "100");
|
||||
do_check_eq(gHeader.getStringProperty("junkscoreorigin"), "filter");
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
|
||||
// The remaining tests add new messages
|
||||
function WatchThread() {
|
||||
gAction.type = Ci.nsMsgFilterAction.WatchThread;
|
||||
gChecks = function checkWatchThread() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
let thread = db().GetThreadContainingMsgHdr(gHeader);
|
||||
do_check_neq(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function WatchThreadBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.WatchThread;
|
||||
gChecks = function checkWatchThreadBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
let thread = db().GetThreadContainingMsgHdr(gHeader);
|
||||
do_check_neq(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function MarkFlagged() {
|
||||
gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
|
||||
gChecks = function checkMarkFlagged() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_true(gHeader.isFlagged);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function MarkFlaggedBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
|
||||
gChecks = function checkMarkFlaggedBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_true(gHeader.isFlagged);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function ChangePriority() {
|
||||
gAction.type = Ci.nsMsgFilterAction.ChangePriority;
|
||||
gAction.priority = Ci.nsMsgPriority.highest;
|
||||
gChecks = function checkChangePriority() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(Ci.nsMsgPriority.highest, gHeader.priority);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function ChangePriorityBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.ChangePriority;
|
||||
gAction.priority = Ci.nsMsgPriority.highest;
|
||||
gChecks = function checkChangePriorityBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(Ci.nsMsgPriority.highest, gHeader.priority);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function Label() {
|
||||
gAction.type = Ci.nsMsgFilterAction.Label;
|
||||
gAction.label = 2;
|
||||
gChecks = function checkLabel() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(2, gHeader.label);
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function LabelBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.Label;
|
||||
gAction.label = 3;
|
||||
gChecks = function checkLabelBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(3, gHeader.label);
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function AddTag() {
|
||||
gAction.type = Ci.nsMsgFilterAction.AddTag;
|
||||
gAction.strValue = "TheTag";
|
||||
gChecks = function checkAddTag() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(gHeader.getStringProperty("keywords"), "TheTag");
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function AddTagBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.AddTag;
|
||||
gAction.strValue = "TheTag2";
|
||||
gChecks = function checkAddTagBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(gHeader.getStringProperty("keywords"), "TheTag2");
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
// this tests for marking message as good
|
||||
function JunkScoreAsGood() {
|
||||
gAction.type = Ci.nsMsgFilterAction.JunkScore;
|
||||
gAction.junkScore = 0;
|
||||
gChecks = function checkJunkScore() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(gHeader.getStringProperty("junkscore"), "0");
|
||||
do_check_eq(gHeader.getStringProperty("junkscoreorigin"), "filter");
|
||||
}
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
// this tests for marking message as good
|
||||
function JunkScoreAsGoodBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.JunkScore;
|
||||
gAction.junkScore = 0;
|
||||
gChecks = function checkJunkScoreBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(gHeader.getStringProperty("junkscore"), "0");
|
||||
do_check_eq(gHeader.getStringProperty("junkscoreorigin"), "filter");
|
||||
}
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
function CopyToFolder() {
|
||||
gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
|
||||
gAction.targetFolderUri = gSubfolder.URI;
|
||||
gChecks = function checkCopyToFolder() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(gInboxCount + 1, folderCount(gIMAPInbox));
|
||||
do_check_eq(gSubfolderCount + 1, folderCount(gSubfolder));
|
||||
}
|
||||
gInboxCount = folderCount(gIMAPInbox);
|
||||
gSubfolderCount = folderCount(gSubfolder);
|
||||
setupTest(gFilter, gAction);
|
||||
},
|
||||
function CopyToFolderBody() {
|
||||
gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
|
||||
gAction.targetFolderUri = gSubfolder.URI;
|
||||
gChecks = function checkCopyToFolderBody() {
|
||||
testCounts(true, 1, 1, 1);
|
||||
do_check_eq(gInboxCount + 1, folderCount(gIMAPInbox));
|
||||
do_check_eq(gSubfolderCount + 1, folderCount(gSubfolder));
|
||||
}
|
||||
gInboxCount = folderCount(gIMAPInbox);
|
||||
gSubfolderCount = folderCount(gSubfolder);
|
||||
setupTest(gBodyFilter, gAction);
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
function run_test()
|
||||
{
|
||||
// This is before any of the actual tests, so...
|
||||
gTest = 0;
|
||||
|
||||
// Add a listener.
|
||||
gIMAPDaemon = new imapDaemon();
|
||||
gServer = makeServer(gIMAPDaemon, "");
|
||||
|
||||
gIMAPIncomingServer = createLocalIMAPServer();
|
||||
|
||||
if (!gLocalInboxFolder)
|
||||
loadLocalMailAccount();
|
||||
|
||||
// We need an identity so that updateFolder doesn't fail
|
||||
let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
|
||||
.getService(Ci.nsIMsgAccountManager);
|
||||
let localAccount = acctMgr.createAccount();
|
||||
let identity = acctMgr.createIdentity();
|
||||
localAccount.addIdentity(identity);
|
||||
localAccount.defaultIdentity = identity;
|
||||
localAccount.incomingServer = gLocalIncomingServer;
|
||||
acctMgr.defaultAccount = localAccount;
|
||||
|
||||
// Let's also have another account, using the same identity
|
||||
let imapAccount = acctMgr.createAccount();
|
||||
imapAccount.addIdentity(identity);
|
||||
imapAccount.defaultIdentity = identity;
|
||||
imapAccount.incomingServer = gIMAPIncomingServer;
|
||||
|
||||
// The server doesn't support more than one connection
|
||||
let prefBranch = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
prefBranch.setIntPref("mail.server.default.max_cached_connections", 1);
|
||||
// We aren't interested in downloading messages automatically
|
||||
prefBranch.setBoolPref("mail.server.default.download_on_biff", false);
|
||||
prefBranch.setBoolPref("mail.biff.play_sound", false);
|
||||
prefBranch.setBoolPref("mail.biff.show_alert", false);
|
||||
prefBranch.setBoolPref("mail.biff.show_tray_icon", false);
|
||||
prefBranch.setBoolPref("mail.biff.animate_dock_icon", false);
|
||||
|
||||
gSubfolder = gLocalIncomingServer.rootFolder.addSubfolder("Subfolder");
|
||||
gIMAPIncomingServer.performExpand(null);
|
||||
|
||||
gRootFolder = gIMAPIncomingServer.rootFolder;
|
||||
gIMAPInbox = gRootFolder.getChildNamed("INBOX");
|
||||
gIMAPMailbox = gIMAPDaemon.getMailbox("INBOX");
|
||||
dump("gIMAPInbox uri = " + gIMAPInbox.URI + "\n");
|
||||
msgImapFolder = gIMAPInbox.QueryInterface(Ci.nsIMsgImapMailFolder);
|
||||
// these hacks are required because we've created the inbox before
|
||||
// running initial folder discovery, and adding the folder bails
|
||||
// out before we set it as verified online, so we bail out, and
|
||||
// then remove the INBOX folder since it's not verified.
|
||||
msgImapFolder.hierarchyDelimiter = '/';
|
||||
msgImapFolder.verifiedAsOnlineFolder = true;
|
||||
|
||||
// Create a non-body filter.
|
||||
let filterList = gIMAPIncomingServer.getFilterList(null);
|
||||
gFilter = filterList.createFilter("subject");
|
||||
let searchTerm = gFilter.createTerm();
|
||||
searchTerm.attrib = Subject;
|
||||
searchTerm.op = Is;
|
||||
var value = searchTerm.value;
|
||||
value.attrib = Subject;
|
||||
value.str = gMessageSubject;
|
||||
searchTerm.value = value;
|
||||
searchTerm.booleanAnd = false;
|
||||
gFilter.appendTerm(searchTerm);
|
||||
gFilter.enabled = true;
|
||||
|
||||
// Create a filter with a body term that that forces delayed application of
|
||||
// filters until after body download.
|
||||
gBodyFilter = filterList.createFilter("body");
|
||||
searchTerm = gBodyFilter.createTerm();
|
||||
searchTerm.attrib = Body;
|
||||
searchTerm.op = Contains;
|
||||
value = searchTerm.value;
|
||||
value.attrib = Body;
|
||||
value.str = gMessageInBody;
|
||||
searchTerm.value = value;
|
||||
searchTerm.booleanAnd = false;
|
||||
gBodyFilter.appendTerm(searchTerm);
|
||||
gBodyFilter.enabled = true;
|
||||
|
||||
// an action that can be modified by tests
|
||||
gAction = gFilter.createAction();
|
||||
|
||||
gMailSession.AddFolderListener(FolderListener, Ci.nsIFolderListener.event);
|
||||
|
||||
// "Master" do_test_pending(), paired with a do_test_finished() at the end of
|
||||
// all the operations.
|
||||
do_test_pending();
|
||||
|
||||
//start first test
|
||||
gCurTestNum = 1;
|
||||
doTest();
|
||||
}
|
||||
|
||||
/*
|
||||
* functions used to support test setup and execution
|
||||
*/
|
||||
|
||||
// if the source matches the listener used to continue a test,
|
||||
// run the test checks, and start the next test.
|
||||
function testContinue(source)
|
||||
{
|
||||
if (gContinueListener === source)
|
||||
{
|
||||
if (gContinueListener == kDeleteOrMoveMsgCompleted &&
|
||||
gAction.type == Ci.nsMsgFilterAction.MoveToFolder)
|
||||
{
|
||||
// Moves give 2 events, just use the second.
|
||||
gMoveCallbackCount++;
|
||||
if (gMoveCallbackCount != 2)
|
||||
return;
|
||||
}
|
||||
if (gChecks)
|
||||
gChecks();
|
||||
gCurTestNum++;
|
||||
do_timeout(100, "doTest();");
|
||||
}
|
||||
}
|
||||
|
||||
// basic preparation done for each test
|
||||
function setupTest(aFilter, aAction)
|
||||
{
|
||||
if (aAction &&
|
||||
((aAction.type == Ci.nsMsgFilterAction.CopyToFolder) ||
|
||||
(aAction.type == Ci.nsMsgFilterAction.MoveToFolder)))
|
||||
gContinueListener = kDeleteOrMoveMsgCompleted;
|
||||
else if (aFilter === gBodyFilter)
|
||||
gContinueListener = kFiltersAppliedAtom;
|
||||
else
|
||||
gContinueListener = URLListener;
|
||||
let filterList = gIMAPIncomingServer.getFilterList(null);
|
||||
while (filterList.filterCount)
|
||||
filterList.removeFilterAt(0);
|
||||
if (aFilter)
|
||||
{
|
||||
aFilter.clearActionList();
|
||||
if (aAction) {
|
||||
aFilter.appendAction(aAction);
|
||||
filterList.insertFilterAt(0, aFilter);
|
||||
}
|
||||
}
|
||||
if (gInboxListener)
|
||||
{
|
||||
try {
|
||||
gIMAPInbox.msgDatabase.RemoveListener(gInboxListener);
|
||||
}
|
||||
catch(e) {}
|
||||
try {
|
||||
gDbService.UnregisterPendingListener(gInboxListener);
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
|
||||
gInboxListener = new DBListener();
|
||||
gDbService.registerPendingListener(gIMAPInbox, gInboxListener);
|
||||
gMoveCallbackCount = 0;
|
||||
gIMAPMailbox.addMessage(new imapMessage(specForFileName(gMessage),
|
||||
gIMAPMailbox.uidnext++, []));
|
||||
gIMAPInbox.updateFolderWithListener(null, URLListener);
|
||||
}
|
||||
|
||||
// run the next test
|
||||
function doTest()
|
||||
{
|
||||
test = gCurTestNum;
|
||||
if (test <= gTestArray.length)
|
||||
{
|
||||
|
||||
var testFn = gTestArray[test-1];
|
||||
dump("Doing test " + test + " " + testFn.name + "\n");
|
||||
// Set a limit of ten seconds; if the notifications haven't arrived by then there's a problem.
|
||||
do_timeout(10000, "if (gCurTestNum == "+test+") \
|
||||
do_throw('Notifications not received in 10000 ms for operation "+testFn.name+", current status is '+gCurrStatus);");
|
||||
try {
|
||||
testFn();
|
||||
} catch(ex) {
|
||||
gServer.stop();
|
||||
do_throw ('TEST FAILED ' + e);
|
||||
}
|
||||
}
|
||||
else
|
||||
do_timeout(1000, "endTest();");
|
||||
}
|
||||
|
||||
// Cleanup, null out everything, close all cached connections and stop the
|
||||
// server
|
||||
function endTest()
|
||||
{
|
||||
dump(" Exiting mail tests\n");
|
||||
if (gInboxListener)
|
||||
{
|
||||
try {
|
||||
gIMAPInbox.msgDatabase.RemoveListener(gInboxListener);
|
||||
}
|
||||
catch(e) {}
|
||||
try {
|
||||
gDbService.UnregisterPendingListener(gInboxListener);
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
gRootFolder = null;
|
||||
gIMAPInbox = null;
|
||||
gIMAPTrashFolder = null;
|
||||
gSubfolder = null;
|
||||
gServer.resetTest();
|
||||
gIMAPIncomingServer.closeCachedConnections();
|
||||
gServer.performTest();
|
||||
gServer.stop();
|
||||
let thread = gThreadManager.currentThread;
|
||||
while (thread.hasPendingEvents())
|
||||
thread.processNextEvent(true);
|
||||
|
||||
do_test_finished(); // for the one in run_test()
|
||||
}
|
||||
|
||||
/*
|
||||
* listener objects
|
||||
*/
|
||||
|
||||
// nsIFolderListener implementation
|
||||
var FolderListener = {
|
||||
OnItemEvent: function OnItemEvent(aEventFolder, aEvent) {
|
||||
dump("received folder event " + aEvent.toString() +
|
||||
" folder " + aEventFolder.name +
|
||||
"\n");
|
||||
testContinue(aEvent);
|
||||
}
|
||||
};
|
||||
|
||||
// nsIMsgCopyServiceListener implementation - runs next test when copy
|
||||
// is completed.
|
||||
var CopyListener =
|
||||
{
|
||||
OnStartCopy: function OnStartCopy() {},
|
||||
OnProgress: function OnProgress(aProgress, aProgressMax) {},
|
||||
SetMessageKey: function SetMessageKey(aKey)
|
||||
{
|
||||
gLastKey = aKey;
|
||||
},
|
||||
SetMessageId: function SetMessageId(aMessageId) {},
|
||||
OnStopCopy: function OnStopCopy(aStatus)
|
||||
{
|
||||
dump("in OnStopCopy " + gCurTestNum + "\n");
|
||||
// Check: message successfully copied.
|
||||
do_check_eq(aStatus, 0);
|
||||
// Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack
|
||||
// This can happen with a bunch of synchronous functions grouped together, and
|
||||
// can even cause tests to fail because they're still waiting for the listener
|
||||
// to return
|
||||
testContinue(this);
|
||||
}
|
||||
};
|
||||
|
||||
// nsIURLListener implementation - runs next test
|
||||
var URLListener =
|
||||
{
|
||||
OnStartRunningUrl: function OnStartRunningUrl(aURL) {},
|
||||
OnStopRunningUrl: function OnStopRunningUrl(aURL, aStatus)
|
||||
{
|
||||
dump("in OnStopRunningURL " + gCurTestNum + "\n");
|
||||
do_check_eq(aStatus, 0);
|
||||
testContinue(this);
|
||||
}
|
||||
}
|
||||
|
||||
// nsIDBChangeListener implementation. Counts of calls are kept, but not
|
||||
// currently used in the tests. Current role is to provide a reference
|
||||
// to the new message header (plus give some examples of using db listeners
|
||||
// in javascript).
|
||||
function DBListener()
|
||||
{
|
||||
this.counts = {};
|
||||
counts = this.counts;
|
||||
counts.onHdrFlagsChanged = 0;
|
||||
counts.onHdrDeleted = 0;
|
||||
counts.onHdrAdded = 0;
|
||||
counts.onParentChanged = 0;
|
||||
counts.onAnnouncerGoingAway = 0;
|
||||
counts.onReadChanged = 0;
|
||||
counts.onJunkScoreChanged = 0;
|
||||
counts.onHdrPropertyChanged = 0;
|
||||
counts.onEvent = 0;
|
||||
}
|
||||
|
||||
DBListener.prototype =
|
||||
{
|
||||
onHdrFlagsChanged:
|
||||
function onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator)
|
||||
{
|
||||
this.counts.onHdrFlagsChanged++;
|
||||
},
|
||||
|
||||
onHdrDeleted:
|
||||
function onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator)
|
||||
{
|
||||
this.counts.onHdrDeleted++;
|
||||
},
|
||||
|
||||
onHdrAdded:
|
||||
function onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator)
|
||||
{
|
||||
this.counts.onHdrAdded++;
|
||||
gHeader = aHdrChanged;
|
||||
},
|
||||
|
||||
onParentChanged:
|
||||
function onParentChanged(aKeyChanged, oldParent, newParent, aInstigator)
|
||||
{
|
||||
this.counts.onParentChanged++;
|
||||
},
|
||||
|
||||
onAnnouncerGoingAway:
|
||||
function onAnnouncerGoingAway(instigator)
|
||||
{
|
||||
if (gInboxListener)
|
||||
try {
|
||||
gIMAPInbox.msgDatabase.RemoveListener(gInboxListener);
|
||||
}
|
||||
catch (e) {dump(" listener not found\n");}
|
||||
this.counts.onAnnouncerGoingAway++;
|
||||
},
|
||||
|
||||
onReadChanged:
|
||||
function onReadChanged(aInstigator)
|
||||
{
|
||||
this.counts.onReadChanged++;
|
||||
},
|
||||
|
||||
onJunkScoreChanged:
|
||||
function onJunkScoreChanged(aInstigator)
|
||||
{
|
||||
this.counts.onJunkScoreChanged++;
|
||||
},
|
||||
|
||||
onHdrPropertyChanged:
|
||||
function onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator)
|
||||
{
|
||||
this.counts.onHdrPropertyChanged++;
|
||||
},
|
||||
|
||||
onEvent:
|
||||
function onEvent(aDB, aEvent)
|
||||
{
|
||||
this.counts.onEvent++;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* helper functions
|
||||
*/
|
||||
|
||||
// return the number of messages in a folder (and check that the
|
||||
// folder counts match the database counts)
|
||||
function folderCount(folder)
|
||||
{
|
||||
// count using the database
|
||||
let enumerator = folder.msgDatabase.EnumerateMessages();
|
||||
let dbCount = 0;
|
||||
while (enumerator.hasMoreElements())
|
||||
{
|
||||
dbCount++;
|
||||
let hdr = enumerator.getNext();
|
||||
}
|
||||
|
||||
// count using the folder
|
||||
let folderCount = folder.getTotalMessages(false);
|
||||
|
||||
// compare the two
|
||||
do_check_eq(dbCount, folderCount);
|
||||
return dbCount;
|
||||
}
|
||||
|
||||
// list all of the messages in a folder for debug
|
||||
function listMessages(folder) {
|
||||
let enumerator = folder.msgDatabase.EnumerateMessages();
|
||||
var msgCount = 0;
|
||||
dump("listing messages for " + folder.prettyName + "\n");
|
||||
while(enumerator.hasMoreElements())
|
||||
{
|
||||
msgCount++;
|
||||
let hdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
|
||||
dump(msgCount + ": " + hdr.subject + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// given a test file, return the file uri spec
|
||||
function specForFileName(aFileName)
|
||||
{
|
||||
let file = do_get_file("../../mailnews/data/" + aFileName);
|
||||
let msgfileuri = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService)
|
||||
.newFileURI(file)
|
||||
.QueryInterface(Ci.nsIFileURL);
|
||||
return msgfileuri.spec;
|
||||
}
|
||||
|
||||
// shorthand for the inbox message summary database
|
||||
function db()
|
||||
{
|
||||
return gIMAPInbox.msgDatabase;
|
||||
}
|
||||
|
||||
// This function may be used in the test array to show
|
||||
// more detailed results after a particular test.
|
||||
function showResults() {
|
||||
listMessages(gIMAPInbox);
|
||||
if (gInboxListener)
|
||||
printListener(gInboxListener);
|
||||
gCurTestNum++;
|
||||
do_timeout(100, "doTest();");
|
||||
}
|
||||
|
||||
// static variables used in testCounts
|
||||
var gPreviousUnread = 0;
|
||||
var gPreviousDbNew = 0;
|
||||
|
||||
// Test various counts.
|
||||
//
|
||||
// aHasNew: folder hasNew flag
|
||||
// aUnreadDelta: change in unread count for the folder
|
||||
// aFolderNewDelta: change in new count for the folder
|
||||
// aDbNewDelta: change in new count for the database
|
||||
//
|
||||
function testCounts(aHasNew, aUnreadDelta, aFolderNewDelta, aDbNewDelta)
|
||||
{
|
||||
try {
|
||||
let folderNew = gIMAPInbox.getNumNewMessages(false);
|
||||
let hasNew = gIMAPInbox.hasNewMessages;
|
||||
let unread = gIMAPInbox.getNumUnread(false);
|
||||
let countOut = {};
|
||||
let arrayOut = {};
|
||||
db().getNewList(countOut, arrayOut);
|
||||
let dbNew = countOut.value ? countOut.value : 0;
|
||||
let folderNewFlag = gIMAPInbox.getFlag(Ci.nsMsgFolderFlags.GotNew);
|
||||
dump(" hasNew: " + hasNew +
|
||||
" unread: " + unread +
|
||||
" folderNew: " + folderNew +
|
||||
" folderNewFlag: " + folderNewFlag +
|
||||
" dbNew: " + dbNew +
|
||||
"\n");
|
||||
do_check_eq(aHasNew, hasNew);
|
||||
do_check_eq(aUnreadDelta, unread - gPreviousUnread);
|
||||
gPreviousUnread = unread;
|
||||
// this seems to be rest for each folder update
|
||||
do_check_eq(aFolderNewDelta, folderNew);
|
||||
do_check_eq(aDbNewDelta, dbNew - gPreviousDbNew);
|
||||
gPreviousDbNew = dbNew;
|
||||
} catch (e) {dump(e);}
|
||||
}
|
||||
|
||||
// print the counts for debugging purposes in this test
|
||||
function printListener(listener)
|
||||
{
|
||||
print("DBListener counts: ");
|
||||
for (var item in listener.counts) {
|
||||
dump(item + ": " + listener.counts[item] + " ");
|
||||
}
|
||||
dump("\n");
|
||||
}
|
|
@ -2757,12 +2757,17 @@ NS_IMETHODIMP nsMsgLocalMailFolder::EndCopy(PRBool copySucceeded)
|
|||
if (txnMgr)
|
||||
txnMgr->DoTransaction(mCopyState->m_undoMsgTxn);
|
||||
}
|
||||
if (srcFolder && !mCopyState->m_isFolder)
|
||||
srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
|
||||
|
||||
(void) OnCopyCompleted(mCopyState->m_srcSupport, PR_TRUE);
|
||||
// enable the dest folder
|
||||
EnableNotifications(allMessageCountNotifications, PR_TRUE, PR_FALSE /*dbBatching*/); //dest folder doesn't need db batching
|
||||
if (srcFolder && !mCopyState->m_isFolder)
|
||||
{
|
||||
// I'm not too sure of the proper location of this event. It seems to need to be
|
||||
// after the EnableNotifications, or the folder counts can be incorrect
|
||||
// during the mDeleteOrMoveMsgCompletedAtom call.
|
||||
srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
|
||||
}
|
||||
(void) OnCopyCompleted(mCopyState->m_srcSupport, PR_TRUE);
|
||||
}
|
||||
}
|
||||
// Send the itemAdded notification in case we didn't send the itemMoveCopyCompleted notification earlier.
|
||||
|
@ -2839,11 +2844,14 @@ NS_IMETHODIMP nsMsgLocalMailFolder::EndMove(PRBool moveSucceeded)
|
|||
}
|
||||
// lets delete these all at once - much faster that way
|
||||
rv = srcFolder->DeleteMessages(mCopyState->m_messages, mCopyState->m_msgWindow, PR_TRUE, PR_TRUE, nsnull, mCopyState->m_allowUndo);
|
||||
srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom);
|
||||
AutoCompact(mCopyState->m_msgWindow);
|
||||
|
||||
// enable the dest folder
|
||||
EnableNotifications(allMessageCountNotifications, PR_TRUE, PR_FALSE /*dbBatching*/); //dest folder doesn't need db batching
|
||||
// I'm not too sure of the proper location of this event. It seems to need to be
|
||||
// after the EnableNotifications, or the folder counts can be incorrect
|
||||
// during the mDeleteOrMoveMsgCompletedAtom call.
|
||||
srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn)
|
||||
{
|
||||
|
|
|
@ -539,6 +539,8 @@ nsresult nsPop3Protocol::Initialize(nsIURI * aURL)
|
|||
{
|
||||
nsCOMPtr<nsIMsgWindow> msgwin;
|
||||
mailnewsUrl->GetMsgWindow(getter_AddRefs(msgwin));
|
||||
if (!msgwin)
|
||||
GetTopmostMsgWindow(getter_AddRefs(msgwin));
|
||||
if (msgwin)
|
||||
{
|
||||
nsCOMPtr<nsIDocShell> docshell;
|
||||
|
|
|
@ -39,6 +39,11 @@
|
|||
|
||||
// Much of the original code is taken from netwerk's httpserver implementation
|
||||
|
||||
// Make sure we execute this file exactly once
|
||||
var gMaild_js__;
|
||||
if (!gMaild_js__) {
|
||||
gMaild_js__ = true;
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
|
@ -120,6 +125,12 @@ function nsMailServer(handler) {
|
|||
this._handler = handler;
|
||||
this._readers = [];
|
||||
this._test = false;
|
||||
|
||||
/**
|
||||
* An array to hold refs to all the input streams below, so that they don't
|
||||
* get GCed
|
||||
*/
|
||||
this._inputStreams = [];
|
||||
}
|
||||
nsMailServer.prototype = {
|
||||
onSocketAccepted : function (socket, trans) {
|
||||
|
@ -130,6 +141,7 @@ nsMailServer.prototype = {
|
|||
const SEGMENT_COUNT = 1024;
|
||||
var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
|
||||
.QueryInterface(Ci.nsIAsyncInputStream);
|
||||
this._inputStreams.push(input);
|
||||
|
||||
var reader = new nsMailReader(this, this._handler, trans, this._debug);
|
||||
this._readers.push(reader);
|
||||
|
@ -526,3 +538,5 @@ function server(port, handler) {
|
|||
srv.performTest();
|
||||
return srv.playTransaction();
|
||||
}
|
||||
|
||||
} // gMaild_js__
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
* <objdir>/dist/Thunderbird{Debug}.app/Contents/MacOS/mailtest/ (on Mac OS X)
|
||||
*/
|
||||
|
||||
// Make sure we execute this file exactly once
|
||||
var gMailDirService_js__;
|
||||
if (!gMailDirService_js__) {
|
||||
gMailDirService_js__ = true;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// Declare these globally for unit tests and be done with it.
|
||||
|
@ -133,3 +138,5 @@ catch (e) {
|
|||
}
|
||||
// Always ensure the profile directory exists before we start the tests
|
||||
gProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
|
||||
|
||||
} // gMailDirService_js__
|
||||
|
|
|
@ -35,6 +35,11 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// Make sure we execute this file exactly once
|
||||
var gMailTestUtils_js__;
|
||||
if (!gMailTestUtils_js__) {
|
||||
gMailTestUtils_js__ = true;
|
||||
|
||||
// we would like for everyone to have fixIterator and toXPComArray
|
||||
Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
|
||||
|
||||
|
@ -275,3 +280,5 @@ function updateFolderAndNotify(aFolder, aCallback, aCallbackThis,
|
|||
|
||||
aFolder.updateFolder(null);
|
||||
}
|
||||
|
||||
} // gMailTestUtils_js__
|
||||
|
|
|
@ -172,7 +172,8 @@
|
|||
<menuitem id="dlContext-open"
|
||||
label="&cmd.open.label;"
|
||||
accesskey="&cmd.open.accesskey;"
|
||||
command="cmd_open"/>
|
||||
command="cmd_open"
|
||||
default="true"/>
|
||||
<menuitem id="dlContext-show"
|
||||
label="&cmd.show.label;"
|
||||
accesskey="&cmd.show.accesskey;"
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
***** END LICENSE BLOCK ***** -->
|
||||
|
||||
<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mozapps/content/preferences/preferences.css"?>
|
||||
|
||||
<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/pref/pref-download.dtd">
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
<?xml-stylesheet type="text/css" href="chrome://communicator/skin/"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://communicator/content/communicator.css"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://communicator/content/pref/prefpanels.css"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://mozapps/content/preferences/preferences.css"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://communicator/skin/prefpanels.css"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://communicator/skin/preferences.css"?>
|
||||
|
||||
|
|
|
@ -318,7 +318,7 @@ SessionStoreService.prototype = {
|
|||
let openWindows = {};
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
Array.forEach(aWindow.getBrowser().browsers, function(aBrowser) {
|
||||
delete aBrowser.parentNode.__SS_data;
|
||||
delete aBrowser.__SS_data;
|
||||
});
|
||||
openWindows[aWindow.__SSi] = true;
|
||||
});
|
||||
|
@ -377,36 +377,35 @@ SessionStoreService.prototype = {
|
|||
* Implement nsIDOMEventListener for handling various window and tab events
|
||||
*/
|
||||
handleEvent: function sss_handleEvent(aEvent) {
|
||||
var win = aEvent.currentTarget.ownerDocument.defaultView;
|
||||
switch (aEvent.type) {
|
||||
case "load":
|
||||
case "pageshow":
|
||||
this.onTabLoad(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
|
||||
this.onTabLoad(win, aEvent.currentTarget, aEvent);
|
||||
break;
|
||||
case "change":
|
||||
case "input":
|
||||
case "DOMAutoComplete":
|
||||
this.onTabInput(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget);
|
||||
this.onTabInput(win, aEvent.currentTarget);
|
||||
break;
|
||||
case "scroll":
|
||||
this.onTabScroll(aEvent.currentTarget.ownerDocument.defaultView);
|
||||
this.onTabScroll(win);
|
||||
break;
|
||||
case "TabOpen":
|
||||
case "TabClose":
|
||||
var panelID = aEvent.originalTarget.linkedPanel;
|
||||
var tabpanel = aEvent.originalTarget.ownerDocument.getElementById(panelID);
|
||||
let browser = aEvent.originalTarget.linkedBrowser;
|
||||
if (aEvent.type == "TabOpen") {
|
||||
this.onTabAdd(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
|
||||
this.onTabAdd(win, browser);
|
||||
}
|
||||
else {
|
||||
// aEvent.detail determines if the tab was closed by moving to a different window
|
||||
if (!aEvent.detail)
|
||||
this.onTabClose(aEvent.currentTarget.ownerDocument.defaultView, aEvent.originalTarget);
|
||||
this.onTabRemove(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
|
||||
this.onTabClose(win, aEvent.originalTarget);
|
||||
this.onTabRemove(win, browser);
|
||||
}
|
||||
break;
|
||||
case "TabSelect":
|
||||
var tabpanels = aEvent.currentTarget.mPanelContainer;
|
||||
this.onTabSelect(aEvent.currentTarget.ownerDocument.defaultView, tabpanels);
|
||||
this.onTabSelect(win);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -471,11 +470,10 @@ SessionStoreService.prototype = {
|
|||
}
|
||||
|
||||
var tabbrowser = aWindow.getBrowser();
|
||||
var tabpanels = tabbrowser.mPanelContainer;
|
||||
|
||||
// add tab change listeners to all already existing tabs
|
||||
for (var i = 0; i < tabpanels.childNodes.length; i++) {
|
||||
this.onTabAdd(aWindow, tabpanels.childNodes[i], true);
|
||||
for (let i = 0; i < tabbrowser.browsers.length; i++) {
|
||||
this.onTabAdd(aWindow, tabbrowser.browsers[i], true);
|
||||
}
|
||||
// notification of tab add/remove/selection
|
||||
tabbrowser.addEventListener("TabOpen", this, true);
|
||||
|
@ -511,7 +509,6 @@ SessionStoreService.prototype = {
|
|||
}
|
||||
|
||||
var tabbrowser = aWindow.getBrowser();
|
||||
var tabpanels = tabbrowser.mPanelContainer;
|
||||
|
||||
tabbrowser.removeEventListener("TabOpen", this, true);
|
||||
tabbrowser.removeEventListener("TabClose", this, true);
|
||||
|
@ -543,8 +540,8 @@ SessionStoreService.prototype = {
|
|||
this.saveStateDelayed();
|
||||
}
|
||||
|
||||
for (var i = 0; i < tabpanels.childNodes.length; i++) {
|
||||
this.onTabRemove(aWindow, tabpanels.childNodes[i], true);
|
||||
for (let i = 0; i < tabbrowser.browsers.length; i++) {
|
||||
this.onTabRemove(aWindow, tabbrowser.browsers[i], true);
|
||||
}
|
||||
|
||||
// cache the window state until the window is completely gone
|
||||
|
@ -557,18 +554,18 @@ SessionStoreService.prototype = {
|
|||
* set up listeners for a new tab
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aPanel
|
||||
* TabPanel reference
|
||||
* @param aBrowser
|
||||
* Browser reference
|
||||
* @param aNoNotification
|
||||
* bool Do not save state if we're updating an existing tab
|
||||
*/
|
||||
onTabAdd: function sss_onTabAdd(aWindow, aPanel, aNoNotification) {
|
||||
aPanel.addEventListener("load", this, true);
|
||||
aPanel.addEventListener("pageshow", this, true);
|
||||
aPanel.addEventListener("change", this, true);
|
||||
aPanel.addEventListener("input", this, true);
|
||||
aPanel.addEventListener("DOMAutoComplete", this, true);
|
||||
aPanel.addEventListener("scroll", this, true);
|
||||
onTabAdd: function sss_onTabAdd(aWindow, aBrowser, aNoNotification) {
|
||||
aBrowser.addEventListener("load", this, true);
|
||||
aBrowser.addEventListener("pageshow", this, true);
|
||||
aBrowser.addEventListener("change", this, true);
|
||||
aBrowser.addEventListener("input", this, true);
|
||||
aBrowser.addEventListener("DOMAutoComplete", this, true);
|
||||
aBrowser.addEventListener("scroll", this, true);
|
||||
|
||||
if (!aNoNotification) {
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
@ -579,20 +576,20 @@ SessionStoreService.prototype = {
|
|||
* remove listeners for a tab
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aPanel
|
||||
* TabPanel reference
|
||||
* @param aBrowser
|
||||
* Browser reference
|
||||
* @param aNoNotification
|
||||
* bool Do not save state if we're updating an existing tab
|
||||
*/
|
||||
onTabRemove: function sss_onTabRemove(aWindow, aPanel, aNoNotification) {
|
||||
aPanel.removeEventListener("load", this, true);
|
||||
aPanel.removeEventListener("pageshow", this, true);
|
||||
aPanel.removeEventListener("change", this, true);
|
||||
aPanel.removeEventListener("input", this, true);
|
||||
aPanel.removeEventListener("DOMAutoComplete", this, true);
|
||||
aPanel.removeEventListener("scroll", this, true);
|
||||
onTabRemove: function sss_onTabRemove(aWindow, aBrowser, aNoNotification) {
|
||||
aBrowser.removeEventListener("load", this, true);
|
||||
aBrowser.removeEventListener("pageshow", this, true);
|
||||
aBrowser.removeEventListener("change", this, true);
|
||||
aBrowser.removeEventListener("input", this, true);
|
||||
aBrowser.removeEventListener("DOMAutoComplete", this, true);
|
||||
aBrowser.removeEventListener("scroll", this, true);
|
||||
|
||||
delete aPanel.__SS_data;
|
||||
delete aBrowser.__SS_data;
|
||||
|
||||
if (!aNoNotification) {
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
@ -604,7 +601,7 @@ SessionStoreService.prototype = {
|
|||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aTab
|
||||
* TabPanel reference
|
||||
* Tab reference
|
||||
*/
|
||||
onTabClose: function sss_onTabClose(aWindow, aTab) {
|
||||
// notify the tabbrowser that the tab state will be retrieved for the last time
|
||||
|
@ -643,19 +640,19 @@ SessionStoreService.prototype = {
|
|||
* When a tab loads, save state.
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aPanel
|
||||
* TabPanel reference
|
||||
* @param aBrowser
|
||||
* Browser reference
|
||||
* @param aEvent
|
||||
* Event obj
|
||||
*/
|
||||
onTabLoad: function sss_onTabLoad(aWindow, aPanel, aEvent) {
|
||||
onTabLoad: function sss_onTabLoad(aWindow, aBrowser, aEvent) {
|
||||
// react on "load" and solitary "pageshow" events (the first "pageshow"
|
||||
// following "load" is too late for deleting the data caches)
|
||||
if (aEvent.type != "load" && !aEvent.persisted) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete aPanel.__SS_data;
|
||||
delete aBrowser.__SS_data;
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
||||
// attempt to update the current URL we send in a crash report
|
||||
|
@ -663,21 +660,21 @@ SessionStoreService.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Called when a tabpanel sends the "input" notification
|
||||
* Called when a browser sends the "input" notification
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aPanel
|
||||
* TabPanel reference
|
||||
* @param aBrowser
|
||||
* Browser reference
|
||||
*/
|
||||
onTabInput: function sss_onTabInput(aWindow, aPanel) {
|
||||
if (aPanel.__SS_data)
|
||||
delete aPanel.__SS_data._formDataSaved;
|
||||
onTabInput: function sss_onTabInput(aWindow, aBrowser) {
|
||||
if (aBrowser.__SS_data)
|
||||
delete aBrowser.__SS_data._formDataSaved;
|
||||
|
||||
this.saveStateDelayed(aWindow, 3000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a tabpanel sends a "scroll" notification
|
||||
* Called when a browser sends a "scroll" notification
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
*/
|
||||
|
@ -689,12 +686,10 @@ SessionStoreService.prototype = {
|
|||
* When a tab is selected, save session data
|
||||
* @param aWindow
|
||||
* Window reference
|
||||
* @param aPanels
|
||||
* TabPanel reference
|
||||
*/
|
||||
onTabSelect: function sss_onTabSelect(aWindow, aPanels) {
|
||||
onTabSelect: function sss_onTabSelect(aWindow) {
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
this._windows[aWindow.__SSi].selected = aPanels.selectedIndex;
|
||||
this._windows[aWindow.__SSi].selected = aWindow.getBrowser().tabContainer.selectedIndex;
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
||||
// attempt to update the current URL we send in a crash report
|
||||
|
@ -943,9 +938,9 @@ SessionStoreService.prototype = {
|
|||
if (!browser || !browser.currentURI)
|
||||
// can happen when calling this function right after .addTab()
|
||||
return tabData;
|
||||
else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tabStillLoading)
|
||||
else if (browser.__SS_data && browser.__SS_data._tabStillLoading)
|
||||
// use the data to be restored when the tab hasn't been completely loaded
|
||||
return browser.parentNode.__SS_data;
|
||||
return browser.__SS_data;
|
||||
|
||||
var history = null;
|
||||
try {
|
||||
|
@ -955,10 +950,10 @@ SessionStoreService.prototype = {
|
|||
|
||||
// XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
|
||||
// data even when we shouldn't (e.g. Back, different anchor)
|
||||
if (history && browser.parentNode.__SS_data &&
|
||||
browser.parentNode.__SS_data.entries[history.index] &&
|
||||
if (history && browser.__SS_data &&
|
||||
browser.__SS_data.entries[history.index] &&
|
||||
history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
|
||||
tabData = browser.parentNode.__SS_data;
|
||||
tabData = browser.__SS_data;
|
||||
tabData.index = history.index + 1;
|
||||
}
|
||||
else if (history && history.count > 0) {
|
||||
|
@ -969,7 +964,7 @@ SessionStoreService.prototype = {
|
|||
|
||||
// make sure not to cache privacy sensitive data which shouldn't get out
|
||||
if (!aFullData)
|
||||
browser.parentNode.__SS_data = tabData;
|
||||
browser.__SS_data = tabData;
|
||||
}
|
||||
else if (browser.currentURI.spec != "about:blank" ||
|
||||
browser.contentDocument.body.hasChildNodes()) {
|
||||
|
@ -1184,8 +1179,8 @@ SessionStoreService.prototype = {
|
|||
for (var i = 0; i < browsers.length; i++) {
|
||||
try {
|
||||
var tabData = this._windows[aWindow.__SSi].tabs[i];
|
||||
if (browsers[i].parentNode.__SS_data &&
|
||||
browsers[i].parentNode.__SS_data._tabStillLoading)
|
||||
if (browsers[i].__SS_data &&
|
||||
browsers[i].__SS_data._tabStillLoading)
|
||||
continue; // ignore incompletely initialized tabs
|
||||
this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
|
||||
}
|
||||
|
@ -1733,7 +1728,7 @@ SessionStoreService.prototype = {
|
|||
|
||||
// keep the data around to prevent dataloss in case
|
||||
// a tab gets closed before it's been properly restored
|
||||
browser.parentNode.__SS_data = aTabData[t];
|
||||
browser.__SS_data = aTabData[t];
|
||||
}
|
||||
|
||||
// make sure to restore the selected tab first (if any)
|
||||
|
@ -2140,7 +2135,7 @@ SessionStoreService.prototype = {
|
|||
}
|
||||
// since resizing/moving a window brings it to the foreground,
|
||||
// we might want to re-focus the last focused window
|
||||
if (this.windowToFocus) {
|
||||
if (this.windowToFocus && this.windowToFocus.content) {
|
||||
this.windowToFocus.content.focus();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -183,7 +183,7 @@ function onLoad()
|
|||
gStatusBar = document.getElementById("statusbar-icon");
|
||||
gStatusText = document.getElementById("statusText");
|
||||
|
||||
updateButtons();
|
||||
gFilterTree.view = gFilterTreeView;
|
||||
|
||||
// get the selected server if it can have filters.
|
||||
var firstItem = getSelectedServerForFilters();
|
||||
|
@ -194,11 +194,13 @@ function onLoad()
|
|||
if (!firstItem)
|
||||
firstItem = getServerThatCanHaveFilters();
|
||||
|
||||
if (firstItem) {
|
||||
if (firstItem)
|
||||
selectServer(firstItem);
|
||||
}
|
||||
else
|
||||
updateButtons();
|
||||
|
||||
gFilterTree.view = gFilterTreeView;
|
||||
// Focus the list.
|
||||
gFilterTree.focus();
|
||||
|
||||
window.tryToClose = onFilterClose;
|
||||
}
|
||||
|
@ -273,6 +275,10 @@ function setServer(uri)
|
|||
//Calling getFilterList will detect any errors in rules.dat, backup the file, and alert the user
|
||||
gFilterTreeView.filterList = msgFolder.getEditableFilterList(gFilterListMsgWindow);
|
||||
|
||||
// Select the first item in the list, if there is one.
|
||||
if (gFilterTreeView.rowCount)
|
||||
gFilterTreeView.selection.select(0);
|
||||
|
||||
// this will get the deferred to account root folder, if server is deferred
|
||||
msgFolder = msgFolder.server.rootMsgFolder;
|
||||
var rootFolderUri = msgFolder.URI;
|
||||
|
|
|
@ -612,8 +612,9 @@
|
|||
this.tabInfo.splice(iTab, 1);
|
||||
tabInfo.mode.tabs.splice(tabInfo.mode.tabs.indexOf(tabInfo), 1);
|
||||
this.tabContainer.removeChild(aTabNode);
|
||||
--numTabs;
|
||||
if (this.tabContainer.selectedIndex == -1)
|
||||
this.tabContainer.selectedIndex = (iTab == --numTabs) ? iTab - 1 : iTab;
|
||||
this.tabContainer.selectedIndex = (iTab == numTabs) ? iTab - 1 : iTab;
|
||||
if (this.currentTabInfo == tabInfo)
|
||||
this.updateCurrentTab();
|
||||
|
||||
|
|
|
@ -1,11 +1,3 @@
|
|||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
||||
function url(spec) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
|
||||
return ios.newURI(spec, null, null);
|
||||
}
|
||||
|
||||
var gPageA = null;
|
||||
var gPageB = null;
|
||||
|
||||
|
@ -18,41 +10,55 @@ var gTabMoveCount = 0;
|
|||
var gPageLoadCount = 0;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
// nsIFocusManager is not available on MOZILLA_1_9_1
|
||||
if ("nsIFocusManager" in Ci) {
|
||||
if (Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager)
|
||||
.activeWindow != window) {
|
||||
setTimeout(test, 0);
|
||||
window.focus();
|
||||
return;
|
||||
}
|
||||
} else if (!document.hasFocus()) {
|
||||
setTimeout(test, 0);
|
||||
window.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var windows = Application.windows;
|
||||
ok(windows, "Check access to browser windows");
|
||||
ok(windows.length, "There should be at least one browser window open");
|
||||
is(windows.length, 1, "There should be one browser window open");
|
||||
|
||||
var activeWin = Application.activeWindow;
|
||||
activeWin.events.addListener("TabOpen", onTabOpen);
|
||||
activeWin.events.addListener("TabClose", onTabClose);
|
||||
activeWin.events.addListener("TabMove", onTabMove);
|
||||
|
||||
gPageA = activeWin.open(url("chrome://mochikit/content/browser/suite/smile/test/ContentA.html"));
|
||||
gPageA = activeWin.open(makeURI("chrome://mochikit/content/browser/suite/smile/test/ContentA.html"));
|
||||
gPageA.events.addListener("load", onPageAFirstLoad);
|
||||
|
||||
is(activeWin.tabs.length, 2, "Checking length of 'Browser.tabs' after opening 1 additional tab");
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
function execAfterOpen() {
|
||||
executeSoon(afterOpen);
|
||||
}
|
||||
|
||||
function onPageAFirstLoad(event) {
|
||||
gPageA.events.removeListener("load", onPageAFirstLoad);
|
||||
is(gPageA.uri.spec, event.data.uri.spec, "Checking event browser tab is equal to page A");
|
||||
|
||||
gPageB = activeWin.open(url("chrome://mochikit/content/browser/suite/smile/test/ContentB.html"));
|
||||
gPageB.events.addListener("load", execAfterOpen);
|
||||
gPageB = activeWin.open(makeURI("chrome://mochikit/content/browser/suite/smile/test/ContentB.html"));
|
||||
gPageB.events.addListener("load", delayAfterOpen);
|
||||
gPageB.focus();
|
||||
|
||||
is(activeWin.tabs.length, 3, "Checking length of 'Browser.tabs' after opening a second additional tab");
|
||||
is(activeWin.activeTab.index, gPageB.index, "Checking 'Browser.activeTab' after setting focus");
|
||||
}
|
||||
|
||||
function delayAfterOpen() {
|
||||
executeSoon(afterOpen);
|
||||
}
|
||||
|
||||
// need to wait for the url's to be refreshed during the load
|
||||
function afterOpen(event) {
|
||||
gPageB.events.removeListener("load", execAfterOpen);
|
||||
gPageB.events.removeListener("load", delayAfterOpen);
|
||||
// check actuals
|
||||
is(gPageA.uri.spec, "chrome://mochikit/content/browser/suite/smile/test/ContentA.html", "Checking 'BrowserTab.uri' after opening");
|
||||
is(gPageB.uri.spec, "chrome://mochikit/content/browser/suite/smile/test/ContentB.html", "Checking 'BrowserTab.uri' after opening");
|
||||
|
@ -69,6 +75,9 @@ function test() {
|
|||
is(test1.innerHTML, "A", "Checking content of element in content DOM");
|
||||
|
||||
// test moving tab
|
||||
is(gTabMoveCount, 0, "Checking initial tab move count");
|
||||
|
||||
// move the tab
|
||||
gPageA.moveToEnd();
|
||||
is(gPageA.index, 2, "Checking index after moving tab");
|
||||
|
||||
|
@ -102,9 +111,9 @@ function test() {
|
|||
});
|
||||
|
||||
// test loading new content with a frame into a tab
|
||||
// the event will be checked in afterClose
|
||||
// the event will be checked in onPageBLoadComplete
|
||||
gPageB.events.addListener("load", onPageBLoadWithFrames);
|
||||
gPageB.load(url("chrome://mochikit/content/browser/suite/smile/test/ContentWithFrames.html"));
|
||||
gPageB.load(makeURI("chrome://mochikit/content/browser/suite/smile/test/ContentWithFrames.html"));
|
||||
}
|
||||
|
||||
function onPageBLoadWithFrames(event) {
|
||||
|
@ -117,9 +126,9 @@ function test() {
|
|||
is(gPageLoadCount, 1, "Checking load count after loading new content with a frame");
|
||||
|
||||
// test loading new content into a tab
|
||||
// the event will be checked in onPageLoad
|
||||
// the event will be checked in onPageASecondLoad
|
||||
gPageA.events.addListener("load", onPageASecondLoad);
|
||||
gPageA.load(url("chrome://mochikit/content/browser/suite/smile/test/ContentB.html"));
|
||||
gPageA.load(makeURI("chrome://mochikit/content/browser/suite/smile/test/ContentB.html"));
|
||||
}
|
||||
|
||||
function onPageASecondLoad(event) {
|
||||
|
|
|
@ -47,33 +47,3 @@ prefwindow {
|
|||
-moz-padding-start: 8px;
|
||||
-moz-padding-end: 10px;
|
||||
}
|
||||
|
||||
/* File Field Widget */
|
||||
filefield {
|
||||
margin: 2px 4px;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.fileFieldContentBox {
|
||||
background-color: -moz-Dialog;
|
||||
}
|
||||
|
||||
.fileFieldIcon[disabled="true"] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.fileFieldIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
-moz-margin-start: 1px;
|
||||
-moz-margin-end: 4px;
|
||||
}
|
||||
|
||||
.fileFieldLabel {
|
||||
-moz-appearance: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 0px;
|
||||
}
|
||||
|
|
|
@ -49,6 +49,11 @@
|
|||
-moz-box-orient: vertical;
|
||||
}
|
||||
|
||||
.toolbarbutton-1[open],
|
||||
.toolbarbutton-1[open] > .toolbarbutton-menubutton-button {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.toolbarbutton-1[type="menu-button"] {
|
||||
-moz-box-orient: horizontal;
|
||||
padding: 0;
|
||||
|
|
|
@ -70,7 +70,8 @@
|
|||
-moz-image-region: rect(60px 29px 89px 0);
|
||||
}
|
||||
|
||||
#help-back-button:not([disabled="true"]):hover:active {
|
||||
#help-back-button:not([disabled="true"]):hover:active,
|
||||
#help-back-button[open]:not([disabled="true"]) {
|
||||
-moz-image-region: rect(60px 59px 89px 30px);
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,8 @@
|
|||
-moz-image-region: rect(90px 29px 119px 0);
|
||||
}
|
||||
|
||||
#help-forward-button:not([disabled="true"]):hover:active {
|
||||
#help-forward-button:not([disabled="true"]):hover:active,
|
||||
#help-forward-button[open]:not([disabled="true"]) {
|
||||
-moz-image-region: rect(90px 59px 119px 30px);
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,8 @@
|
|||
-moz-image-region: rect(0 29px 29px 0);
|
||||
}
|
||||
|
||||
#printButton:hover:active {
|
||||
#printButton:hover:active,
|
||||
#printButton[open] {
|
||||
-moz-image-region: rect(0 59px 29px 30px);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@
|
|||
-moz-image-region: rect(300px 29px 329px 0);
|
||||
}
|
||||
|
||||
#button-attach:hover:active {
|
||||
#button-attach:hover:active,
|
||||
#button-attach[open] {
|
||||
-moz-image-region: rect(300px 59px 329px 30px);
|
||||
}
|
||||
|
||||
|
@ -92,7 +93,8 @@
|
|||
-moz-image-region: rect(240px 29px 269px 0);
|
||||
}
|
||||
|
||||
#spellingButton:hover:active {
|
||||
#spellingButton:hover:active,
|
||||
#spellingButton[open] {
|
||||
-moz-image-region: rect(240px 59px 269px 30px);
|
||||
}
|
||||
|
||||
|
@ -105,7 +107,8 @@
|
|||
-moz-image-region: rect(210px 29px 239px 0);
|
||||
}
|
||||
|
||||
#button-save:hover:active {
|
||||
#button-save:hover:active,
|
||||
#button-save[open] {
|
||||
-moz-image-region: rect(210px 59px 239px 30px);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,8 @@
|
|||
-moz-image-region: rect(90px 29px 119px 0);
|
||||
}
|
||||
|
||||
#button-getmsg:hover:active {
|
||||
#button-getmsg:hover:active,
|
||||
#button-getmsg[open] {
|
||||
-moz-image-region: rect(90px 59px 119px 30px);
|
||||
}
|
||||
|
||||
|
@ -62,7 +63,8 @@
|
|||
-moz-image-region: rect(150px 29px 179px 0);
|
||||
}
|
||||
|
||||
#button-newmsg:hover:active {
|
||||
#button-newmsg:hover:active,
|
||||
#button-newmsg[open] {
|
||||
-moz-image-region: rect(150px 59px 179px 30px);
|
||||
}
|
||||
|
||||
|
@ -101,7 +103,8 @@
|
|||
-moz-image-region: rect(60px 29px 89px 0);
|
||||
}
|
||||
|
||||
#button-forward:hover:active {
|
||||
#button-forward:hover:active,
|
||||
#button-forward[open] {
|
||||
-moz-image-region: rect(60px 59px 89px 30px);
|
||||
}
|
||||
|
||||
|
@ -128,7 +131,8 @@
|
|||
-moz-image-region: rect(60px 29px 89px 0);
|
||||
}
|
||||
|
||||
#button-goback:hover:active {
|
||||
#button-goback:hover:active,
|
||||
#button-goback[open] {
|
||||
-moz-image-region: rect(60px 59px 89px 30px);
|
||||
}
|
||||
|
||||
|
@ -141,7 +145,8 @@
|
|||
-moz-image-region: rect(90px 29px 119px 0);
|
||||
}
|
||||
|
||||
#button-goforward:hover:active {
|
||||
#button-goforward:hover:active,
|
||||
#button-goforward[open] {
|
||||
-moz-image-region: rect(90px 59px 119px 30px);
|
||||
}
|
||||
|
||||
|
@ -154,7 +159,8 @@
|
|||
-moz-image-region: rect(180px 29px 209px 0);
|
||||
}
|
||||
|
||||
#button-next:hover:active {
|
||||
#button-next:hover:active,
|
||||
#button-next[open] {
|
||||
-moz-image-region: rect(180px 59px 209px 30px);
|
||||
}
|
||||
|
||||
|
@ -184,7 +190,8 @@ toolbarpaletteitem > #button-delete {
|
|||
-moz-image-region: rect(120px 29px 149px 0);
|
||||
}
|
||||
|
||||
#button-mark:hover:active {
|
||||
#button-mark:hover:active,
|
||||
#button-mark[open] {
|
||||
-moz-image-region: rect(120px 59px 149px 30px);
|
||||
}
|
||||
|
||||
|
@ -210,7 +217,8 @@ toolbarpaletteitem > #button-delete {
|
|||
-moz-image-region: rect(0 29px 29px 0);
|
||||
}
|
||||
|
||||
#button-print:hover:active {
|
||||
#button-print:hover:active,
|
||||
#button-print[open] {
|
||||
-moz-image-region: rect(0 59px 29px 30px);
|
||||
}
|
||||
|
||||
|
@ -238,7 +246,8 @@ toolbar[iconsize="small"] > #button-getmsg {
|
|||
-moz-image-region: rect(60px 19px 79px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-getmsg:hover:active {
|
||||
toolbar[iconsize="small"] > #button-getmsg:hover:active,
|
||||
toolbar[iconsize="small"] > #button-getmsg[open] {
|
||||
-moz-image-region: rect(60px 39px 79px 20px);
|
||||
}
|
||||
|
||||
|
@ -251,7 +260,8 @@ toolbar[iconsize="small"] > #button-newmsg {
|
|||
-moz-image-region: rect(100px 19px 119px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-newmsg:hover:active {
|
||||
toolbar[iconsize="small"] > #button-newmsg:hover:active,
|
||||
toolbar[iconsize="small"] > #button-newmsg[open] {
|
||||
-moz-image-region: rect(100px 39px 119px 20px);
|
||||
}
|
||||
|
||||
|
@ -290,7 +300,8 @@ toolbar[iconsize="small"] > #button-forward {
|
|||
-moz-image-region: rect(40px 19px 59px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-forward:hover:active {
|
||||
toolbar[iconsize="small"] > #button-forward:hover:active,
|
||||
toolbar[iconsize="small"] > #button-forward[open] {
|
||||
-moz-image-region: rect(40px 39px 59px 20px);
|
||||
}
|
||||
|
||||
|
@ -317,7 +328,8 @@ toolbar[iconsize="small"] > #button-goback {
|
|||
-moz-image-region: rect(40px 19px 59px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-goback:hover:active {
|
||||
toolbar[iconsize="small"] > #button-goback:hover:active,
|
||||
toolbar[iconsize="small"] > #button-goback[open] {
|
||||
-moz-image-region: rect(40px 39px 59px 20px);
|
||||
}
|
||||
|
||||
|
@ -330,7 +342,8 @@ toolbar[iconsize="small"] > #button-goforward {
|
|||
-moz-image-region: rect(60px 19px 79px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-goforward:hover:active {
|
||||
toolbar[iconsize="small"] > #button-goforward:hover:active,
|
||||
toolbar[iconsize="small"] > #button-goforward[open] {
|
||||
-moz-image-region: rect(60px 39px 79px 20px);
|
||||
}
|
||||
|
||||
|
@ -343,7 +356,8 @@ toolbar[iconsize="small"] > #button-next {
|
|||
-moz-image-region: rect(120px 19px 139px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-next:hover:active {
|
||||
toolbar[iconsize="small"] > #button-next:hover:active,
|
||||
toolbar[iconsize="small"] > #button-next[open] {
|
||||
-moz-image-region: rect(120px 39px 139px 20px);
|
||||
}
|
||||
|
||||
|
@ -369,7 +383,8 @@ toolbar[iconsize="small"] > #button-mark {
|
|||
-moz-image-region: rect(80px 19px 99px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-mark:hover:active {
|
||||
toolbar[iconsize="small"] > #button-mark:hover:active,
|
||||
toolbar[iconsize="small"] > #button-mark[open] {
|
||||
-moz-image-region: rect(80px 39px 99px 20px);
|
||||
}
|
||||
|
||||
|
@ -395,7 +410,8 @@ toolbar[iconsize="small"] > #button-print {
|
|||
-moz-image-region: rect(0 19px 19px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > #button-print:hover:active {
|
||||
toolbar[iconsize="small"] > #button-print:hover:active,
|
||||
toolbar[iconsize="small"] > #button-print[open] {
|
||||
-moz-image-region: rect(0 39px 19px 20px);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,8 @@
|
|||
-moz-image-region: rect(0 29px 29px 0px);
|
||||
}
|
||||
|
||||
#button-security:hover:active {
|
||||
#button-security:hover:active,
|
||||
#button-security[open] {
|
||||
-moz-image-region: rect(0 59px 29px 30px);
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,8 @@
|
|||
-moz-image-region: rect(60px 29px 89px 0);
|
||||
}
|
||||
|
||||
#back-button:hover:active {
|
||||
#back-button:hover:active,
|
||||
#back-button[open] {
|
||||
-moz-image-region: rect(60px 59px 89px 30px);
|
||||
}
|
||||
|
||||
|
@ -79,7 +80,8 @@
|
|||
-moz-image-region: rect(90px 29px 119px 0);
|
||||
}
|
||||
|
||||
#forward-button:hover:active {
|
||||
#forward-button:hover:active,
|
||||
#forward-button[open] {
|
||||
-moz-image-region: rect(90px 59px 119px 30px);
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,8 @@
|
|||
-moz-image-region: rect(0 29px 29px 0);
|
||||
}
|
||||
|
||||
#print-button:not([disabled="true"]):hover:active {
|
||||
#print-button:hover:active,
|
||||
#print-button[open] {
|
||||
-moz-image-region: rect(0 59px 29px 30px);
|
||||
}
|
||||
|
||||
|
@ -147,12 +150,11 @@ toolbar[iconsize="small"] > #back-button {
|
|||
-moz-image-region: rect(40px 19px 59px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #back-button:hover:active,
|
||||
toolbar[iconsize="small"] > #back-button:hover:active {
|
||||
toolbar[iconsize="small"] > #back-button:hover:active,
|
||||
toolbar[iconsize="small"] > #back-button[open] {
|
||||
-moz-image-region: rect(40px 39px 59px 20px);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #back-button[disabled="true"],
|
||||
toolbar[iconsize="small"] > #back-button[disabled="true"] {
|
||||
-moz-image-region: rect(40px 59px 59px 40px) !important;
|
||||
}
|
||||
|
@ -163,12 +165,11 @@ toolbar[iconsize="small"] > #forward-button {
|
|||
-moz-image-region: rect(60px 19px 79px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #forward-button:hover:active,
|
||||
toolbar[iconsize="small"] > #forward-button:hover:active {
|
||||
toolbar[iconsize="small"] > #forward-button:hover:active,
|
||||
toolbar[iconsize="small"] > #forward-button[open] {
|
||||
-moz-image-region: rect(60px 39px 79px 20px);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #forward-button[disabled="true"],
|
||||
toolbar[iconsize="small"] > #forward-button[disabled="true"] {
|
||||
-moz-image-region: rect(60px 59px 79px 40px) !important;
|
||||
}
|
||||
|
@ -179,12 +180,10 @@ toolbar[iconsize="small"] > #reload-button {
|
|||
-moz-image-region: rect(0 19px 19px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #reload-button:hover:active,
|
||||
toolbar[iconsize="small"] > #reload-button:hover:active {
|
||||
-moz-image-region: rect(0 39px 19px 20px);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #reload-button[disabled="true"],
|
||||
toolbar[iconsize="small"] > #reload-button[disabled="true"] {
|
||||
-moz-image-region: rect(0 59px 19px 40px) !important;
|
||||
}
|
||||
|
@ -195,12 +194,10 @@ toolbar[iconsize="small"] > #stop-button {
|
|||
-moz-image-region: rect(20px 19px 39px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #stop-button:hover:active,
|
||||
toolbar[iconsize="small"] > #stop-button:hover:active {
|
||||
-moz-image-region: rect(20px 39px 39px 20px);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #stop-button[disabled="true"],
|
||||
toolbar[iconsize="small"] > #stop-button[disabled="true"] {
|
||||
-moz-image-region: rect(20px 59px 39px 40px) !important;
|
||||
}
|
||||
|
@ -211,12 +208,11 @@ toolbar[iconsize="small"] > #print-button {
|
|||
-moz-image-region: rect(0 19px 19px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #print-button:hover:active,
|
||||
toolbar[iconsize="small"] > #print-button:hover:active {
|
||||
toolbar[iconsize="small"] > #print-button:hover:active,
|
||||
toolbar[iconsize="small"] > #print-button[open] {
|
||||
-moz-image-region: rect(0 39px 19px 20px);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #print-button[disabled="true"],
|
||||
toolbar[iconsize="small"] > #print-button[disabled="true"] {
|
||||
-moz-image-region: rect(0 59px 19px 40px) !important;
|
||||
}
|
||||
|
@ -227,12 +223,10 @@ toolbar[iconsize="small"] > #home-button {
|
|||
-moz-image-region: rect(80px 19px 99px 0);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #home-button:hover:active,
|
||||
toolbar[iconsize="small"] > #home-button:hover:active {
|
||||
-moz-image-region: rect(80px 39px 99px 20px);
|
||||
}
|
||||
|
||||
toolbar[iconsize="small"] > toolbarpaletteitem > #home-button[disabled="true"],
|
||||
toolbar[iconsize="small"] > #home-button[disabled="true"] {
|
||||
-moz-image-region: rect(80px 59px 99px 40px) !important;
|
||||
}
|
||||
|
|
|
@ -49,42 +49,3 @@ prefpane,
|
|||
.prefWindow-dlgbuttons {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/* File Field Widget */
|
||||
filefield {
|
||||
margin: 2px 4px;
|
||||
border: 2px solid;
|
||||
-moz-border-top-colors: #BEC3D3 #5D616E;
|
||||
-moz-border-right-colors: #F8FAFE #5D616E;
|
||||
-moz-border-bottom-colors: #F8FAFE #5D616E;
|
||||
-moz-border-left-colors: #BEC3D3 #5D616E;
|
||||
}
|
||||
|
||||
.fileFieldContentBox {
|
||||
background-color: #C7D0D9;
|
||||
}
|
||||
|
||||
filefield[disabled="true"] {
|
||||
-moz-border-top-colors: #BEC3D3 #98A5B2;
|
||||
-moz-border-right-colors: #F8FAFE #98A5B2;
|
||||
-moz-border-bottom-colors: #F8FAFE #98A5B2;
|
||||
-moz-border-left-colors: #BEC3D3 #98A5B2;
|
||||
}
|
||||
|
||||
.fileFieldIcon[disabled="true"] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.fileFieldIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
-moz-margin-start: 1px;
|
||||
-moz-margin-end: 4px;
|
||||
}
|
||||
|
||||
.fileFieldLabel {
|
||||
border: none;
|
||||
margin: 0px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is SeaMonkey filefield code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the SeaMonkey project.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Ian Neal <iann_bugzilla@blueyonder.co.uk>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||||
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
/* File Field Widget */
|
||||
filefield {
|
||||
margin: 2px 4px;
|
||||
border: 2px solid;
|
||||
-moz-border-top-colors: #BEC3D3 #5D616E;
|
||||
-moz-border-right-colors: #F8FAFE #5D616E;
|
||||
-moz-border-bottom-colors: #F8FAFE #5D616E;
|
||||
-moz-border-left-colors: #BEC3D3 #5D616E;
|
||||
}
|
||||
|
||||
.fileFieldContentBox {
|
||||
background-color: #C7D0D9;
|
||||
}
|
||||
|
||||
filefield[disabled="true"] {
|
||||
-moz-border-top-colors: #BEC3D3 #98A5B2;
|
||||
-moz-border-right-colors: #F8FAFE #98A5B2;
|
||||
-moz-border-bottom-colors: #F8FAFE #98A5B2;
|
||||
-moz-border-left-colors: #BEC3D3 #98A5B2;
|
||||
}
|
||||
|
||||
.fileFieldIcon[disabled="true"] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.fileFieldIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 1px;
|
||||
-moz-margin-start: 1px;
|
||||
-moz-margin-end: 4px;
|
||||
}
|
||||
|
||||
.fileFieldLabel {
|
||||
border: none;
|
||||
margin: 0px;
|
||||
}
|
|
@ -54,7 +54,7 @@ autorepeatbutton {
|
|||
padding: 3px;
|
||||
}
|
||||
|
||||
autorepeatbutton:hover {
|
||||
autorepeatbutton:not([disabled="true"]):hover {
|
||||
margin: 1px;
|
||||
border: 1px inset #A5B2C2;
|
||||
padding-top: 4px;
|
||||
|
@ -64,10 +64,65 @@ autorepeatbutton:hover {
|
|||
background-color: #A5B2C2;
|
||||
}
|
||||
|
||||
.autorepeatbutton-up {
|
||||
.scrollbutton-up,
|
||||
.scrollbutton-down {
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
}
|
||||
|
||||
.scrollbutton-up > .toolbarbutton-text,
|
||||
.scrollbutton-down > .toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Vertical enabled */
|
||||
.autorepeatbutton-up,
|
||||
.scrollbutton-up {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-up.gif")
|
||||
}
|
||||
|
||||
.autorepeatbutton-down {
|
||||
.autorepeatbutton-down,
|
||||
.scrollbutton-down {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif")
|
||||
}
|
||||
|
||||
/* Vertical disabled */
|
||||
.autorepeatbutton-up[disabled="true"],
|
||||
.scrollbutton-up[disabled="true"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
|
||||
}
|
||||
|
||||
.autorepeatbutton-down[disabled="true"],
|
||||
.scrollbutton-down[disabled="true"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
|
||||
}
|
||||
|
||||
/* Horizontal enabled */
|
||||
.autorepeatbutton-up[orient="horizontal"],
|
||||
.autorepeatbutton-down[orient="horizontal"][chromedir="rtl"],
|
||||
.scrollbutton-up[orient="horizontal"],
|
||||
.scrollbutton-down[orient="horizontal"][chromedir="rtl"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-lft.gif");
|
||||
}
|
||||
|
||||
.autorepeatbutton-down[orient="horizontal"],
|
||||
.autorepeatbutton-up[orient="horizontal"][chromedir="rtl"],
|
||||
.scrollbutton-down[orient="horizontal"],
|
||||
.scrollbutton-up[orient="horizontal"][chromedir="rtl"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-rit.gif");
|
||||
}
|
||||
|
||||
/* Horizontal disabled */
|
||||
.autorepeatbutton-up[orient="horizontal"][disabled="true"],
|
||||
.autorepeatbutton-down[orient="horizontal"][chromedir="rtl"][disabled="true"],
|
||||
.scrollbutton-up[orient="horizontal"][disabled="true"],
|
||||
.scrollbutton-down[orient="horizontal"][chromedir="rtl"][disabled="true"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
|
||||
}
|
||||
|
||||
.autorepeatbutton-down[orient="horizontal"][disabled="true"],
|
||||
.autorepeatbutton-up[orient="horizontal"][chromedir="rtl"][disabled="true"],
|
||||
.scrollbutton-down[orient="horizontal"][disabled="true"],
|
||||
.scrollbutton-up[orient="horizontal"][chromedir="rtl"][disabled="true"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
|
||||
}
|
||||
|
|
|
@ -58,6 +58,11 @@ textbox {
|
|||
font: inherit;
|
||||
}
|
||||
|
||||
textbox[empty="true"],
|
||||
textbox[isempty="true"] {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
textbox,
|
||||
textbox[readonly="true"][focused="true"] {
|
||||
-moz-border-top-colors: #BEC3D3 #5D616E;
|
||||
|
@ -145,3 +150,9 @@ textbox.plain {
|
|||
margin: 0px !important;
|
||||
border: 1px solid #000000 !important;
|
||||
}
|
||||
|
||||
/* ::::: textboxes inside toolbarpaletteitems ::::: */
|
||||
|
||||
toolbarpaletteitem > toolbaritem > textbox > .textbox-input-box > html|*.textbox-input {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@
|
|||
color: #ffffff;
|
||||
}
|
||||
|
||||
.wizard-header-description[value=""] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wizard-header-label {
|
||||
-moz-margin-start: 23px;
|
||||
font-weight: bold;
|
||||
|
@ -63,3 +67,21 @@
|
|||
.wizard-page-box {
|
||||
margin: 10px 44px;
|
||||
}
|
||||
|
||||
.wizard-buttons-separator {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.wizard-buttons-box-2,
|
||||
.wizard-buttons-btm {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.wizard-button[dlgtype="finish"],
|
||||
.wizard-button[dlgtype="next"] {
|
||||
-moz-margin-start: 0px;
|
||||
}
|
||||
|
||||
.wizard-button[dlgtype="back"] {
|
||||
-moz-margin-end: 0px;
|
||||
}
|
||||
|
|
|
@ -159,6 +159,7 @@ modern.jar:
|
|||
skin/modern/global/datetimepicker.css (global/datetimepicker.css)
|
||||
skin/modern/global/dialog.css (global/dialog.css)
|
||||
skin/modern/global/dropmarker.css (global/dropmarker.css)
|
||||
skin/modern/global/filefield.css (global/filefield.css)
|
||||
skin/modern/global/filepicker.css (global/filepicker.css)
|
||||
skin/modern/global/findBar.css (global/findBar.css)
|
||||
skin/modern/global/global.css (global/global.css)
|
||||
|
|
Загрузка…
Ссылка в новой задаче