--- a/mail/base/content/folderDisplay.js
+++ b/mail/base/content/folderDisplay.js
@@ -129,24 +129,33 @@ function FolderDisplayWidget(aTabInfo, a
this._active = false;
/**
* A list of methods to call on 'this' object when we are next made active.
* This list is populated by calls to |_notifyWhenActive| when we are
* not active at the moment.
*/
this._notificationsPendingActivation = [];
- let dummyDOMNode = document.getElementById('mail-toolbox');
+ // Create a DOM node for the fake tree box below.
+ let domNode = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "vbox");
+
+ // We care about onselect events, so add a listener for that.
+ let self = this;
+ domNode.addEventListener("select", function () {
+ self.view.dbView.selectionChanged();
+ }, false);
+
/**
* Create a fake tree box object for if/when this folder is in the background.
- * We need to give it a bogus DOM object to send events to, so we choose the
- * mail-toolbox, who is hopefully unlikely to take offense.
+ * We need to give it a DOM object to send events to, including the onselect
+ * event we care about and for which we added a handler above, and all the
+ * other events we don't care about.
*/
- this._fakeTreeBox = dummyDOMNode ?
- new FakeTreeBoxObject(dummyDOMNode.boxObject) : null;
+ this._fakeTreeBox = new FakeTreeBoxObject(domNode);
/**
* Create a fake tree selection for cases where we have opened a background
* tab. We'll get rid of this as soon as we've switched to the tab for the
* first time, and have a real tree selection.
*/
this._fakeTreeSelection = new JSTreeSelection(this._fakeTreeBox);
@@ -818,22 +827,18 @@ FolderDisplayWidget.prototype = {
UpdateSortIndicators(this.view.primarySortType,
this.view.primarySortOrder);
},
/**
* Messages (that may have been displayed) have been removed; this may impact
* our message selection. If we saw this coming, then
* this._nextViewIndexAfterDelete should know what view index we should
- * select next. If we didn't see this coming, the cause is likely an
- * explicit deletion in another tab/window.
- * Because the nsMsgDBView is on top of things, it will already have called
- * summarizeSelection as a result of the changes to the message display.
- * So our job here is really just to try and potentially improve on the
- * default selection logic.
+ * select next. (We should really always see this coming -- the extra code
+ * is just in case we didn't.)
*/
onMessagesRemoved: function FolderDisplayWidget_onMessagesRemoved() {
// - we saw this coming
let rowCount = this.view.dbView.rowCount;
if (!this._massMoveActive && (this._nextViewIndexAfterDelete != null)) {
// adjust the index if it is after the last row...
// (this can happen if the "mail.delete_matches_sort_order" pref is not
// set and the message is the last message in the view.)
@@ -908,17 +913,20 @@ FolderDisplayWidget.prototype = {
/* ================================== */
/**
* This gets called when the selection changes AND !suppressCommandUpdating
* AND (we're not removing a row OR we are now out of rows).
* In response, we update the toolbar.
*/
updateCommandStatus: function FolderDisplayWidget_updateCommandStatus() {
- UpdateMailToolbar("FolderDisplayWidget command updater notification");
+ // Do this only if we're active. If we aren't, we're going to take care of
+ // this when we switch back to the tab.
+ if (this._active)
+ UpdateMailToolbar("FolderDisplayWidget command updater notification");
},
/**
* This gets called by nsMsgDBView::UpdateDisplayMessage following a call
* to nsIMessenger.OpenURL to kick off message display OR (UDM gets called)
* by nsMsgDBView::SelectionChanged in lieu of loading the message because
* mSupressMsgDisplay.
* In other words, we get notified immediately after the process of displaying
@@ -953,20 +961,21 @@ FolderDisplayWidget.prototype = {
// to display (bug 183394). This is not a problem for us because we hook
// our notification when the message load is initiated, rather than when
// the message completes loading.
this._nextViewIndexAfterDelete = null;
},
/**
* This gets called as a hint that the currently selected message is junk and
- * said junked message is going to be moved out of the current folder. The
- * legacy behaviour is to retrieve the msgToSelectAfterDelete attribute off
- * the db view, stashing it for benefit of the code that gets called when a
- * message move/deletion is completed so that we can trigger its display.
+ * said junked message is going to be moved out of the current folder, or
+ * right before a header is removed from the db view. The legacy behaviour
+ * is to retrieve the msgToSelectAfterDelete attribute off the db view,
+ * stashing it for benefit of the code that gets called when a message
+ * move/deletion is completed so that we can trigger its display.
*/
updateNextMessageAfterDelete:
function FolderDisplayWidget_updateNextMessageAfterDelete() {
this.hintAboutToDeleteMessages();
},
/**
* The most recent currentIndexes on the selection (from the last time
@@ -1990,24 +1999,23 @@ FolderDisplayWidget.prototype = {
/**
* Implement a fake nsITreeBoxObject so that we can keep the view
* nsITreeSelection selections 'live' when they are in the background. We need
* to do this because nsTreeSelection changes its behaviour (and gets ornery)
* if it does not have a box object.
* This does not need to exist once we abandon multiplexed tabbing.
*
* Sometimes, nsTreeSelection tries to turn us into an nsIBoxObject and then in
- * turn get the associated element, and then create DOM events on that. We
- * can't really stop that, but we can use misdirection to tell it about a box
- * object that we don't care about. That way it gets the bogus events,
- * effectively blackholing them.
+ * turn get the associated element, and then create DOM events on that. The
+ * only event that we care about is onselect, so we get a DOM node here (with
+ * an event listener for onselect already attached), and pass its boxObject in
+ * whenever nsTreeSelection QIs us to nsIBoxObject.
*/
-function FakeTreeBoxObject(aDummyBoxObject) {
- this.dummyBoxObject = aDummyBoxObject.QueryInterface(
- Components.interfaces.nsIBoxObject);
+function FakeTreeBoxObject(aDOMNode) {
+ this.domNode = aDOMNode;
this.view = null;
}
FakeTreeBoxObject.prototype = {
view: null,
ensureRowIsVisible: function FakeTreeBoxObject_ensureRowIsVisible() {
// NOP
},
/**
@@ -2024,27 +2032,43 @@ FakeTreeBoxObject.prototype = {
// NOP
},
beginUpdateBatch: function FakeTreeBoxObject_beginUpdateBatch() {
},
endUpdateBatch: function FakeTreeBoxObject_endUpdateBatch() {
},
- rowCountChanged: function FakeTreeBoxObject_rowCountChanged() {
+ /**
+ * We're going to make an exception to our NOP rule here, as this is rather
+ * important for us to pass on. The db view calls this if a row's been
+ * inserted or deleted. Without this, the selection's going to be out of sync
+ * with the view.
+ *
+ * @param aIndex the index where the rows have been inserted or deleted
+ * @param aCount the number of rows inserted or deleted (negative for
+ * deleted)
+ */
+ rowCountChanged: function FakeTreeBoxObject_rowCountChanged(aIndex, aCount) {
+ if (aCount == 0 || !this.view)
+ // Nothing to do
+ return;
+ let selection = this.view.selection;
+ if (selection)
+ selection.adjustSelection(aIndex, aCount);
},
/**
* Sleight of hand! If someone asks us about an nsIBoxObject, we tell them
- * about a real box object that is just a dummy and is never used for
- * anything.
+ * about a real box object that only has an onselect event listener attached
+ * to it. (This violates the QI equivalence requirement, though.)
*/
QueryInterface: function FakeTreeBoxObject_QueryInterface(aIID) {
if (aIID.equals(Components.interfaces.nsIBoxObject))
- return this.dummyBoxObject;
+ return this.domNode.boxObject;
if (!aIID.equals(Components.interfaces.nsISupports) &&
!aIID.equals(Components.interfaces.nsITreeBoxObject))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};
/*
* Provide attribute and function implementations that complain very loudly if
--- a/mail/base/content/messageDisplay.js
+++ b/mail/base/content/messageDisplay.js
@@ -319,45 +319,54 @@ MessageDisplayWidget.prototype = {
_clearSummaryTimer: function MessageDisplayWidget__clearSummaryTimer(aThis) {
aThis._summaryStabilityTimeout = null;
},
/**
* Called by the FolderDisplayWidget when it is being made active again and
* it's time for us to step up and re-display or clear the message as
* demanded by our multiplexed tab implementation.
- *
+ *
* @param aDontReloadMessage [optional] true if you don't want to make us
* call reloadMessage even if the conditions are
* right for doing so. Use only when you're sure
* that you've already triggered a message load,
* and that a message reload would be harmful.
*/
makeActive: function MessageDisplayWidget_makeActive(aDontReloadMessage) {
let wasInactive = !this._active;
this._active = true;
if (wasInactive) {
+ let dbView = this.folderDisplay.view.dbView;
// (see our usage below)
- let preDisplayedMessage = this.displayedMessage;
+ let preDisplayedViewIndex =
+ dbView.currentlyDisplayedMessage;
// Force a synthetic selection changed event. This will propagate through
// to a call to onSelectedMessagesChanged who will handle making sure the
// right message pane is in use, etc.
- this.folderDisplay.view.dbView.selectionChanged();
+ dbView.selectionChanged();
// The one potential problem is that the message view only triggers message
// streaming if it doesn't think the message is already displayed. In that
// case we need to force a re-display by calling reloadMessage. We can
- // detect that case by seeing if our displayedMessage value changes its
- // value during this call (because we will receive a onDisplayingMessage
- // notification). If we should be displaying a single message but the
- // value does not change, we need to force a re-display.
+ // detect that case by seeing if the preDisplayedViewIndex corresponds to
+ // the current value of displayedMessage, since if it doesn't, the value
+ // of displayedMessage has changed during this call (because we will
+ // receive a onDisplayingMessage notification). If we should be
+ // displaying a single message but the value does not change, we need to
+ // force a re-display.
+ // We used to use the value of this.displayedMessage prior to the
+ // selectionChanged() call here instead of preDisplayedViewIndex, but we
+ // don't do that any more because this.displayedMessage might be out of
+ // sync with reality for an inactive tab.
if (!aDontReloadMessage && this.singleMessageDisplay &&
- this.displayedMessage && (this.displayedMessage ==
- preDisplayedMessage))
- this.folderDisplay.view.dbView.reloadMessage();
+ this.displayedMessage &&
+ (preDisplayedViewIndex != nsMsgViewIndex_None) &&
+ (this.displayedMessage == dbView.getMsgHdrAt(preDisplayedViewIndex)))
+ dbView.reloadMessage();
}
this._updateActiveMessagePane();
},
/**
* Called by the FolderDisplayWidget when it is being made inactive or no
* longer requires messages to be displayed.
@@ -432,19 +441,39 @@ MessageTabDisplayWidget.prototype = {
get visible() {
return true;
},
set visible(aIgnored) {
},
onSelectedMessagesChanged:
function MessageTabDisplayWidget_onSelectedMessagesChanged() {
- this.__proto__.__proto__.onSelectedMessagesChanged.apply(this, arguments);
- if (!this.closing)
- document.getElementById('tabmail').setTabTitle(this.folderDisplay._tabInfo);
+ // Look at the number of messages left in the db view. If there aren't any,
+ // close the tab.
+ if (this.folderDisplay.view.dbView.rowCount == 0) {
+ if (!this.closing) {
+ this.closing = true;
+ document.getElementById('tabmail').closeTab(
+ this.folderDisplay._tabInfo);
+ }
+ return true;
+ }
+ else {
+ if (!this.closing)
+ document.getElementById('tabmail').setTabTitle(
+ this.folderDisplay._tabInfo);
+
+ // The db view shouldn't do anything if we're inactive or about to close
+ if (!this.active || this.closing)
+ return true;
+
+ // No summaries in a message tab
+ this.singleMessageDisplay = true;
+ return false;
+ }
},
/**
* A message tab should never ever be blank. Close the tab if we become
* blank.
*/
clearDisplay: function MessageTabDisplayWidget_clearDisplay() {
if (!this.closing) {
--- a/mail/base/content/messageWindow.js
+++ b/mail/base/content/messageWindow.js
@@ -231,20 +231,16 @@ StandaloneMessageDisplayWidget.prototype
// null out the selection on the view so it operates in stand alone mode
this.folderDisplay.view.dbView.selection = null;
this.folderDisplay.view.dbView.loadMessageByUrl(aUri);
},
clearDisplay: function () {
this.messageLoading = false;
this.messageLoaded = false;
- // XXX we should figure out why we're calling window.close() both from here
- // and from onSelectedMessagesChanged.
- if (!this.aboutToLoadMessage)
- window.close();
},
_updateActiveMessagePane: function() {
// no-op. the message pane is always visible.
},
onDisplayingMessage:
function StandaloneMessageDisplayWidget_onDisplayingMessage(aMsgHdr) {
this.__proto__.__proto__.onDisplayingMessage.call(this, aMsgHdr);
@@ -270,19 +266,22 @@ StandaloneMessageDisplayWidget.prototype
if (!this.isDummy)
this.displayedUri = null;
// We've loaded a message, so this should be set to false
this.aboutToLoadMessage = false;
},
onSelectedMessagesChanged: function () {
- // XXX we should figure out why we're calling window.close() both from here
- // and from clearDisplay.
- if (!this.aboutToLoadMessage && this.folderDisplay.treeSelection.count == 0) {
+ // If the message we're displaying is deleted, we won't have any selection
+ // for a while, but we'll soon select a new message. So don't test the
+ // selection count -- instead see if there are any messages in the db view
+ // at all.
+ if (!this.aboutToLoadMessage &&
+ this.folderDisplay.view.dbView.rowCount == 0) {
window.close();
return true;
}
return false;
},
};
var messagepaneObserver = {
--- a/mail/test/mozmill/folder-display/test-deletion-with-multiple-displays.js
+++ b/mail/test/mozmill/folder-display/test-deletion-with-multiple-displays.js
@@ -15,16 +15,17 @@
*
* The Initial Developer of the Original Code is
* Mozilla Messaging, Inc.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* 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
* 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
@@ -42,50 +43,69 @@
* this both for tabs that have ever been opened in the foreground, and tabs
* that haven't (and thus might have fake selections).
*/
var MODULE_NAME = 'test-deletion-with-multiple-displays';
var RELATIVE_ROOT = '../shared-modules';
var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
-var folder;
+var folder, lastMessageFolder, oneBeforeFolder, oneAfterFolder,
+ multipleDeletionFolder1, multipleDeletionFolder2, multipleDeletionFolder3,
+ multipleDeletionFolder4;
function setupModule(module) {
let fdh = collector.getModule('folder-display-helpers');
fdh.installInto(module);
let wh = collector.getModule('window-helpers');
wh.installInto(module);
folder = create_folder("DeletionA");
+ lastMessageFolder = create_folder("DeletionB");
+ oneBeforeFolder = create_folder("DeletionC");
+ oneAfterFolder = create_folder("DeletionD");
+ multipleDeletionFolder1 = create_folder("DeletionE");
+ multipleDeletionFolder2 = create_folder("DeletionF");
+ multipleDeletionFolder3 = create_folder("DeletionG");
+ multipleDeletionFolder4 = create_folder("DeletionH");
// we want exactly as many messages as we plan to delete, so that we can test
// that the message window and tabs close when they run out of things to
// to display.
make_new_sets_in_folder(folder, [{count: 4}]);
+
+ // since we don't test window close here, it doesn't really matter how many
+ // messages these have
+ make_new_sets_in_folder(lastMessageFolder, [{count: 4}]);
+ make_new_sets_in_folder(oneBeforeFolder, [{count: 10}]);
+ make_new_sets_in_folder(oneAfterFolder, [{count: 10}]);
+ make_new_sets_in_folder(multipleDeletionFolder1, [{count: 30}]);
+
+ // We're depending on selecting the last message here, so these do matter
+ make_new_sets_in_folder(multipleDeletionFolder2, [{count: 10}]);
+ make_new_sets_in_folder(multipleDeletionFolder3, [{count: 10}]);
+ make_new_sets_in_folder(multipleDeletionFolder4, [{count: 10}]);
}
var tabFolder, tabMessage, tabMessageBackground, curMessage, nextMessage;
/**
* The message window controller. Short names because controllers get used a
* lot.
*/
var msgc;
/**
- * Have a message displayed in a folder tab, message tab, and message window.
- * The idea is that as we delete from the various sources, they should all
- * advance in lock-step through their messages, simplifying our lives (but
- * making us explode forevermore the first time any of the tests fail.)
+ * Open up the message at aIndex in all our display mechanisms, and check to see
+ * if the displays are all correct. This also sets up all our globals.
*/
-function test_open_message_in_all_three_display_mechanisms() {
+function _open_message_in_all_four_display_mechanisms_helper(aFolder, aIndex) {
// - Select the message in this tab.
- tabFolder = be_in_folder(folder);
- curMessage = select_click_row(0);
+ tabFolder = be_in_folder(aFolder);
+ curMessage = select_click_row(aIndex);
assert_selected_and_displayed(curMessage);
// - Open the tab with the message
tabMessage = open_selected_message_in_new_tab();
assert_selected_and_displayed(curMessage);
assert_tab_titled_from(tabMessage, curMessage);
// go back to the folder tab
@@ -97,115 +117,569 @@ function test_open_message_in_all_three_
// - Open the window with the message
// need to go back to the folder tab. (well, should.)
switch_tab(tabFolder);
msgc = open_selected_message_in_new_window();
assert_selected_and_displayed(msgc, curMessage);
}
+/// Check whether this message is displayed in the folder tab
+var VERIFY_FOLDER_TAB = 0x1;
+/// Check whether this message is displayed in the foreground message tab
+var VERIFY_MESSAGE_TAB = 0x2;
+/// Check whether this message is displayed in the background message tab
+var VERIFY_BACKGROUND_MESSAGE_TAB = 0x4;
+/// Check whether this message is displayed in the message window
+var VERIFY_MESSAGE_WINDOW = 0x8;
+var VERIFY_ALL = 0xE;
+
+/**
+ * Verify that the message is displayed in the given tabs. The index is
+ * optional.
+ */
+function _verify_message_is_displayed_in(aFlags, aMessage, aIndex) {
+ if (aFlags & VERIFY_FOLDER_TAB) {
+ switch_tab(tabFolder);
+ assert_selected_and_displayed(aMessage);
+ if (aIndex !== undefined)
+ assert_selected_and_displayed(aIndex);
+ }
+ if (aFlags & VERIFY_MESSAGE_TAB) {
+ // Verify the title first
+ assert_tab_titled_from(tabMessage, aMessage);
+ switch_tab(tabMessage);
+ // Verify the title again, just in case
+ assert_tab_titled_from(tabMessage, aMessage);
+ assert_selected_and_displayed(aMessage);
+ if (aIndex !== undefined)
+ assert_selected_and_displayed(aIndex);
+ }
+ if (aFlags & VERIFY_BACKGROUND_MESSAGE_TAB) {
+ // Only verify the title
+ assert_tab_titled_from(tabMessageBackground, aMessage);
+ }
+ if (aFlags & VERIFY_MESSAGE_WINDOW) {
+ assert_selected_and_displayed(msgc, aMessage);
+ if (aIndex !== undefined)
+ assert_selected_and_displayed(msgc, aIndex);
+ }
+}
+
+/**
+ * Have a message displayed in a folder tab, message tab (foreground and
+ * background), and message window. The idea is that as we delete from the
+ * various sources, they should all advance in lock-step through their messages,
+ * simplifying our lives (but making us explode forevermore the first time any
+ * of the tests fail.)
+ */
+function test_open_first_message_in_all_four_display_mechanisms() {
+ _open_message_in_all_four_display_mechanisms_helper(folder, 0);
+}
+
/**
* Perform a deletion from the folder tab, verify the others update correctly
* (advancing to the next message).
*/
function test_delete_in_folder_tab() {
// - plan to end up on the guy who is currently at index 1
curMessage = mc.dbView.getMsgHdrAt(1);
// while we're at it, figure out who is at 2 for the next step
nextMessage = mc.dbView.getMsgHdrAt(2);
// - delete the message
press_delete();
- // - make sure the right guy is selected, and that he is at index 0
- assert_selected_and_displayed(curMessage);
- assert_selected_and_displayed(0);
- // - make sure the message tab updated its title even without us switching
- assert_tab_titled_from(tabMessage, curMessage);
-
- // - switch to the message tab, make sure he is now on the right guy
- switch_tab(tabMessage);
- assert_selected_and_displayed(curMessage);
-
- // - make sure the background message tab updated its title
- assert_tab_titled_from(tabMessageBackground, curMessage);
-
- // - check the window
- assert_selected_and_displayed(msgc, curMessage);
+ // - verify all displays
+ _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
}
/**
* Perform a deletion from the message tab, verify the others update correctly
* (advancing to the next message).
*/
function test_delete_in_message_tab() {
- // (we're still on the message tab, and nextMessage is the guy we want to see
- // once the delete completes.)
+ switch_tab(tabMessage);
+ // nextMessage is the guy we want to see once the delete completes.
press_delete();
curMessage = nextMessage;
- assert_selected_and_displayed(curMessage);
- assert_tab_titled_from(tabMessage, curMessage);
- // - check the background message tab
- assert_tab_titled_from(tabMessageBackground, curMessage);
+ // - verify all displays
+ _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
- // - switch to the folder tab and make sure he is on the right guy and at 0
- switch_tab(tabFolder);
- assert_selected_and_displayed(curMessage);
- assert_selected_and_displayed(0);
// figure out the next guy...
nextMessage = mc.dbView.getMsgHdrAt(1);
if (!nextMessage)
throw new Error("We ran out of messages early?");
-
- // - check the message window
- assert_selected_and_displayed(msgc, curMessage);
}
/**
* Perform a deletion from the message window, verify the others update
* correctly (advancing to the next message).
*/
function test_delete_in_message_window() {
- // - delete, verify in the message window
+ // - delete
press_delete(msgc);
curMessage = nextMessage;
- assert_selected_and_displayed(msgc, curMessage);
-
- // - verify in the folder tab (we're still on this tab)
- assert_selected_and_displayed(curMessage);
- assert_selected_and_displayed(0);
-
- // - verify in the message tab
- switch_tab(tabMessage);
- assert_selected_and_displayed(curMessage);
- assert_tab_titled_from(tabMessage, curMessage);
-
- // - verify in the background message tab
- assert_tab_titled_from(tabMessageBackground, curMessage);
+ // - verify all displays
+ _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
}
/**
* Delete the last message in that folder, which should close all message
* displays.
*/
function test_delete_last_message_closes_message_displays() {
// - since we have both foreground and background message tabs, we don't need
// to open yet another tab to test
// - prep for the message window disappearing
plan_for_window_close(msgc);
// - let's arbitrarily perform the deletion on this message tab
+ switch_tab(tabMessage);
press_delete();
// - the message window should have gone away...
// (this also helps ensure that the 3pane gets enough event loop time to do
// all that it needs to accomplish)
wait_for_window_close(msgc);
msgc = null;
// - and we should now be on the folder tab and there should be no other tabs
if (mc.tabmail.tabInfo.length != 1)
throw new Error("There should only be one tab left!");
// the below check is implied by the previous check if things are sane-ish
if (mc.tabmail.currentTabInfo != tabFolder)
throw new Error("We should be on the folder tab!");
}
+
+/*
+ * Now we retest everything, but while deleting the last message in our
+ * selection. We need to make sure we select the previously next-to-last message
+ * in that case.
+ */
+
+/**
+ * Have the last message displayed in a folder tab, message tab (foreground and
+ * background), and message window. The idea is that as we delete from the
+ * various sources, they should all advance in lock-step through their messages,
+ * simplifying our lives (but making us explode forevermore the first time any
+ * of the tests fail.)
+ */
+function test_open_last_message_in_all_four_display_mechanisms() {
+ // since we have four messages, index 3 is the last message.
+ _open_message_in_all_four_display_mechanisms_helper(lastMessageFolder, 3);
+}
+
+/**
+ * Perform a deletion from the folder tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+function test_delete_last_message_in_folder_tab() {
+ // - plan to end up on the guy who is currently at index 2
+ curMessage = mc.dbView.getMsgHdrAt(2);
+ // while we're at it, figure out who is at 1 for the next step
+ nextMessage = mc.dbView.getMsgHdrAt(1);
+ // - delete the message
+ press_delete();
+
+ // - verify all displays
+ _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 2);
+}
+
+/**
+ * Perform a deletion from the message tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+function test_delete_last_message_in_message_tab() {
+ // (we're still on the message tab, and nextMessage is the guy we want to see
+ // once the delete completes.)
+ press_delete();
+ curMessage = nextMessage;
+
+ // - verify all displays
+ _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 1);
+ // figure out the next guy...
+
+ nextMessage = mc.dbView.getMsgHdrAt(0);
+ if (!nextMessage)
+ throw new Error("We ran out of messages early?");
+}
+
+/**
+ * Perform a deletion from the message window, verify the others update
+ * correctly (advancing to the next message).
+ */
+function test_delete_last_message_in_message_window() {
+ // Vary this up. Switch to the folder tab instead of staying on the message
+ // tab
+ switch_tab(tabFolder);
+ // - delete
+ press_delete(msgc);
+ curMessage = nextMessage;
+ // - verify all displays
+ _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+
+ // - clean up, close the message window and displays
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/*
+ * Our next job is to open up a message, then delete the message one before it
+ * in another view. The other selections shouldn't be affected.
+ */
+
+/**
+ * Test "one before" deletion in the folder tab.
+ */
+function test_delete_one_before_message_in_folder_tab() {
+ // Open up message 4 in message tabs and a window (we'll delete message 3).
+ _open_message_in_all_four_display_mechanisms_helper(oneBeforeFolder, 4);
+
+ let expectedMessage = mc.dbView.getMsgHdrAt(4);
+ select_click_row(3);
+ press_delete();
+
+ // The message tab, background message tab and window shouldn't have changed
+ _verify_message_is_displayed_in(VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test "one before" deletion in the message tab.
+ */
+function test_delete_one_before_message_in_message_tab() {
+ // Open up 3 in a message tab, then select and open up 4 in a background tab
+ // and window.
+ select_click_row(3);
+ tabMessage = open_selected_message_in_new_tab(true);
+ let expectedMessage = select_click_row(4);
+ tabMessageBackground = open_selected_message_in_new_tab(true);
+ msgc = open_selected_message_in_new_window(true);
+
+ // Switch to the message tab, and delete.
+ switch_tab(tabMessage);
+ press_delete();
+
+ // The folder tab, background message tab and window shouldn't have changed
+ _verify_message_is_displayed_in(VERIFY_FOLDER_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test "one before" deletion in the message window.
+ */
+function test_delete_one_before_message_in_message_window() {
+ // Open up 3 in a message window, then select and open up 4 in a background
+ // and a foreground tab.
+ select_click_row(3);
+ msgc = open_selected_message_in_new_window();
+ let expectedMessage = select_click_row(4);
+ tabMessage = open_selected_message_in_new_tab();
+ switch_tab(tabFolder);
+ tabMessageBackground = open_selected_message_in_new_tab(true);
+
+ // Press delete in the message window.
+ press_delete(msgc);
+
+ // The folder tab, message tab and background message tab shouldn't have
+ // changed
+ _verify_message_is_displayed_in(VERIFY_FOLDER_TAB |
+ VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB,
+ expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/*
+ * Now do all of that again, but this time delete the message _after_ the open one.
+ */
+
+/**
+ * Test "one after" deletion in the folder tab.
+ */
+function test_delete_one_after_message_in_folder_tab() {
+ // Open up message 4 in message tabs and a window (we'll delete message 5).
+ _open_message_in_all_four_display_mechanisms_helper(oneAfterFolder, 4);
+
+ let expectedMessage = mc.dbView.getMsgHdrAt(4);
+ select_click_row(5);
+ press_delete();
+
+ // The message tab, background message tab and window shouldn't have changed
+ _verify_message_is_displayed_in(VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test "one after" deletion in the message tab.
+ */
+function test_delete_one_after_message_in_message_tab() {
+ // Open up 5 in a message tab, then select and open up 4 in a background tab
+ // and window.
+ select_click_row(5);
+ tabMessage = open_selected_message_in_new_tab(true);
+ let expectedMessage = select_click_row(4);
+ tabMessageBackground = open_selected_message_in_new_tab(true);
+ msgc = open_selected_message_in_new_window(true);
+
+ // Switch to the message tab, and delete.
+ switch_tab(tabMessage);
+ press_delete();
+
+ // The folder tab, background message tab and window shouldn't have changed
+ _verify_message_is_displayed_in(VERIFY_FOLDER_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test "one after" deletion in the message window.
+ */
+function test_delete_one_after_message_in_message_window() {
+ // Open up 5 in a message window, then select and open up 4 in a background
+ // and a foreground tab.
+ select_click_row(5);
+ msgc = open_selected_message_in_new_window();
+ let expectedMessage = select_click_row(4);
+ tabMessage = open_selected_message_in_new_tab();
+ switch_tab(tabFolder);
+ tabMessageBackground = open_selected_message_in_new_tab(true);
+
+ // Press delete in the message window.
+ press_delete(msgc);
+
+ // The folder tab, message tab and background message tab shouldn't have
+ // changed
+ _verify_message_is_displayed_in(VERIFY_FOLDER_TAB |
+ VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB,
+ expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/*
+ * Delete multiple messages in a folder tab. Make sure message displays at the
+ * beginning, middle and end of a selection work out.
+ */
+
+/**
+ * Test deleting multiple messages in a folder tab, with message displays open
+ * to the beginning of a selection.
+ */
+function test_delete_multiple_messages_with_first_selected_message_open() {
+ // Open up 2 in a message tab, background tab, and message window.
+ _open_message_in_all_four_display_mechanisms_helper(multipleDeletionFolder1,
+ 2);
+
+ // We'll select 2-5, 8, 9 and 10. We expect 6 to be the next displayed
+ // message.
+ select_click_row(2);
+ select_shift_click_row(5);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ select_control_click_row(10);
+ let expectedMessage = mc.dbView.getMsgHdrAt(6);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test deleting multiple messages in a folder tab, with message displays open
+ * to somewhere in the middle of a selection.
+ */
+function test_delete_multiple_messages_with_nth_selected_message_open() {
+ // Open up 9 in a message tab, background tab, and message window.
+ _open_message_in_all_four_display_mechanisms_helper(multipleDeletionFolder1,
+ 9);
+
+ // We'll select 2-5, 8, 9 and 10. We expect 11 to be the next displayed
+ // message.
+ select_click_row(2);
+ select_shift_click_row(5);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ select_control_click_row(10);
+ let expectedMessage = mc.dbView.getMsgHdrAt(11);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test deleting multiple messages in a folder tab, with message displays open
+ * to the end of a selection.
+ */
+function test_delete_multiple_messages_with_last_selected_message_open() {
+ // Open up 10 in a message tab, background tab, and message window.
+ _open_message_in_all_four_display_mechanisms_helper(multipleDeletionFolder1,
+ 9);
+
+ // We'll select 2-5, 8, 9 and 10. We expect 11 to be the next displayed
+ // message.
+ select_click_row(2);
+ select_shift_click_row(5);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ select_control_click_row(10);
+ let expectedMessage = mc.dbView.getMsgHdrAt(11);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test deleting multiple messages in a folder tab (including the last one!),
+ * with message displays open to the beginning of a selection.
+ */
+function test_delete_multiple_messages_including_the_last_one_with_first_open() {
+ // 10 messages in this folder. Open up message 1 everywhere.
+ _open_message_in_all_four_display_mechanisms_helper(multipleDeletionFolder2,
+ 1);
+
+ // We'll select 1-4, 7, 8 and 9. We expect 5 to be the next displayed message.
+ select_click_row(1);
+ select_shift_click_row(4);
+ select_control_click_row(7);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ let expectedMessage = mc.dbView.getMsgHdrAt(5);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test deleting multiple messages in a folder tab (including the last one!),
+ * with message displays open to the middle of a selection.
+ */
+function test_delete_multiple_messages_including_the_last_one_with_nth_open() {
+ // 10 messages in this folder. Open up message 7 everywhere.
+ _open_message_in_all_four_display_mechanisms_helper(multipleDeletionFolder3,
+ 7);
+
+ // We'll select 1-4, 7, 8 and 9. We expect 6 to be the next displayed message.
+ select_click_row(1);
+ select_shift_click_row(4);
+ select_control_click_row(7);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ let expectedMessage = mc.dbView.getMsgHdrAt(6);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
+
+/**
+ * Test deleting multiple messages in a folder tab (including the last one!),
+ * with message displays open to the end of a selection.
+ */
+function test_delete_multiple_messages_including_the_last_one_with_last_open() {
+ // 10 messages in this folder. Open up message 9 everywhere.
+ _open_message_in_all_four_display_mechanisms_helper(multipleDeletionFolder4,
+ 9);
+
+ // We'll select 1-4, 7, 8 and 9. We expect 6 to be the next displayed message.
+ select_click_row(1);
+ select_shift_click_row(4);
+ select_control_click_row(7);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ let expectedMessage = mc.dbView.getMsgHdrAt(6);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ switch_tab(tabFolder);
+}
--- a/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
@@ -317,16 +317,18 @@ function open_selected_message_in_new_wi
/**
* Switch to another tab. If no tab is specified, we switch to the 'other' tab.
* That is the last tab we used, most likely the tab that was current when we
* created this tab.
*
* @param aNewTab Optional, index of the other tab to switch to.
*/
function switch_tab(aNewTab) {
+ // If we're still loading a message at this point, wait for that to finish
+ wait_for_message_display_completion();
let targetTab = (aNewTab != null) ? aNewTab : otherTab;
// now the current tab will be the 'other' tab after we switch
otherTab = mc.tabmail.currentTabInfo;
mc.tabmail.switchToTab(targetTab);
wait_for_message_display_completion();
}
/**
--- a/mailnews/base/src/nsMsgDBView.cpp
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -5567,17 +5567,29 @@ NS_IMETHODIMP nsMsgDBView::OnHdrFlagsCha
return NS_OK;
}
NS_IMETHODIMP nsMsgDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, PRInt32 aFlags,
nsIDBChangeListener *aInstigator)
{
nsMsgViewIndex deletedIndex = FindHdr(aHdrChanged);
if (deletedIndex != nsMsgViewIndex_None)
+ {
+ // Check if this message is currently selected. If it is, tell the frontend
+ // to be prepared for a delete.
+ if (mTreeSelection && mCommandUpdater)
+ {
+ PRBool isMsgSelected = PR_FALSE;
+ mTreeSelection->IsSelected(deletedIndex, &isMsgSelected);
+ if (isMsgSelected)
+ mCommandUpdater->UpdateNextMessageAfterDelete();
+ }
+
RemoveByIndex(deletedIndex);
+ }
return NS_OK;
}
NS_IMETHODIMP nsMsgDBView::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, PRInt32 aFlags,
nsIDBChangeListener *aInstigator)
{
return OnNewHeader(aHdrChanged, aParentKey, PR_FALSE);
--- a/mailnews/base/util/jsTreeSelection.js
+++ b/mailnews/base/util/jsTreeSelection.js
@@ -114,17 +114,17 @@ JSTreeSelection.prototype = {
/**
* The number of currently selected rows.
*/
_count: 0,
// In the case of the stand-alone message window, there's no tree, but
// there's a view.
_view: null,
-
+
get tree JSTreeSelection_get_treeBoxObject() {
return this._treeBoxObject;
},
set tree JSTreeSelection_set_treeBoxObject(aTreeBoxObject) {
this._treeBoxObject = aTreeBoxObject;
},
set view JSTreeSelection_set_view(aView) {