--HG--
branch : add-protovis
This commit is contained in:
Andrew Sutherland 2009-09-04 15:55:58 -07:00
Родитель 9330480476 2d4908df9e
Коммит c51b6f4722
90 изменённых файлов: 2653 добавлений и 737 удалений

Просмотреть файл

@ -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,31 +409,50 @@ 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())
{
// Check if the file exists. If it does, then assume indexing to be
// complete for this file
if (this._getSupportFile(msgHdr).exists())
{
this._log.debug("Message time not set but file exists; setting " +
"time to " + reindexTime);
msgHdr.setUint32Property(this._hdrIndexedProperty, reindexTime);
}
else
{
return [msgHdr, reindexTime];
}
this._log.debug("Message time not set but file exists; setting " +
"time to " + reindexTime);
msgHdr.setUint32Property(this._hdrIndexedProperty, reindexTime);
}
else
{
return [msgHdr, reindexTime];
}
}
}

Просмотреть файл

@ -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);

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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;
@ -241,7 +241,7 @@ function folderPropsOnLoad()
document.getElementById("offline.selectForOfflineNewsgroup").checked = false;
}
// select the menu item
// select the menu item
var folderCharsetList = document.getElementById("folderCharsetList");
var elements = folderCharsetList.getElementsByAttribute("value", gMsgFolder.charset);
folderCharsetList.selectedItem = elements[0];
@ -342,17 +342,17 @@ function hideShowControls(serverType)
}
}
function getEnclosingContainer(startNode)
function getEnclosingContainer(startNode)
{
var parent = startNode;
var box;
while (parent && parent != document)
var box;
while (parent && parent != document)
{
var isContainer = (parent.getAttribute("iscontrolcontainer") == "true");
// remember the FIRST container we encounter, or the first controlcontainer
if (!box || isContainer) box=parent;
// break out with a controlcontainer
if (isContainer) break;
parent = parent.parentNode;
@ -372,7 +372,7 @@ function onFolderPrivileges()
if (imapFolder)
imapFolder.folderPrivileges(window.arguments[0].msgWindow);
// let's try closing the modal dialog to see if it fixes the various problems running this url
window.close();
window.close();
}

Просмотреть файл

@ -43,7 +43,7 @@ function initCommonRetentionSettings(retentionSettings)
document.getElementById("retention.keepUnread").checked = retentionSettings.keepUnreadMessagesOnly;
document.getElementById("retention.keepMsg").value = retentionSettings.retainByPreference;
if(retentionSettings.daysToKeepHdrs > 0)
document.getElementById("retention.keepOldMsgMin").value =
document.getElementById("retention.keepOldMsgMin").value =
(retentionSettings.daysToKeepHdrs > 0) ? retentionSettings.daysToKeepHdrs : 30;
document.getElementById("retention.keepNewMsgMin").value =
(retentionSettings.numHeadersToKeep > 0) ? retentionSettings.numHeadersToKeep : 2000;
@ -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,34 +1843,46 @@ 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) {
if (aMessage._content && aMessage._content.hasContent())
aMessage._indexedBodyText = aMessage._content.getContentString(true);
else
aMessage._indexedBodyText = aMessage._bodyLines.join("\n");
// 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);
},
let imts = this._insertMessageTextStatement;
imts.bindInt64Parameter(0, aMessage.id);
imts.bindStringParameter(1, aMessage._subject);
/**
* 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 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);
if (aMessage._attachmentNames === null)
imts.bindNullParameter(3);
else
imts.bindStringParameter(3, aMessage._attachmentNames.join("\n"));
try {
imts.executeAsync(this.trackAsync());
}
catch(ex) {
throw("error executing fulltext statement... " +
this.asyncConnection.lastError + ": " +
this.asyncConnection.lastErrorString + " - " + ex);
}
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);
}
},
@ -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)
sqlString += nounDef.dbQueryJoinMagic;
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,19 +446,21 @@ var GlodaFundAttr = {
aGlodaMessage.cc = ccIdentities;
// -- Attachments
let attachmentTypes = [];
for each (let [, attachment] in Iterator(aMimeMsg.allAttachments)) {
// We don't care about would-be attachments that are not user-intended
// attachments but rather artifacts of the message content.
// We also want to avoid dealing with obviously bogus mime types.
// (If you don't have a "/", you are probably bogus.)
if (attachment.isRealAttachment &&
(attachment.contentType.indexOf("/") != -1)) {
attachmentTypes.push(MimeTypeNoun.getMimeType(attachment.contentType));
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
// attachments but rather artifacts of the message content.
// We also want to avoid dealing with obviously bogus mime types.
// (If you don't have a "/", you are probably bogus.)
if (attachment.isRealAttachment &&
(attachment.contentType.indexOf("/") != -1)) {
attachmentTypes.push(MimeTypeNoun.getMimeType(attachment.contentType));
}
}
if (attachmentTypes.length) {
aGlodaMessage.attachmentTypes = attachmentTypes;
}
}
if (attachmentTypes.length) {
aGlodaMessage.attachmentTypes = attachmentTypes;
}
// TODO: deal with mailing lists, including implicit-to. this will require
@ -565,9 +581,12 @@ var GlodaFundAttr = {
if (fromMeCc.length)
aGlodaMessage.fromMeCc = fromMeCc;
if (aRawReps.bodyLines &&
this.contentWhittle({}, aRawReps.bodyLines, aRawReps.content)) {
// we were going to do something here?
// 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,16 +1105,19 @@ var GlodaIndexer = {
if (!isLocal)
{
// third term: && Status Is nsMsgMessageFlags.Offline
searchTerm = searchSession.createTerm();
searchTerm.booleanAnd = true;
searchTerm.attrib = nsMsgSearchAttrib.MsgStatus;
searchTerm.op = nsMsgSearchOp.Is;
value = searchTerm.value;
value.attrib = searchTerm.attrib;
value.status = Ci.nsMsgMessageFlags.Offline;
searchTerm.value = value;
searchTerms.appendElement(searchTerm, false);
// 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;
searchTerm.attrib = nsMsgSearchAttrib.MsgStatus;
searchTerm.op = nsMsgSearchOp.Is;
value = searchTerm.value;
value.attrib = searchTerm.attrib;
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))
return;
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))
return;
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.indexing = true;
}
else {
this.indexer.indexingSweepNeeded = true;
this.indexer.indexing = 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);
MsgHdrToMimeMessage(aMsgHdr, aCallbackHandle.callbackThis,
aCallbackHandle.callback);
let [,aMimeMsg] = yield this.kWorkAsync;
// 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);
[,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,8 +2537,8 @@ var GlodaIndexer = {
// also see if we already know about the message...
references.push(aMsgHdr.messageId);
this._datastore.getMessagesByMessageID(references, aCallbackHandle.callback,
aCallbackHandle.callbackThis);
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
}
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,7 +231,32 @@ function _synthMessagesToFakeRep(aSynthMessages) {
(msg in aSynthMessages)];
}
function lobotomizeAdaptiveIndexer() {
// The indexer doesn't need to worry about load; zero his rescheduling time.
GlodaIndexer._INDEX_INTERVAL = 0;
let realIdleService = GlodaIndexer._idleService;
// pretend we are always idle
GlodaIndexer._idleService = {
idleTime: 1000,
addIdleObserver: function() {
realIdleService.addIdleObserver.apply(realIdleService, arguments);
},
removeIdleObserver: function() {
realIdleService.removeIdleObserver.apply(realIdleService, arguments);
}
};
// Lobotomize the adaptive indexer
GlodaIndexer._cpuTargetIndexTime = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_ACTIVE = 10000;
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) {
@ -246,29 +275,12 @@ function imsInit() {
// Make the indexer be more verbose about indexing for us...
GlodaIndexer._unitTestSuperVerbose = true;
// The indexer doesn't need to worry about load; zero his rescheduling time.
GlodaIndexer._INDEX_INTERVAL = 0;
let realIdleService = GlodaIndexer._idleService;
// pretend we are always idle
GlodaIndexer._idleService = {
idleTime: 1000,
addIdleObserver: function() {
realIdleService.addIdleObserver.apply(realIdleService, arguments);
},
removeIdleObserver: function() {
realIdleService.removeIdleObserver.apply(realIdleService, arguments);
}
};
// Lobotomize the adaptive indexer
GlodaIndexer._cpuTargetIndexTime = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_ACTIVE = 10000;
GlodaIndexer._CPU_TARGET_INDEX_TIME_IDLE = 10000;
GlodaIndexer._CPU_IS_BUSY_TIME = 10000;
GlodaIndexer._PAUSE_LATE_IS_BUSY_TIME = 10000;
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 " +
"more results, ignoring.\n");
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.');");
imsInit();
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
([, info] in Iterator(messageInfos))];
indexMessages(synMessages, glodaInfoStasher, next_test);
gSynMessages = [info._synMsg for each
([, info] in Iterator(messageInfos))];
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);
@ -97,12 +117,22 @@ function verify_attributes_fundamental(smsg, gmsg) {
// date
do_check_eq(smsg.date.valueOf(), gmsg.date.valueOf());
// -- message ID
do_check_eq(smsg.messageId, gmsg.headerMessageID);
// -- attachments
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");
// -- 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...
@ -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() {
world.phase++;
indexMessages(generateFolderMessages(), glodaInfoStasher, next_test);
// If we have one folder, we don't attempt to populate the other one
if (singleFolder) {
next_test();
}
else {
world.phase++;
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,7 +337,9 @@ function test_query_messages_by_folder() {
* @tests gloda.query.test.kConstraintIn
*/
function test_query_messages_by_folder_nonmatches() {
verify_nonMatches(ts_folderQueries, ts_folderCollections);
// 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, &notify);
// 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:
msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
break;
case nsMsgFilterAction::KillSubthread:
msgHdr->OrFlags(nsMsgMessageFlags::Ignored, &newFlags);
break;
case nsMsgFilterAction::WatchThread:
msgHdr->OrFlags(nsMsgMessageFlags::Watched, &newFlags);
{
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:
{
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,31 +5611,32 @@ nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol)
}
}
PRBool filtersRun;
CallFilterPlugins(msgWindow, &filtersRun);
if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
(!pendingMoves || !ShowPreviewText()))
// delay calling plugins if filter application is also delayed
if (!m_filterListRequiresBody)
{
if (!pendingMoves)
SetHasNewMessages(PR_TRUE);
PRBool filtersRun;
CallFilterPlugins(msgWindow, &filtersRun);
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);
// 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;
}
SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
if (server)
server->SetPerformingBiff(PR_FALSE);
m_performingBiff = PR_FALSE;
if (m_filterList)
(void)m_filterList->FlushLogIfNecessary();
}
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();
@ -192,13 +192,15 @@ function onLoad()
// if the default server cannot have filters, check all accounts
// and get a server that can have filters.
if (!firstItem)
firstItem = getServerThatCanHaveFilters();
firstItem = getServerThatCanHaveFilters();
if (firstItem) {
selectServer(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);
}

Просмотреть файл

@ -57,11 +57,11 @@
#button-send:hover:active {
-moz-image-region: rect(330px 59px 359px 30px);
}
}
#button-send[disabled="true"] {
-moz-image-region: rect(330px 89px 359px 60px) !important;
}
}
#button-address {
-moz-image-region: rect(270px 29px 299px 0);
@ -69,30 +69,32 @@
#button-address:hover:active {
-moz-image-region: rect(270px 59px 299px 30px);
}
}
#button-address[disabled="true"] {
-moz-image-region: rect(270px 89px 299px 60px) !important;
}
}
#button-attach {
-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);
}
}
#button-attach[disabled="true"] {
-moz-image-region: rect(300px 89px 329px 60px) !important;
}
}
#spellingButton {
list-style-image: url("chrome://editor/skin/icons/editoricons.png");
-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,9 +107,10 @@
-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);
}
}
#button-save[disabled="true"] {
-moz-image-region: rect(210px 89px 239px 60px) !important;

Просмотреть файл

@ -49,26 +49,28 @@
-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);
}
#button-getmsg[disabled="true"] {
-moz-image-region: rect(90px 89px 119px 60px) !important;
}
}
#button-newmsg {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
-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);
}
}
#button-newmsg[disabled="true"] {
-moz-image-region: rect(150px 89px 179px 60px) !important;
}
}
#button-reply {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
@ -77,11 +79,11 @@
#button-reply:hover:active {
-moz-image-region: rect(210px 59px 239px 30px);
}
}
#button-reply[disabled="true"] {
-moz-image-region: rect(210px 89px 239px 60px) !important;
}
}
#button-replyall {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
@ -94,14 +96,15 @@
#button-replyall[disabled="true"] {
-moz-image-region: rect(240px 89px 269px 60px) !important;
}
}
#button-forward {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
-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);
}
@ -117,18 +120,19 @@
#button-file:hover:active,
#button-file[open] {
-moz-image-region: rect(30px 59px 59px 30px);
}
}
#button-file[disabled="true"] {
-moz-image-region: rect(30px 89px 59px 60px) !important;
}
}
#button-goback {
list-style-image: url("chrome://communicator/skin/icons/communicatoricons.png");
-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,13 +159,14 @@
-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);
}
}
#button-next[disabled="true"] {
-moz-image-region: rect(180px 89px 209px 60px) !important;
}
}
#button-delete {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
@ -184,13 +190,14 @@ 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);
}
}
#button-mark[disabled="true"] {
-moz-image-region: rect(120px 89px 149px 60px) !important;
}
}
#button-junk {
list-style-image: url("chrome://messenger/skin/icons/messengericons.png");
@ -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;
}

Просмотреть файл

@ -42,7 +42,7 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/* ::::: auto-repeat button ::::: */
autorepeatbutton {
-moz-box-align: center;
-moz-box-pack: center;
@ -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");
}

Просмотреть файл

@ -40,7 +40,7 @@
======================================================================= */
@import url("chrome://global/content/autocomplete.css");
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
@ -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;
@ -66,7 +71,7 @@ textbox[readonly="true"][focused="true"] {
-moz-border-left-colors: #BEC3D3 #5D616E;
}
html|*.textbox-input,
html|*.textbox-input,
html|*.textbox-textarea {
cursor: text;
margin: 0px !important;
@ -78,14 +83,14 @@ html|*.textbox-textarea {
}
/* ..... focused state ..... */
textbox[focused="true"] {
-moz-border-top-colors: #98A5B2 #000000;
-moz-border-right-colors: #98A5B2 #000000;
-moz-border-bottom-colors: #98A5B2 #000000;
-moz-border-left-colors: #98A5B2 #000000;
}
/* ..... disabled state ..... */
textbox[disabled="true"] {
@ -96,8 +101,8 @@ textbox[disabled="true"] {
background-color: #C7D0D9;
color: #999999;
cursor: default !important;
}
}
/* ..... readonly state ..... */
textbox[readonly="true"] {
@ -128,11 +133,11 @@ textbox.plain {
.scrollfield {
border: none !important;
margin: 0px;
margin-top: 1px;
margin-top: 1px;
padding: 0px !important;
background: inherit;
}
}
.scrollfield > .textbox-internal-box {
border: none !important;
margin: 0px !important;
@ -140,8 +145,14 @@ textbox.plain {
}
/* ::::: inline-edit textbox ::::: */
.textbox-inline-edit {
margin: 0px !important;
border: 1px solid #000000 !important;
}
/* ::::: textboxes inside toolbarpaletteitems ::::: */
toolbarpaletteitem > toolbaritem > textbox > .textbox-input-box > html|*.textbox-input {
visibility: hidden;
}

Просмотреть файл

@ -36,7 +36,7 @@
* ***** END LICENSE BLOCK ***** */
/* ===== wizard.css =====================================================
== Styles used by the wizards which contains buttons for stepping
== Styles used by the wizards which contains buttons for stepping
== through a wizard.
======================================================================= */
@ -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)