Bug 467768 -- "No way to make mail open in tabs by default". r=asuth for folder display parts and tests, bienvenu for desktop search integration and mailnews/, Mnyromyr for mailnews/ and suite/; sr=bienvenu; ui-r=clarkbw.
authorSiddharth Agarwal <sid.bugzilla@gmail.com>
Mon, 29 Jun 2009 01:55:15 +0530
changeset 2958 01bd846dbb7e
parent 2957 235b52d1d665
child 2959 ff9b5051d01d
push id2399
push usersid.bugzilla@gmail.com
push dateSun, 28 Jun 2009 20:26:23 +0000
treeherdercomm-central@ff9b5051d01d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth, bienvenu, clarkbw
bugs467768
Bug 467768 -- "No way to make mail open in tabs by default". r=asuth for folder display parts and tests, bienvenu for desktop search integration and mailnews/, Mnyromyr for mailnews/ and suite/; sr=bienvenu; ui-r=clarkbw. Add support for opening mail in tabs, and for opening tabs in the background. Fix most code that opens mail to obey the preference. Fix several cases of multiple message loads at once with tabs (including with message tab restore at startup) and the standalone message window.
mail/app/profile/all-thunderbird.js
mail/base/Makefile.in
mail/base/content/folderDisplay.js
mail/base/content/mailWindowOverlay.js
mail/base/content/mailWindowOverlay.xul
mail/base/content/messageDisplay.js
mail/base/content/messageWindow.js
mail/base/content/msgMail3PaneWindow.js
mail/base/content/search.xml
mail/base/content/specialTabs.js
mail/base/content/tabmail.xml
mail/base/content/widgetglue.js
mail/base/modules/MailConsts.js
mail/base/modules/MailUtils.js
mail/base/modules/Makefile.in
mail/components/nsMailDefaultHandler.js
mail/components/preferences/advanced.xul
mail/components/search/SpotlightIntegration.js
mail/components/search/WinSearchIntegration.js
mail/components/search/content/searchCommon.js
mail/installer/windows/packages-static
mail/locales/en-US/chrome/messenger/messenger.properties
mail/locales/en-US/chrome/messenger/preferences/advanced.dtd
mail/test/mozmill/folder-display/test-deletion-with-multiple-displays.js
mail/test/mozmill/folder-display/test-message-window.js
mail/test/mozmill/folder-display/test-opening-messages.js
mail/test/mozmill/folder-display/test-right-click-middle-click.js
mail/test/mozmill/shared-modules/test-folder-display-helpers.js
mail/test/mozmill/shared-modules/test-window-helpers.js
mailnews/base/build/nsMsgFactory.cpp
mailnews/base/src/Makefile.in
mailnews/base/src/dbViewWrapper.js
mailnews/base/src/nsMailNewsCommandLineHandler.js
mailnews/base/src/nsMessengerBootstrap.cpp
mailnews/base/src/nsMessengerBootstrap.h
mailnews/base/test/unit/test_jsTreeSelection.js
mailnews/base/util/jsTreeSelection.js
mailnews/build/nsMailModule.cpp
mailnews/mailnews.js
suite/installer/unix/packages
suite/installer/windows/packages
suite/mailnews/Makefile.in
suite/mailnews/modules/MailUtils.js
suite/mailnews/modules/Makefile.in
suite/mailnews/widgetglue.js
--- a/mail/app/profile/all-thunderbird.js
+++ b/mail/app/profile/all-thunderbird.js
@@ -390,16 +390,26 @@ pref("browser.safebrowsing.warning.infoU
 
 // prevent status-bar spoofing even if people are foolish enough to turn on JS
 pref("dom.disable_window_status_change",          true);
 
 // For the Empty Junk/Trash confirmation dialogs.
 pref("mail.emptyJunk.dontAskAgain", false);
 pref("mail.emptyTrash.dontAskAgain", false);
 
+// If a message is opened using Enter or a double click, what should we do?
+// 0 - open it in a new window
+// 1 - open it in an existing window
+// 2 - open it in a new tab
+pref("mail.openMessageBehavior", 2);
+pref("mail.openMessageBehavior.version", 0);
+// If messages or folders are opened using the context menu or a middle click,
+// should we open them in the foreground or in the background?
+pref("mail.contextMenuBackgroundTabs", true);
+
 // Tabs
 pref("mail.tabs.tabMinWidth", 100);
 pref("mail.tabs.tabMaxWidth", 250);
 pref("mail.tabs.tabClipWidth", 140);
 pref("mail.tabs.autoHide", false);
 pref("mail.tabs.closeWindowWithLastTab", true);
 
 // Where to show tab close buttons:
--- a/mail/base/Makefile.in
+++ b/mail/base/Makefile.in
@@ -37,16 +37,18 @@
 
 DEPTH		= ../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
+DIRS = modules
+
 DEFINES += -DMOZ_APP_VERSION=$(MOZ_APP_VERSION)
 
 ifneq (,$(BUILD_OFFICIAL)$(MOZILLA_OFFICIAL))
 DEFINES += -DOFFICIAL_BUILD=1
 endif
 
 ifneq (,$(filter windows gtk2 mac cocoa, $(MOZ_WIDGET_TOOLKIT)))
 DEFINES += -DHAVE_SHELL_SERVICE=1
--- a/mail/base/content/folderDisplay.js
+++ b/mail/base/content/folderDisplay.js
@@ -33,16 +33,17 @@
  * 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 ***** */
 
 Components.utils.import("resource://app/modules/dbViewWrapper.js");
+Components.utils.import("resource://app/modules/jsTreeSelection.js");
 
 var gFolderDisplay = null;
 var gMessageDisplay = null;
 
 var nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
 
 /**
  * Abstraction for a widget that (roughly speaking) displays the contents of
@@ -137,33 +138,45 @@ function FolderDisplayWidget(aTabInfo, a
   /**
    * 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.
    */
   this._fakeTreeBox = dummyDOMNode ?
                         new FakeTreeBoxObject(dummyDOMNode.boxObject) : null;
 
+  /**
+   * 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);
+
   this._mostRecentSelectionCounts = [];
   this._mostRecentCurrentIndices = [];
 }
 FolderDisplayWidget.prototype = {
   /**
    * @return the currently displayed folder.  This is just proxied from the
    *     view wrapper.
    */
   get displayedFolder() {
     return this._nonViewFolder || this.view.displayedFolder;
   },
 
   /**
-   * @return the nsITreeSelection object for our tree view if there is one,
-   *     null otherwise.
+   * @return the nsITreeSelection object for our tree view.  This exists for
+   *     the benefit of message tabs that haven't been switched to yet.
+   *     We provide a fake tree selection in those cases.
    */
   get treeSelection() {
+    // If we haven't switched to this tab yet, dbView will exist but
+    // dbView.selection won't, so use the fake tree selection instead.
+    if (this._fakeTreeSelection)
+      return this._fakeTreeSelection;
     if (this.view.dbView)
       return this.view.dbView.selection;
     else
       return null;
   },
 
   /**
    * Number of headers to tell the message database to cache when we enter a
@@ -542,16 +555,17 @@ FolderDisplayWidget.prototype = {
     //  down to the message display and we don't want it doing anything it
     //  doesn't have to do.
     this.messageDisplay._close();
 
     this.view.close();
     this.messenger.setWindow(null, null);
     this.messenger = null;
     this._fakeTreeBox = null;
+    this._fakeTreeSelection = null;
   },
 
   /*   ===============================   */
   /* ===== IDBViewWrapper Listener ===== */
   /*   ===============================   */
 
   /**
    * @return true if the mail view picker is visible.  This affects whether the
@@ -765,39 +779,16 @@ FolderDisplayWidget.prototype = {
     }
 
     // - new messages
     // if configured to scroll to new messages, try that
     if (gPrefBranch.getBoolPref("mailnews.scroll_to_new_message") &&
         this.navigate(nsMsgNavigationType.firstNew, /* select */ false))
       return;
 
-    // - last selected message
-    // if configured to load the last selected message (this is currently more
-    //  persistent than our saveSelection/restoreSelection stuff), and the view
-    //  is backed by a single underlying folder (the only way having just a
-    //  message key works out), try that
-    if (gPrefBranch.getBoolPref("mailnews.remember_selected_message") &&
-        this.view.isSingleFolder) {
-      // use the displayed folder; nsMsgDBView goes to the effort to save the
-      //  state to the viewFolder, so this is the correct course of action.
-      let lastLoadedMessageKey = this.view.displayedFolder.lastMessageLoaded;
-      if (lastLoadedMessageKey != nsMsgKey_None) {
-        this.view.dbView.selectMsgByKey(lastLoadedMessageKey);
-        // The message key may not be present in the view for a variety of
-        //  reasons.  Beyond message deletion, it simply may not match the
-        //  active mail view or quick search, for example.
-        if (this.view.dbView.numSelected) {
-          this.ensureRowIsVisible(
-            this.view.dbView.viewIndexForFirstSelectedMsg);
-          return;
-        }
-      }
-    }
-
     // - towards the newest messages, but don't select
     if (this.view.isSortedAscending && this.view.sortImpliesTemporalOrdering &&
       this.navigate(nsMsgNavigationType.lastMessage, /* select */ false))
       return;
 
     // - to the top, the coliseum
     this.ensureRowIsVisible(0);
   },
@@ -1197,37 +1188,65 @@ FolderDisplayWidget.prototype = {
     messenger = this.messenger;
 
     // update singleton globals' state
     msgWindow.openFolder = this.view.displayedFolder;
 
     this._active = true;
     this._runNotificationsPendingActivation();
 
+    // Make sure we get rid of this._fakeTreeSelection, whether we use it below
+    // or not.
+    let fakeTreeSelection = this._fakeTreeSelection;
+    this._fakeTreeSelection = null;
+
     // -- UI
 
+    // We're going to set this to true if we've already caused a
+    // selectionChanged event, so that the message display doesn't cause
+    // another.
+    let dontReloadMessage = false;
     // thread pane if we have a db view
     if (this.view.dbView) {
       // Make sure said thread pane is visible.  If we do this after we re-root
       //  the tree, the thread pane may not actually replace the account central
       //  pane.  Concerning...
       this._showThreadPane();
 
       // some things only need to happen if we are transitioning from inactive
       //  to active
       if (wasInactive) {
-        // Setting the 'view' attribute on treeBox results in the following
-        //  effective calls, noting that in makeInactive we made sure to null
-        //  out its view so that it won't try and clean up any views or their
-        //  selections.  (The actual actions happen in nsTreeBodyFrame::SetView)
-        // - this.view.dbView.selection.tree = this.treeBox
-        // - this.view.dbView.setTree(this.treeBox)
-        // - this.treeBox.view = this.view.dbView (in nsTreeBodyObject::SetView)
         if (this.treeBox) {
+          // We might have assigned our JS tree selection to
+          //  this.view.dbView.selection back in _hookUpFakeTreeBox. If we've
+          //  done so, null the selection out so that the line after this
+          //  causes a real selection to be created.
+          // If we haven't done so, we're fine as selection would be null here
+          //  anyway. (The fake tree selection should persist only till the
+          //  first time the tab is switched to.)
+          if (fakeTreeSelection)
+            this.view.dbView.selection = null;
+
+          // Setting the 'view' attribute on treeBox results in the following
+          //  effective calls, noting that in makeInactive we made sure to null
+          //  out its view so that it won't try and clean up any views or their
+          //  selections.  (The actual actions happen in
+          //  nsTreeBodyFrame::SetView)
+          // - this.view.dbView.selection.tree = this.treeBox
+          // - this.view.dbView.setTree(this.treeBox)
+          // - this.treeBox.view = this.view.dbView (in
+          //   nsTreeBodyObject::SetView)
           this.treeBox.view = this.view.dbView;
+
+          if (fakeTreeSelection) {
+            fakeTreeSelection.duplicateSelection(this.view.dbView.selection);
+            // Since duplicateSelection will fire a selectionChanged event,
+            // which will try to reload the message, we shouldn't do the same.
+            dontReloadMessage = true;
+          }
           if (this._savedFirstVisibleRow != null)
             this.treeBox.scrollToRow(this._savedFirstVisibleRow);
 
           this.restoreColumnStates();
         }
 
         // restore the quick search widget
         let searchInput = document.getElementById("searchInput");
@@ -1249,17 +1268,17 @@ FolderDisplayWidget.prototype = {
       if (this._tabInfo)
         mailTabType._setPaneStates(this._tabInfo.mode.legalPanes,
           {folder: !this._tabInfo.folderPaneCollapsed,
            message: this.messageDisplay.visible});
 
       // update the columns and such that live inside the thread pane
       this._updateThreadDisplay();
 
-      this.messageDisplay.makeActive();
+      this.messageDisplay.makeActive(dontReloadMessage);
     }
     // account central stuff when we don't have a dbview
     else {
       this._showAccountCentral();
       if (this._tabInfo)
         mailTabType._setPaneStates(this._tabInfo.mode.accountCentralLegalPanes,
           {folder: !this._tabInfo.folderPaneCollapsed});
     }
@@ -1314,38 +1333,54 @@ FolderDisplayWidget.prototype = {
       let searchInput = document.getElementById("searchInput");
       if (searchInput) {
         this._savedQuickSearch = {
           text: searchInput.showingSearchCriteria ? null : searchInput.value,
           searchMode: searchInput.searchMode
         };
       }
 
-      // save off the tree selection object.  the nsTreeBodyFrame will make the
-      //  view forget about it when our view is removed, so it's up to us to
-      //  save it.
-      let treeViewSelection = this.view.dbView.selection;
-      // make the tree forget about the view right now so we can tell the db
-      //  view about its selection object so it can try and keep it up-to-date
-      //  even while hidden in the background
-      if (this.treeBox)
-        this.treeBox.view = null;
-      // (and tell the db view about its selection again...)
-      this.view.dbView.selection = treeViewSelection;
-
-      // hook the dbview up to the fake tree box
-      this._fakeTreeBox.view = this.view.dbView;
-      this.view.dbView.setTree(this._fakeTreeBox);
-      treeViewSelection.tree = this._fakeTreeBox;
+      this.hookUpFakeTreeBox(true);
     }
 
     this.messageDisplay.makeInactive();
   },
 
   /**
+   * Called when we want to "disable" the real treeBox for a while and hook up
+   * the fake tree box to the db view. This also takes care of our
+   * treeSelection object.
+   *
+   * @param aNullRealTreeBoxView true if we want to null out the real tree box.
+   *          We don't want to null out the view if we're opening a background
+   *          tab, for example.
+   */
+  hookUpFakeTreeBox: function FolderDisplayWidget_hookUpFakeTreeBox(
+                         aNullRealTreeBoxView) {
+    // save off the tree selection object.  the nsTreeBodyFrame will make the
+    //  view forget about it when our view is removed, so it's up to us to
+    //  save it.
+    // We use this.treeSelection instead of this.view.dbView.selection here,
+    //  so that we get the fake tree selection if we have it.
+    let treeSelection = this.treeSelection;
+    // if we want to, make the tree forget about the view right now so we can
+    //  tell the db view about its selection object so it can try and keep it
+    //  up-to-date even while hidden in the background
+    if (aNullRealTreeBoxView && this.treeBox)
+      this.treeBox.view = null;
+    // (and tell the db view about its selection again...)
+    this.view.dbView.selection = treeSelection;
+
+    // hook the dbview up to the fake tree box
+    this._fakeTreeBox.view = this.view.dbView;
+    this.view.dbView.setTree(this._fakeTreeBox);
+    treeSelection.tree = this._fakeTreeBox;
+  },
+
+  /**
    * @return true if there is a db view and the command is enabled on the view.
    *  This function hides some of the XPCOM-odditities of the getCommandStatus
    *  call.
    */
   getCommandStatus: function FolderDisplayWidget_getCommandStatus(
       aCommandType, aEnabledObj, aCheckStatusObj) {
     // no view means not enabled
     if (!this.view.dbView)
@@ -1626,59 +1661,57 @@ FolderDisplayWidget.prototype = {
     if (!treeSelection)
       return;
     treeSelection.clearSelection();
     this.messageDisplay.clearDisplay();
     this._updateContextDisplay();
   },
 
   /**
-   * Select a message for display by header.  If the view is active, attempt
-   *  to select the message right now.  If the view is not active or we were
-   *  unable to find it, update our saved selection to want to display the
-   *  message.  Threads are expanded to find the header.
+   * Select a message for display by header.  Attempt to select the message
+   *  right now.  If we were unable to find it, update our saved selection
+   *  to want to display the message.  Threads are expanded to find the header.
    *
    * @param aMsgHdr The message header to select for display.
    */
   selectMessage: function FolderDisplayWidget_selectMessage(aMsgHdr,
       aForceNotification) {
-    if (this.active) {
-      let viewIndex = this.view.dbView.findIndexOfMsgHdr(aMsgHdr, true);
-      if (viewIndex != nsMsgViewIndex_None) {
-        this.selectViewIndex(viewIndex);
-        return;
-      }
+    let viewIndex = this.view.dbView.findIndexOfMsgHdr(aMsgHdr, true);
+    if (viewIndex != nsMsgViewIndex_None) {
+      this.selectViewIndex(viewIndex);
     }
-    this._savedSelection = [{messageId: aMsgHdr.messageId}];
-    // queue the selection to be restored once we become active if we are not
-    //  active.
-    if (!this.active)
-      this._notifyWhenActive(this._restoreSelection);
+    else {
+      this._savedSelection = [{messageId: aMsgHdr.messageId}];
+      // queue the selection to be restored once we become active if we are not
+      //  active.
+      if (!this.active)
+        this._notifyWhenActive(this._restoreSelection);
+    }
   },
 
   /**
    * Select all of the provided nsIMsgDBHdrs in the aMessages array, expanding
-   *  threads as required.  If the view is not active, or we were not able to
-   *  find all of the messages, update our saved selection to want to display
-   *  the messages.  The messages will then be selected when we are made active
-   *  or all messages in the folder complete loading.  This is to accomodate the
-   *  use-case where we are backed by an in-progress search and no
+   *  threads as required.  If we were not able to find all of the messages,
+   *  update our saved selection to want to display the messages.  The messages
+   *  will then be selected when we are made active or all messages in the
+   *  folder complete loading.  This is to accomodate the use-case where we
+   *  are backed by an in-progress search and no
    *
    * @param aMessages An array of nsIMsgDBHdr instances.
    * @param aDoNotNeedToFindAll If true (can be omitted and left undefined), we
    *     do not attempt to save the selection for future use.  This is intended
    *     for use by the _restoreSelection call which is the end-of-the-line for
    *     restoring the selection.  (Once it gets called all of our messages
    *     should have already been loaded.)
    */
   selectMessages: function FolderDisplayWidget_selectMessages(
-    aMessages, aDoNotNeedToFindAll) {
-    if (this.active && this.treeSelection) {
+      aMessages, aDoNotNeedToFindAll) {
+    let treeSelection = this.treeSelection; // potentially magic getter
+    if (treeSelection) {
       let foundAll = true;
-      let treeSelection = this.treeSelection;
       let dbView = this.view.dbView;
       let minRow = null, maxRow = null;
 
       treeSelection.selectEventsSuppressed = true;
       treeSelection.clearSelection();
 
       for each (let [, msgHdr] in Iterator(aMessages)) {
         let viewIndex = dbView.findIndexOfMsgHdr(msgHdr, true);
@@ -1968,16 +2001,19 @@ FolderDisplayWidget.prototype = {
  */
 function FakeTreeBoxObject(aDummyBoxObject) {
   this.dummyBoxObject = aDummyBoxObject.QueryInterface(
                           Components.interfaces.nsIBoxObject);
   this.view = null;
 }
 FakeTreeBoxObject.prototype = {
   view: null,
+  ensureRowIsVisible: function FakeTreeBoxObject_ensureRowIsVisible() {
+    // NOP
+  },
   /**
    * No need to actually invalidate, as when we re-root the view this will
    *  happen.
    */
   invalidate: function FakeTreeBoxObject_invalidate() {
     // NOP
   },
   invalidateRange: function FakeTreeBoxObject_invalidateRange() {
@@ -2055,26 +2091,24 @@ FTBO_stubOutAttributes(FakeTreeBoxObject
   "rowWidth",
   "horizontalPosition",
   "selectionRegion",
   ]);
 FTBO_stubOutMethods(FakeTreeBoxObject.prototype, [
   "getFirstVisibleRow",
   "getLastVisibleRow",
   "getPageLength",
-  "ensureRowIsVisible",
   "ensureCellIsVisible",
   "scrollToRow",
   "scrollByLines",
   "scrollByPages",
   "scrollToCell",
   "scrollToColumn",
   "scrollToHorizontalPosition",
   "invalidateColumn",
-  "invalidateRow",
   "invalidateCell",
   "invalidateColumnRange",
   "getRowAt",
   "getCellAt",
   "getCoordsForCellItem",
   "isCellCropped",
   "clearStyleAndImageCaches",
-  ]);
\ No newline at end of file
+  ]);
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -61,16 +61,19 @@ const kVerticalMailLayout = 2;
 const kNoRemoteContentPolicy = 0;
 const kBlockRemoteContent = 1;
 const kAllowRemoteContent = 2;
 
 const kMsgNotificationPhishingBar = 1;
 const kMsgNotificationJunkBar = 2;
 const kMsgNotificationRemoteImages = 3;
 
+Components.utils.import("resource://app/modules/MailUtils.js");
+Components.utils.import("resource://app/modules/MailConsts.js");
+
 var gMessengerBundle;
 var gPrefBranch = Components.classes["@mozilla.org/preferences-service;1"]
                             .getService(Components.interfaces.nsIPrefService)
                             .getBranch(null);
 var gCopyService = Components.classes["@mozilla.org/messenger/messagecopyservice;1"]
                      .getService(Components.interfaces.nsIMsgCopyService);
 
 // Timer to mark read, if the user has configured the app to mark a message as
@@ -1694,62 +1697,68 @@ let mailTabType = {
         // persistence and restoreTab wants to know if we are the magic first tab
         aTab.firstTab = true;
         aTab.folderDisplay.makeActive();
       },
       /**
        * @param aFolder The nsIMsgFolder to display.
        * @param aMsgHdr Optional message header to display.
        */
-      openTab: function(aTab, aFolder, aMsgHdr, aOptions) {
-        if (aOptions === undefined)
-          aOptions = {};
-
+      openTab: function(aTab, aArgs) {
         // persistence and restoreTab wants to know if we are the magic first tab
         aTab.firstTab = false;
 
         // Get a tab that we can initialize our user preferences from.
         // (We don't want to assume that our immediate predecessor was a
         //  "folder" tab.)
         let modelTab = document.getElementById("tabmail")
                          .getTabInfoForCurrentOrFirstModeInstance(aTab.mode);
 
         if (modelTab)
           aTab.folderPaneCollapsed = modelTab.folderPaneCollapsed;
 
         // - figure out whether to show the message pane
         let messagePaneShouldBeVisible;
         // explicitly told to us?
-        if ("messagePaneVisible" in aOptions)
-          messagePaneShouldBeVisible = aOptions.messagePaneVisible;
+        if ("messagePaneVisible" in aArgs)
+          messagePaneShouldBeVisible = aArgs.messagePaneVisible;
         // inherit from the previous tab (if we've got one)
         else if (modelTab)
           messagePaneShouldBeVisible = modelTab.messageDisplay.visible;
         // who does't love a message pane?
         else
           messagePaneShouldBeVisible = true;
 
         this.openTab(aTab, false,
                      new MessagePaneDisplayWidget(messagePaneShouldBeVisible));
 
-        // Clear selection, because context clicking on a folder and opening in a
-        // new tab needs to have SelectFolder think the selection has changed.
-        // We also need to clear these globals to subvert the code that prevents
-        // folder loads when things haven't changed.
-        var folderTree = document.getElementById("folderTree");
-        folderTree.view.selection.clearSelection();
-        folderTree.view.selection.currentIndex = -1;
-
-        aTab.folderDisplay.makeActive();
-
-        // selecting the folder effectively calls
-        //  aTab.folderDisplay.show(aFolder)
-        gFolderTreeView.selectFolder(aFolder);
-        if(aMsgHdr)
-          aTab.folderDisplay.selectMessage(aMsgHdr);
+        let background = ("background" in aArgs) && aArgs.background;
+        let msgHdr = ("msgHdr" in aArgs) && aArgs.msgHdr;
+
+        if (!background) {
+          // Clear selection, because context clicking on a folder and opening in a
+          // new tab needs to have SelectFolder think the selection has changed.
+          // We also need to clear these globals to subvert the code that prevents
+          // folder loads when things haven't changed.
+          let folderTree = document.getElementById("folderTree");
+          folderTree.view.selection.clearSelection();
+          folderTree.view.selection.currentIndex = -1;
+
+          aTab.folderDisplay.makeActive();
+          // This will call aTab.folderDisplay.show(aArgs.folder), which takes
+          // care of the tab title and other stuff
+          gFolderTreeView.selectFolder(aArgs.folder);
+          if (msgHdr)
+            aTab.folderDisplay.selectMessage(msgHdr);
+        }
+        else {
+          // Since we can't call gFolderTreeView.selectFolder, we need to make the folder
+          // display believe it's showing this folder
+          aTab.folderDisplay.show(aArgs.folder);
+        }
 
         aTab.mode.onTitleChanged.call(this, aTab, aTab.tabNode);
       },
       persistTab: function(aTab) {
         if (!aTab.folderDisplay.displayedFolder)
           return null;
         return {
           folderURI: aTab.folderDisplay.displayedFolder.URI,
@@ -1776,17 +1785,19 @@ let mailTabType = {
               //  need to explicitly set the _visible value because otherwise it
               //  misses out on the toggle event.
               if (!gMessageDisplay._active)
                 gMessageDisplay._visible = aPersistedState.messagePaneVisible;
             }
             gFolderTreeView.selectFolder(folder);
           }
           else {
-            aTabmail.openTab("folder", folder, null, aPersistedState);
+            aTabmail.openTab("folder", {folder: folder,
+                messagePaneVisible: aPersistedState.messagePaneVisible,
+                background: true});
           }
         }
       },
       onTitleChanged: function(aTab, aTabNode) {
         if (!aTab.folderDisplay || !aTab.folderDisplay.displayedFolder) {
           // Don't show "undefined" as title when there is no account.
           aTab.title = " ";
           return;
@@ -1814,43 +1825,55 @@ let mailTabType = {
     message: {
       type: "message",
       /// The set of panes that are legal to be displayed in this mode
       legalPanes: {
         folder: false,
         thread: false,
         message: true
       },
-      openTab: function(aTab, aMsgHdr, aViewWrapperToClone) {
-        aTab.mode.onTitleChanged.call(this, aTab, null, aMsgHdr);
-
+      openTab: function(aTab, aArgs) {
         this.openTab(aTab, false, new MessageTabDisplayWidget());
 
-        if (aViewWrapperToClone)
-          aTab.folderDisplay.cloneView(aViewWrapperToClone);
+        let viewWrapperToClone = ("viewWrapperToClone" in aArgs) &&
+                                 aArgs.viewWrapperToClone;
+        let background = ("background" in aArgs) && aArgs.background;
+
+        if (viewWrapperToClone)
+          aTab.folderDisplay.cloneView(viewWrapperToClone);
         else
-          aTab.folderDisplay.show(aMsgHdr.folder);
-
-        aTab.folderDisplay.selectMessage(aMsgHdr);
+          aTab.folderDisplay.show(aArgs.msgHdr.folder);
+
+        // folderDisplay.show is going to try to set the title itself, but we
+        // wouldn't have selected a message at that point, so set the title
+        // here
+        aTab.mode.onTitleChanged.call(this, aTab, null, aArgs.msgHdr);
+
+        aTab.folderDisplay.selectMessage(aArgs.msgHdr);
 
         // we only want to make it active after setting up the view and the message
         //  to avoid generating bogus summarization events.
-        aTab.folderDisplay.makeActive();
+        if (!background)
+          aTab.folderDisplay.makeActive();
+        else
+          // We don't want to null out the real tree box view, as that
+          // corresponds to the _current_ tab, not the new one
+          aTab.folderDisplay.hookUpFakeTreeBox(false);
       },
       persistTab: function(aTab) {
-        let msgHdr = aTab.messageDisplay.displayedMessage;
+        let msgHdr = aTab.folderDisplay.selectedMessage;
         return {
           messageURI: msgHdr.folder.getUriForMsg(msgHdr)
         };
       },
       restoreTab: function(aTabmail, aPersistedState) {
         let msgHdr = messenger.msgHdrFromURI(aPersistedState.messageURI);
         // if the message no longer exists, we can't restore the tab
         if (msgHdr)
-          aTabmail.openTab("message", msgHdr);
+          aTabmail.openTab("message", {msgHdr: msgHdr, background: true});
       },
       onTitleChanged: function(aTab, aTabNode, aMsgHdr) {
         // Try and figure out the selected message if one was not provided.
         // It is possible that the folder has yet to load, so it may still be
         //  null.
         if (aMsgHdr == null)
           aMsgHdr = aTab.folderDisplay.selectedMessage;
         aTab.title = "";
@@ -1893,29 +1916,31 @@ let mailTabType = {
        * @param facetString
        *     - everything:
        *     - subject:
        *     - involves:
        *     - to:
        *     - from:
        *     - body:
        * @param location Either a GlodaFolder or the string "everywhere".
+       * 
+       * XXX This needs to handle opening in the background
        */
-      openTab: function(aTab, searchString, facetString, location) {
+      openTab: function(aTab, aArgs) {
         // make sure the search string bundle is loaded
         getDocumentElements();
         aTab.title = gSearchBundle.getFormattedString("glodaSearchTabTitle",
-                                                      [searchString]);
-        aTab.glodaSearchInputValue = searchString;
-        aTab.searchString = searchString;
-        aTab.facetString = facetString;
-
-        aTab.glodaSynView = new GlodaSyntheticSearchView(searchString,
-                                                         facetString,
-                                                         location);
+                                                      [aArgs.searchString]);
+        aTab.glodaSearchInputValue = aArgs.searchString;
+        aTab.searchString = aArgs.searchString;
+        aTab.facetString = aArgs.facetString;
+
+        aTab.glodaSynView = new GlodaSyntheticSearchView(aArgs.searchString,
+                                                         aArgs.facetString,
+                                                         aArgs.location);
 
         this.openTab(aTab, false, new MessagePaneDisplayWidget());
         aTab.folderDisplay.show(aTab.glodaSynView);
         aTab.folderDisplay.setVisibleColumns(aTab.mode.desiredColumns);
         aTab.folderDisplay.makeActive();
       },
     },
   },
@@ -1944,20 +1969,22 @@ let mailTabType = {
                                    Components.interfaces.nsITreeBoxObject);
 
     if (aIsFirstTab) {
       aTab.folderDisplay.messenger = messenger;
     }
     else {
       // Each tab gets its own messenger instance; this provides each tab with
       // its own undo/redo stack and back/forward navigation history.
-      messenger = Components.classes["@mozilla.org/messenger;1"]
-                            .createInstance(Components.interfaces.nsIMessenger);
-      messenger.setWindow(window, msgWindow);
-      aTab.folderDisplay.messenger = messenger;
+      // If this is a foreground tab, folderDisplay.makeActive() is going to
+      // set it as the global messenger, so there's no need to do it here
+      let tabMessenger = Components.classes["@mozilla.org/messenger;1"]
+                                   .createInstance(Components.interfaces.nsIMessenger);
+      tabMessenger.setWindow(window, msgWindow);
+      aTab.folderDisplay.messenger = tabMessenger;
     }
   },
 
   closeTab: function(aTab) {
     aTab.folderDisplay.close();
   },
 
   saveTabState: function(aTab) {
@@ -2185,63 +2212,58 @@ function MsgOpenNewWindowForFolder(folde
     window.openDialog("chrome://messenger/content/", "_blank",
                       "chrome,all,dialog=no",
                       selectedFolders[i].URI, msgKeyToSelect);
   }
 }
 
 /**
  * UI-triggered command to open the currently selected folder(s) in new tabs.
+ * @param aBackground [optional] if true, then the folder tab is opened in the
+ *                    background. If false or not given, then the folder tab is
+ *                    opened in the foreground.
  */
-function MsgOpenNewTabForFolder()
+function MsgOpenNewTabForFolder(aBackground)
 {
   // If there is a right-click happening, gFolderTreeView.getSelectedFolders()
   // will tell us about it (while the selection's currentIndex would reflect
   // the node that was selected/displayed before the right-click.)
   let selectedFolders = gFolderTreeView.getSelectedFolders();
   for (let i = 0; i < selectedFolders.length; i++) {
-    document.getElementById("tabmail").openTab("folder", selectedFolders[i]);
+    document.getElementById("tabmail").openTab("folder",
+      {folder: selectedFolders[i], background: aBackground});
   }
 }
 
-/**
- * UI-triggered command to open the currently selected message in a new tab.
- */
-function MsgOpenNewTabForMessage()
-{
-  if (!gFolderDisplay.selectedMessage)
-    return;
-  document.getElementById('tabmail').openTab("message",
-                                             gFolderDisplay.selectedMessage,
-                                             gFolderDisplay.view);
-}
-
 function MsgOpenSelectedMessages()
 {
   // Toggle message body (rss summary) and content-base url in message
   // pane per pref, otherwise open summary or web page in new window.
   if (gFolderDisplay.selectedMessageIsFeed && GetFeedOpenHandler() == 2) {
     FeedSetContentViewToggle();
     return;
   }
 
   let selectedMessages = gFolderDisplay.selectedMessages;
-  var numMessages = selectedMessages.length;
-
-  var windowReuse = gPrefBranch.getBoolPref("mailnews.reuse_message_window");
-  // This is a radio type button pref, currently with only 2 buttons.
-  // We need to keep the pref type as 'bool' for backwards compatibility
-  // with 4.x migrated prefs.  For future radio button(s), please use another
-  // pref (either 'bool' or 'int' type) to describe it.
-  //
-  // windowReuse values: false, true
-  //    false: open new standalone message window for each message
-  //    true : reuse existing standalone message window for each message
-  if (windowReuse && numMessages == 1 &&
-      MsgOpenSelectedMessageInExistingWindow())
+  let numMessages = selectedMessages.length;
+
+  let openMessageBehavior = gPrefBranch.getIntPref("mail.openMessageBehavior");
+  if (openMessageBehavior == MailConsts.OpenMessageBehavior.NEW_TAB) {
+    // Open all the tabs in the background, except for the last one
+    let tabmail = document.getElementById("tabmail");
+    for (let i = 0; i < numMessages; i++)
+      tabmail.openTab("message", {msgHdr: selectedMessages[i],
+          viewWrapperToClone: gFolderDisplay.view,
+          background: (i < (numMessages - 1))});
+    return;
+  }
+
+  if (openMessageBehavior == MailConsts.OpenMessageBehavior.EXISTING_WINDOW &&
+      numMessages == 1 && MailUtils.openMessageInExistingWindow(
+          gFolderDisplay.selectedMessage, gFolderDisplay.view))
     return;
 
   var openWindowWarning = gPrefBranch.getIntPref("mailnews.open_window_warning");
   if ((openWindowWarning > 1) && (numMessages >= openWindowWarning)) {
     if (!gMessengerBundle)
         gMessengerBundle = document.getElementById("bundle_messenger");
     var title = gMessengerBundle.getString("openWindowWarningTitle");
     var text = gMessengerBundle.getFormattedString("openWindowWarningText", [numMessages]);
@@ -2251,35 +2273,16 @@ function MsgOpenSelectedMessages()
       return;
   }
 
   for (var i = 0; i < numMessages; i++) {
     MsgOpenNewWindowForMessage(selectedMessages[i]);
   }
 }
 
-function MsgOpenSelectedMessageInExistingWindow()
-{
-  var windowID = GetWindowByWindowType("mail:messageWindow");
-  if (!windowID)
-    return false;
-
-  var msgHdr = gFolderDisplay.selectedMessage;
-
-  // (future work: perhaps make the window have a method we can call to do this)
-  // make the window's folder clone our view
-  windowID.gFolderDisplay.cloneView(gFolderDisplay.view);
-  // boss the window into showing our message
-  windowID.gFolderDisplay.selectMessage(msgHdr);
-
-  // bring existing window to front
-  windowID.focus();
-  return true;
-}
-
 function MsgOpenFromFile()
 {
   const nsIFilePicker = Components.interfaces.nsIFilePicker;
   var fp = Components.classes["@mozilla.org/filepicker;1"]
                      .createInstance(nsIFilePicker);
 
   var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService();
   strBundleService = strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -529,17 +529,17 @@
     <menuseparator id="mailContext-sep-open"/>
     <menuitem id="mailContext-openNewWindow"
               label="&contextOpenNewWindow.label;"
               accesskey="&contextOpenNewWindow.accesskey;"
               oncommand="MsgOpenNewWindowForMessage();"/>
     <menuitem id="threadPaneContext-openNewTab"
               label="&contextOpenNewTab.label;"
               accesskey="&contextOpenNewTab.accesskey;"
-              oncommand="MsgOpenNewTabForMessage();"/>
+              oncommand="ThreadTreeContextMenuNewTab();"/>
     <menuseparator id="mailContext-sep-open2"/>
     <menuitem id="mailContext-replySender"
               label="&contextReplySender.label;"
               accesskey="&contextReplySender.accesskey;"
               oncommand="MsgReplySender(event);"/>
     <menuitem id="mailContext-replyNewsgroup"
               label="&contextReplyNewsgroup.label;"
               accesskey="&contextReplyNewsgroup.accesskey;"
@@ -724,17 +724,17 @@
               oncommand="MsgGetMessage();"/>
     <menuitem id="folderPaneContext-openNewWindow"
               label="&folderContextOpenNewWindow.label;"
               accesskey="&folderContextOpenNewWindow.accesskey;"
               oncommand="MsgOpenNewWindowForFolder(null,-1);"/>
     <menuitem id="folderPaneContext-openNewTab"
               label="&folderContextOpenNewTab.label;"
               accesskey="&folderContextOpenNewTab.accesskey;"
-              oncommand="MsgOpenNewTabForFolder();"/>
+              oncommand="FolderPaneContextMenuNewTab();"/>
     <menuitem id="folderPaneContext-searchMessages"
               label="&folderContextSearchMessages.label;"
               accesskey="&folderContextSearchMessages.accesskey;"
               command="cmd_search"/>
     <menuitem id="folderPaneContext-subscribe"
               label="&folderContextSubscribe.label;"
               accesskey="&folderContextSubscribe.accesskey;"
               oncommand="MsgSubscribe();"/>
--- a/mail/base/content/messageDisplay.js
+++ b/mail/base/content/messageDisplay.js
@@ -319,18 +319,24 @@ 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() {
+  makeActive: function MessageDisplayWidget_makeActive(aDontReloadMessage) {
     let wasInactive = !this._active;
     this._active = true;
 
     if (wasInactive) {
       // (see our usage below)
       let preDisplayedMessage = this.displayedMessage;
       // Force a synthetic selection changed event.  This will propagate through
       //  to a call to onSelectedMessagesChanged who will handle making sure the
@@ -338,18 +344,19 @@ MessageDisplayWidget.prototype = {
       this.folderDisplay.view.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.
-      if (this.singleMessageDisplay && this.displayedMessage &&
-          (this.displayedMessage == preDisplayedMessage))
+      if (!aDontReloadMessage && this.singleMessageDisplay &&
+          this.displayedMessage && (this.displayedMessage ==
+                                    preDisplayedMessage))
         this.folderDisplay.view.dbView.reloadMessage();
     }
 
     this._updateActiveMessagePane();
   },
 
   /**
    * Called by the FolderDisplayWidget when it is being made inactive or no
@@ -460,9 +467,9 @@ NeverVisisbleMessageDisplayWidget.protot
   get visible() {
     return false;
   },
   onSelectedMessagesChanged: function() {
     return false;
   },
   _updateActiveMessagePane: function() {
   },
-};
\ No newline at end of file
+};
--- a/mail/base/content/messageWindow.js
+++ b/mail/base/content/messageWindow.js
@@ -59,17 +59,19 @@ var gMessageDisplay;
  * - To intercept queries about the selected message so that we can do the
  *    .eml file thing.  We were originally trying to avoid involving the
  *    nsMsgDBView, but that might not be important anymore. (future work)
  */
 function StandaloneFolderDisplayWidget(aMessageDisplayWidget) {
   FolderDisplayWidget.call(this, null, aMessageDisplayWidget);
   // do not set the actual treeBox variable or our superclass might try and do
   //  weird rooting things we don't want to have to think about right now.
-  this._magicTreeSelection = new JSTreeSelection(this.treeBox);
+  //  Oh, and since we don't have a real tree selection, the fake tree
+  //  selection is going to be our real one. :)
+  this._magicTreeSelection = this._fakeTreeSelection;
 }
 StandaloneFolderDisplayWidget.prototype = {
   __proto__: FolderDisplayWidget.prototype,
 
   /**
    * If we have a displayed message, then we've got 1 message, otherwise 0.
    */
   get selectedCount() {
@@ -111,16 +113,34 @@ StandaloneFolderDisplayWidget.prototype 
     //  having the selection object properly associated with the tree.
     if (!this.messageDisplay.isDummy) {
       this.view.dbView.setTree(this._fakeTreeBox);
       this.view.dbView.selection = this._magicTreeSelection;
     }
     this.__proto__.__proto__.onCreatedView.call(this);
   },
 
+  /**
+   * Override this to make sure that we don't try to do stuff with
+   * quick-search.
+   *
+   * XXX This should ideally be the default behavior, with a 3pane subclass
+   * implementing the quick-search stuff.
+   */
+  onDisplayingFolder:
+      function StandaloneFolderDisplayWidget_onDisplayingFolder() {
+    let msgDatabase = this.view.displayedFolder.msgDatabase;
+    if (msgDatabase) {
+      msgDatabase.resetHdrCacheSize(this.PERF_HEADER_CACHE_SIZE);
+    }
+
+    if (this.active)
+      this.makeActive();
+  },
+
   _superSelectedMessageUrisGetter:
     FolderDisplayWidget.prototype.__lookupGetter__('selectedMessageUris'),
   /**
    * Check with the message display widget to see if it has a dummy; if so, just
    *  return the dummy's URI, as the nsMsgDBView logic that our superclass uses
    *  falls down in that case.
    */
   get selectedMessageUris() {
@@ -156,28 +176,50 @@ function StandaloneMessageDisplayWidget(
    *  attachment on some mail message.
    */
   this.isDummy = false;
   /**
    * When displaying a dummy message, this is the URI of the message that we are
    *  displaying.  If we are not displaying a dummy message, this is null.
    */
   this.displayedUri = null;
+  /**
+   * This is supposed to be true when we know we're going to load another message
+   * shortly, so clearDisplay et al shouldn't close the window. It is the
+   * responsibility of calling code to set this to true when needed. This is
+   * automatically set to false once a message has been loaded.
+   *
+   * This is true initially, because we know we're going to load another message
+   * shortly.
+   */
+  this.aboutToLoadMessage = true;
 }
 StandaloneMessageDisplayWidget.prototype = {
   __proto__: MessageDisplayWidget.prototype,
 
   /**
    * The message pane is a standalone display widget is always visible.
    */
   get visible() {
     return true;
   },
   set visible(aIgnored) {
   },
+  /// We're always active
+  _active: true,
+  active: true,
+  /**
+   * Since we're always active, there's no reason for us to do anything with
+   * makeActive or makeInactive.
+   */
+  makeActive: function StandaloneMessageDisplayWidget_makeActive() {
+  },
+
+  makeInactive: function StandaloneMessageDisplayWidget_makeInactive() {
+  },
 
   /**
    * Display the external message (from disk or attachment) named by the URI.
    */
   displayExternalMessage:
       function StandaloneMessageDisplayWidget_displayExternalMessage(aUri) {
     this.isDummy = true;
     this.displayedUri = aUri;
@@ -186,17 +228,20 @@ 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;
-    window.close();
+    // 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);
@@ -205,20 +250,25 @@ StandaloneMessageDisplayWidget.prototype
     let title = aMsgHdr.mime2DecodedSubject;
     if (!gPlatformOSX)
       title += " - " + gBrandBundle.getString("brandFullName");
     document.title = title;
 
     this.isDummy = aMsgHdr.folder == null;
     if (!this.isDummy)
       this.displayedUri = null;
+
+    // We've loaded a message, so this should be set to false
+    this.aboutToLoadMessage = false;
   },
 
   onSelectedMessagesChanged: function () {
-    if (this.folderDisplay.treeSelection.count == 0) {
+    // 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) {
       window.close();
       return true;
     }
     return false;
   },
 };
 
 var messagepaneObserver = {
@@ -390,16 +440,43 @@ function actuallyLoadMessage() {
   }
 
   gFolderDisplay.makeActive();
 
   // set focus to the message pane
   window.content.focus();
 }
 
+/**
+ * Load the given message into this window, and bring it to the front. This is
+ * supposed to be called whenever a message is supposed to be displayed in this
+ * window.
+ *
+ * @param aMsgHdr the message to display
+ * @param aViewWrapperToClone [optional] a DB view wrapper to clone for the
+ *                            message window
+ */
+function displayMessage(aMsgHdr, aViewWrapperToClone)
+{
+  // We're about to load another message, so make sure we don't close this
+  // window
+  gMessageDisplay.aboutToLoadMessage = true;
+
+  if (aViewWrapperToClone)
+    gFolderDisplay.cloneView(aViewWrapperToClone);
+  else
+    gFolderDisplay.show(aMsgHdr.folder);
+
+  // show the message
+  gFolderDisplay.selectMessage(aMsgHdr);
+
+  // bring this window to the front
+  window.focus();
+}
+
 function ShowMenus()
 {
   var openMail3Pane_menuitem = document.getElementById('tasksMenuMail');
   if (openMail3Pane_menuitem)
     openMail3Pane_menuitem.removeAttribute("hidden");
 }
 
 function HideMenus()
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -40,16 +40,17 @@
  * 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 ***** */
 
 Components.utils.import("resource://gre/modules/folderUtils.jsm");
 Components.utils.import("resource://app/modules/activity/activityModules.js");
 Components.utils.import("resource://app/modules/jsTreeSelection.js");
+Components.utils.import("resource://app/modules/MailConsts.js");
 
 /* This is where functions related to the 3 pane window are kept */
 
 // from MailNewsTypes.h
 const nsMsgKey_None = 0xFFFFFFFF;
 const nsMsgViewIndex_None = 0xFFFFFFFF;
 const kMailCheckOncePrefName = "mail.startup.enabledMailCheckOnce";
 
@@ -309,16 +310,17 @@ function LoadPostAccountWizard()
 {
   InitMsgWindow();
   messenger.setWindow(window, msgWindow);
 
   InitPanes();
   MigrateAttachmentDownloadStore();
   MigrateJunkMailSettings();
   MigrateFolderViews();
+  MigrateOpenMessageBehavior();
 
   accountManager.setSpecialFolders();
   accountManager.loadVirtualFolders();
   accountManager.addIncomingServerListener(gThreePaneIncomingServerListener);
 
   gPhishingDetector.init();
 
   AddToSession();
@@ -927,24 +929,29 @@ function TreeOnMouseDown(event)
     if (event.button == 2 || event.button == 1)
     {
       // We want a single selection if this is a middle-click (button 1)
       ChangeSelectionWithoutContentLoad(event, event.target.parentNode,
                                         event.button == 1);
     }
 }
 
+function FolderPaneContextMenuNewTab()
+{
+  MsgOpenNewTabForFolder(gPrefBranch.getBoolPref("mail.contextMenuBackgroundTabs"));
+}
+
 function FolderPaneOnClick(event)
 {
   var folderTree = document.getElementById("folderTree");
 
   // Middle click on a folder opens the folder in a tab
   if (event.button == 1)
   {
-    MsgOpenNewTabForFolder();
+    FolderPaneContextMenuNewTab();
     RestoreSelectionWithoutContentLoad(folderTree);
   }
   else if (event.button == 0)
   {
     var row = {};
     var col = {};
     var elt = {};
     folderTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, elt);
@@ -957,24 +964,32 @@ function FolderPaneOnClick(event)
     }
     else if ((event.originalTarget.localName == "slider") ||
              (event.originalTarget.localName == "scrollbarbutton")) {
       event.stopPropagation();
     }
   }
 }
 
+function ThreadTreeContextMenuNewTab()
+{
+  document.getElementById('tabmail').openTab("message",
+      {msgHdr: gFolderDisplay.selectedMessage,
+       viewWrapperToClone: gFolderDisplay.view,
+       background: gPrefBranch.getBoolPref("mail.contextMenuBackgroundTabs")});
+}
+
 function ThreadTreeOnClick(event)
 {
   var threadTree = document.getElementById("threadTree");
 
   // Middle click on a message opens the message in a tab
   if (event.button == 1)
   {
-    MsgOpenNewTabForMessage();
+    ThreadTreeContextMenuNewTab();
     RestoreSelectionWithoutContentLoad(threadTree);
   }
 }
 
 function GetSelectedMsgFolders()
 {
   return gFolderTreeView.getSelectedFolders();
 }
@@ -1065,16 +1080,44 @@ function MigrateAttachmentDownloadStore(
     if (downloadsFile && downloadsFile.exists())
       downloadsFile.remove(false);
 
     // bump the version so we don't bother doing this again.
     gPrefBranch.setIntPref("mail.attachment.store.version", 1);
   }
 }
 
+// Do a one-time migration of the old mailnews.reuse_message_window pref to the
+// newer mail.openMessageBehavior. This does the migration only if the old pref
+// is defined.
+function MigrateOpenMessageBehavior()
+{
+  let openMessageBehaviorVersion = gPrefBranch.getIntPref(
+                                     "mail.openMessageBehavior.version");
+  if (!openMessageBehaviorVersion)
+  {
+    let reuseMessageWindow;
+    try {
+      reuseMessageWindow = gPrefBranch.getBoolPref(
+                             "mailnews.reuse_message_window");
+    }
+    catch (e) {}
+
+    // Don't touch this if it isn't defined
+    if (reuseMessageWindow === true)
+      gPrefBranch.setIntPref("mail.openMessageBehavior",
+          MailConsts.OpenMessageBehavior.EXISTING_WINDOW);
+    else if (reuseMessageWindow === false)
+      gPrefBranch.setIntPref("mail.openMessageBehavior",
+          MailConsts.OpenMessageBehavior.NEW_TAB);
+
+    gPrefBranch.setIntPref("mail.openMessageBehavior.version", 1);
+  }
+}
+
 function threadPaneOnDragStart(aEvent) {
   if (aEvent.originalTarget.localName != "treechildren")
     return;
 
   var messages = gFolderDisplay.selectedMessageUris;
   if (!messages)
     return;
 
--- a/mail/base/content/search.xml
+++ b/mail/base/content/search.xml
@@ -73,17 +73,18 @@
           //  to the initial search value.  Otherwise, clear it.  This
           //  is the value that is going to be saved with the current
           //  tab when we switch back to it next.
           if (tabmail.currentTabInfo.mode.name == "glodaSearch")
             this.value = tabmail.currentTabInfo.searchString;
           else
             this.value = "";
           // open a new tab with our dude
-          tabmail.openTab("glodaSearch", searchString, "everything", "everywhere");
+          tabmail.openTab("glodaSearch", {searchString: searchString,
+              facetString: "everything", locationString: "everywhere"});
         }
       ]]></handler>
     </handlers>
     
   </binding>
 
   <!--
     - The glodaFacets binding is used to display additional search constraints
--- a/mail/base/content/specialTabs.js
+++ b/mail/base/content/specialTabs.js
@@ -71,17 +71,17 @@ var specialTabs = {
     },
 
     modes: {
       contentTab: {
         type: "contentTab",
         maxTabs: 10
       }
     },
-    shouldSwitchTo: function onSwitchTo(aContentPage, aTitle) {
+    shouldSwitchTo: function onSwitchTo({contentPage: aContentPage}) {
       let tabmail = document.getElementById("tabmail");
       let tabInfo = tabmail.tabInfo;
 
       // Remove any anchors - especially for the about: pages, we just want
       // to re-use the same tab.
       let regEx = new RegExp("#.*");
 
       let contentUrl = aContentPage.replace(regEx, "");
@@ -95,17 +95,17 @@ var specialTabs = {
           // Ensure we go to the correct location on the page.
           tabInfo[selectedIndex].panel.firstChild
                                 .setAttribute("src", aContentPage);
           return selectedIndex;
         }
       }
       return -1;
     },
-    openTab: function onTabOpened(aTab, aContentPage) {
+    openTab: function onTabOpened(aTab, {contentPage: aContentPage}) {
       // You can't dynamically change an iframe from a non-content to a content
       // type, therefore we dynamically create the element instead.
       let iframe = document.createElement("browser");
       iframe.setAttribute("type", "content-primary");
       iframe.setAttribute("flex", "1");
       iframe.setAttribute("autocompleteenabled", false);
       iframe.setAttribute("disablehistory", true);
       iframe.setAttribute("id", "contentTabType" + this.lastBrowserId);
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -68,19 +68,22 @@
     -
     - From a javascript perspective, there are three types of code that we
     -  expect to interact with:
     - 1) Code that wants to open new tabs.
     - 2) Code that wants to contribute one or more varieties of tabs.
     - 3) Code that wants to monitor to know when the active tab changes.
     -
     - Consumer code should use the following methods:
-    - * openTab(aTabModeName, arguments...): Open a tab of the given "mode",
-    -   passing the provided arguments.  The tab type author should tell you
-    -   the modes they implement and the required/optional arguments.
+    - * openTab(aTabModeName, aArgs): Open a tab of the given "mode",
+    -   passing the provided arguments as an object.  The tab type author
+    -   should tell you the modes they implement and the required/optional
+    -   arguments.
+    -   One of the arguments you can pass is "background": if this is true,
+    -   the tab will be loaded in the background.
     - * closeTab(aOptionalTabIndexInfoOrTabNode): If no argument is provided,
     -   the current tab is closed.  If an argument is provided, it can be
     -   a tab index, a tab info object, or the tabmail-tab bound element
     -   that _is_ the tab.  Some tabs cannot be closed, in which case this
     -   will do nothing.
     - * switchToTab(aTabIndexInfoOrTabNode): Switch to the tab by providing
     -   a tab index, tab info object, or tab node (tabmail-tab bound
     -   element.)  You can also just poke at tabmail.tabContainer and its
@@ -148,32 +151,33 @@
     -     Generally, this would be the same as the mode name, but you can do as
     -     you please.
     - * isDefault: This should only be present and should be true for the tab
     -     mode that is the tab displayed automatically on startup.
     - * maxTabs: The maximum number of this mode that can be opened at a time.
     -     If this limit is reached, any additional calls to openTab for this
     -     mode will simply result in the first existing tab of this mode being
     -     displayed.
-    - * shouldSwitchTo(arguments...): Optional function. Called when
-    -     openTab is called on the top-level tabmail binding. It is used to
-    -     decide if the openTab function should switch to an existing tab or
-    -     actually open a new tab.
+    - * shouldSwitchTo(aArgs): Optional function. Called when openTab is called
+    -     on the top-level tabmail binding. It is used to decide if the openTab
+    -     function should switch to an existing tab or actually open a new tab.
     -     If the openTab function should switch to an existing tab, return the
     -     index of that tab; otherwise return -1.
-    - * openTab(aTab, arguments...): Called when a tab of the given mode is
-    -     in the process of being opened.  aTab will have its "mode" attribute
+    -     aArgs is a set of named parameters (the ones that are later passed to
+    -     openTab).
+    - * openTab(aTab, aArgs): Called when a tab of the given mode is in the
+    -     process of being opened.  aTab will have its "mode" attribute
     -     set to the mode definition of the tab mode being opened.  You should
     -     set the "title" attribute on it, and may set any other attributes
     -     you wish for your own use in subsequent functions.  Note that 'this'
     -     points to the tab type definition, not the mode definition as you
     -     might expect.  This allows you to place common logic code on the
     -     tab type for use by multiple modes and to defer to it.  Any arguments
     -     provided to the caller of tabmail.openTab will be passed to your
-    -     function as well.
+    -     function as well, including background.
     - * closeTab(aTab): Called when aTab is being closed.  The tab need not be
     -     currently displayed.  You are responsible for properly cleaning up
     -     any state you preserved in aTab.
     - * saveTabState(aTab): Called when aTab is being switched away from so that
     -     you can preserve its state on aTab.  This is primarily for single
     -     tab panel implementations; you may not have much state to save if your
     -     tab has its own tab panel.
     - * showTab(aTab): Called when aTab is being displayed and you should
@@ -404,42 +408,48 @@
               for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
                 tabMonitor.onTabSwitched(firstTab, null);
             }
           }
         ]]></body>
       </method>
       <method name="openTab">
         <parameter name="aTabModeName"/>
+        <parameter name="aArgs"/>
         <body>
             <![CDATA[
           let tabMode = this.tabModes[aTabModeName];
           // if we are already at our limit for this mode, show an existing one
           if (tabMode.tabs.length == tabMode.maxTabs) {
             let desiredTab = tabMode.tabs[0];
             this.tabContainer.selectedIndex = this.tabInfo.indexOf(desiredTab);
             return;
           }
 
+          // Do this so that we don't generate strict warnings
+          let background = ("background" in aArgs) && aArgs.background;
+
           // If the mode wants us to, we should switch to an existing tab
-          // rather than open a new one.
+          // rather than open a new one. We shouldn't switch to the tab if
+          // we're opening it in the background, though.
           let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
                                    tabMode.tabType.shouldSwitchTo;
 
           if (shouldSwitchToFunc) {
-            let args = Array.prototype.slice.call(arguments, 1);
-            let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, args);
+            let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, [aArgs]);
             if (tabIndex >= 0) {
-              this.selectTabByIndex(null, tabIndex);
+              if (!background)
+                this.selectTabByIndex(null, tabIndex);
               return;
             }
           }
 
-          // we need to save the state before it gets corrupted
-          this.saveCurrentTabState();
+          if (!background)
+            // we need to save the state before it gets corrupted
+            this.saveCurrentTabState();
 
           let tab = {mode: tabMode, busy: false, canClose: true};
           tabMode.tabs.push(tab);
 
           var t = document.createElementNS(
             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
             "tab");
           tab.tabNode = t;
@@ -454,60 +464,64 @@
           if (this.tabContainer.collapsed) {
             this.tabContainer.collapsed = false;
             this.tabContainer.adjustTabstrip();
           }
 
           let oldTab = this._mostRecentTabInfo = this.currentTabInfo;
 
           // the order of the following statements is important
-          this.currentTabInfo = tab;
           this.tabInfo[this.tabContainer.childNodes.length - 1] = tab;
-          // this has a side effect of calling updateCurrentTab, but our
-          //  setting currentTabInfo above will cause it to take no action.
-          this.tabContainer.selectedIndex =
-            this.tabContainer.childNodes.length - 1;
+          if (!background) {
+            this.currentTabInfo = tab;
+            // this has a side effect of calling updateCurrentTab, but our
+            //  setting currentTabInfo above will cause it to take no action.
+            this.tabContainer.selectedIndex =
+              this.tabContainer.childNodes.length - 1;
+          }
 
           // make sure we are on the right panel
           if (tab.mode.tabType.perTabPanel) {
             // should we create the element for them, or will they do it?
             if (typeof(tab.mode.tabType.perTabPanel) == "string") {
               tab.panel = document.createElementNS(
                 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                 tab.mode.tabType.perTabPanel);
             }
             else {
               tab.panel = tab.mode.tabType.perTabPanel(tab);
             }
             this.panelContainer.appendChild(tab.panel);
-            this.panelContainer.selectedPanel = tab.panel;
+            if (!background)
+              this.panelContainer.selectedPanel = tab.panel;
           }
-          else
+          else if (!background)
             this.panelContainer.selectedPanel = tab.mode.tabType.panel;
-            
+
           let tabOpenFunc = tab.mode.openTab || tab.mode.tabType.openTab;
-          let args = [tab].concat(Array.prototype.slice.call(arguments, 1));
-          tabOpenFunc.apply(tab.mode.tabType, args);
-          
-          if (this.tabMonitors.length) {
+          tabOpenFunc.apply(tab.mode.tabType, [tab, aArgs]);
+
+          if (!background && this.tabMonitors.length) {
             for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
               tabMonitor.onTabSwitched(tab, oldTab);
           }
           
           // clear _mostRecentTabInfo; we only needed it during the call to
           //  openTab.
           this._mostRecentTabInfo = null;
           
           t.setAttribute("label", tab.title);
-          
-          let docTitle = tab.title;
-          if (!gPlatformOSX)
-            docTitle += " - " + gBrandBundle.getString("brandFullName");
-          document.title = docTitle;
-          
+
+          if (!background) {
+            let docTitle = tab.title;
+            if (!gPlatformOSX)
+              docTitle += " - " + gBrandBundle.getString("brandFullName");
+            document.title = docTitle;
+          }
+
           // for styling purposes, apply the type to the tab...
           t.setAttribute('type', tab.mode.type);
 
           // Update the toolbar status - we don't need to do menus as they
           // do themselves when we open them.
           UpdateMailToolbar("tabmail");
 
           return tab;
--- a/mail/base/content/widgetglue.js
+++ b/mail/base/content/widgetglue.js
@@ -39,16 +39,18 @@
  *  ***** END LICENSE BLOCK ***** */
 
 /*
  * widget-specific wrapper glue. There should be one function for every
  * widget/menu item, which gets some context (like the current selection)
  * and then calls a function/command in commandglue
  */
 
+Components.utils.import("resource://app/modules/MailUtils.js");
+
 //The eventual goal is for this file to go away and its contents to be brought into
 //mailWindowOverlay.js.  This is currently being done.
 
 function MsgToggleMessagePane()
 {
   // Bail without doing anything if we are not a folder tab.
   let currentTabInfo = document.getElementById("tabmail").currentTabInfo;
   if (currentTabInfo.mode.name != "folder")
@@ -66,26 +68,18 @@ function MsgToggleMessagePane()
 // Given a URI we would like to return corresponding message folder here.
 // An additonal input param which specifies whether or not to check folder
 // attributes (like if there exists a parent or is it a server) is also passed
 // to this routine. Qualifying against those checks would return an existing
 // folder. Callers who don't want to check those attributes will specify the
 // same and then this routine will simply return a msgfolder. This scenario
 // applies to a new imap account creation where special folders are created
 // on demand and hence needs to prior check of existence.
+
+/**
+ * Gets the message folder for this URI.
+ *
+ * @deprecated Use |MailUtils.getFolderForURI| instead.
+ */
 function GetMsgFolderFromUri(uri, checkFolderAttributes)
 {
-    var msgfolder = null;
-    try {
-        var rdfService = Components.classes['@mozilla.org/rdf/rdf-service;1']
-                                   .getService(Components.interfaces.nsIRDFService);
-        var resource = rdfService.GetResource(uri);
-        msgfolder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
-        if (checkFolderAttributes) {
-            if (!(msgfolder && (msgfolder.parent || msgfolder.isServer))) {
-                msgfolder = null;
-            }
-        }
-    }
-    catch (ex) {
-    }
-    return msgfolder;
+  return MailUtils.getFolderForURI(uri, checkFolderAttributes);
 }
new file mode 100644
--- /dev/null
+++ b/mail/base/modules/MailConsts.js
@@ -0,0 +1,73 @@
+/* ***** 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 mozilla.org code.
+ *
+ * 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):
+ *   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
+ * 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 ***** */
+
+/**
+ * This is a place to store constants and enumerations that are needed only by
+ * JavaScript code, especially component/module code.
+ */
+
+var EXPORTED_SYMBOLS = ["MailConsts"];
+
+var MailConsts =
+{
+  /**
+   * Determine how to open a message when it is double-clicked or selected and
+   * Enter pressed. The preference to set this is mail.openMessageBehavior.
+   */
+  OpenMessageBehavior: {
+    /**
+     * Open the message in a new window. If multiple messages are selected, all
+     * of them are opened in separate windows.
+     */
+    NEW_WINDOW: 0,
+
+    /**
+     * Open the message in an existing window. If multiple messages are
+     * selected, the fallback is to "new window" behavior. If no standalone
+     * windows are open, the message is opened in a new standalone window.
+     */
+    EXISTING_WINDOW: 1,
+
+    /**
+     * Open the message in a new tab. If multiple messages are selected, all of
+     * them are opened as tabs, with the last tab in the foreground and all the
+     * rest in the background. If no 3-pane window is open, the message is
+     * opened in a new standalone window.
+     */
+    NEW_TAB: 2
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/base/modules/MailUtils.js
@@ -0,0 +1,206 @@
+/* ***** 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 mozilla.org code.
+ *
+ * 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):
+ *   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
+ * 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 ***** */
+
+var EXPORTED_SYMBOLS = ["MailUtils"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
+Components.utils.import("resource://app/modules/MailConsts.js");
+const MC = MailConsts;
+
+/**
+ * This module has several utility functions for use by both core and
+ * third-party code. Some functions are aimed at code that doesn't have a
+ * window context, while others can be used anywhere.
+ */
+var MailUtils =
+{
+  /**
+   * A reference to the root pref branch
+   */
+  get _prefBranch MailUtils_get_prefBranch() {
+    delete this._prefBranch;
+    return this._prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                                .getService(Ci.nsIPrefService)
+                                .getBranch(null);
+  },
+
+  /**
+   * Discover all folders. This is useful during startup, when you have code
+   * that deals with folders and that executes before the main 3pane window is
+   * open (the folder tree wouldn't have been initialized yet).
+   */
+  discoverFolders: function MailUtils_discoverFolders() {
+    let accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+                           .getService(Ci.nsIMsgAccountManager);
+    let servers = accountManager.allServers;
+    for each (let server in fixIterator(servers, Ci.nsIMsgIncomingServer))
+      server.rootFolder.subFolders;
+  },
+
+  /**
+   * Get the nsIMsgFolder corresponding to this file. This just looks at all
+   * folders and does a direct match.
+   *
+   * One of the places this is used is desktop search integration -- to open
+   * the search result corresponding to a mozeml/wdseml file, we need to figure
+   * out the folder using the file's path.
+   *
+   * @param aFile the nsILocalFile to convert to a folder
+   * @returns the nsIMsgFolder corresponding to aFile, or null if the folder
+   *          isn't found
+   */
+  getFolderForFileInProfile:
+      function MailUtils_getFolderForFileInProfile(aFile) {
+    let accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+                           .getService(Ci.nsIMsgAccountManager);
+    let folders = accountManager.allFolders;
+
+    for each (let folder in fixIterator(folders.enumerate(), Ci.nsIMsgFolder)) {
+      if (folder.filePath.equals(aFile))
+        return folder;
+    }
+    return null;
+  },
+
+  /**
+   * Get the nsIMsgFolder corresponding to this URI. This uses the RDF service
+   * to do the work.
+   *
+   * @param aFolderURI the URI to convert into a folder
+   * @param aCheckFolderAttributes whether to check that the folder either has
+   *                              a parent or isn't a server
+   * @returns the nsIMsgFolder corresponding to this URI, or null if
+   *          aCheckFolderAttributes is true and the folder doesn't have a
+   *          parent or is a server
+   */
+  getFolderForURI: function MailUtils_getFolderForURI(aFolderURI,
+                       aCheckFolderAttributes) {
+    let folder = null;
+    let rdfService = Cc['@mozilla.org/rdf/rdf-service;1']
+                       .getService(Ci.nsIRDFService);
+    folder = rdfService.GetResource(aFolderURI);
+    // This is going to QI the folder to an nsIMsgFolder as well
+    if (folder && folder instanceof Ci.nsIMsgFolder) {
+      if (aCheckFolderAttributes && !(folder.parent || folder.isServer))
+        return null;
+    }
+    else {
+      return null;
+    }
+
+    return folder;
+  },
+
+  /**
+   * Displays this message header in a new tab, a new window or an existing
+   * window, depending on the preference and whether a 3pane or standalone
+   * window is already open. This function should be called when you'd like to
+   * display a message to the user according to the pref set.
+   *
+   * @param aMsgHdr the message header to display
+   */
+  displayMessage: function MailUtils_displayMessage(aMsgHdr) {
+    let openMessageBehavior = this._prefBranch.getIntPref(
+                                  "mail.openMessageBehavior");
+
+    if (openMessageBehavior == MC.OpenMessageBehavior.NEW_WINDOW) {
+      this.openMessageInNewWindow(aMsgHdr);
+    }
+    else if (openMessageBehavior == MC.OpenMessageBehavior.EXISTING_WINDOW) {
+      // Try reusing an existing window. If we can't, fall back to opening a
+      // new window
+      if (!this.openMessageInExistingWindow(aMsgHdr))
+        this.openMessageInNewWindow(aMsgHdr);
+    }
+    else if (openMessageBehavior == MC.OpenMessageBehavior.NEW_TAB) {
+      // Try opening a new tab in a 3pane window. If we can't, fall back to
+      // opening a new window
+      let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
+                             .getService(Ci.nsIWindowMediator);
+      let mail3PaneWindow = windowMediator.getMostRecentWindow("mail:3pane");
+      // Do this directly instead of calling MsgOpenNewTabForMessage, because
+      // that passes in gFolderDisplay.view to be cloned, and we don't want
+      // that
+      if (mail3PaneWindow) {
+        mail3PaneWindow.document.getElementById("tabmail").openTab("message",
+            {msgHdr: aMsgHdr, background: false});
+        mail3PaneWindow.focus();
+      }
+      else {
+        this.openMessageInNewWindow(aMsgHdr);
+      }
+    }
+  },
+
+  /**
+   * Show this message in an existing window.
+   *
+   * @param aMsgHdr the message header to display
+   * @param aViewWrapperToClone [optional] a DB view wrapper to clone for the
+   *                            message window
+   * @returns true if an existing window was found and the message header was
+   *          displayed, false otherwise
+   */
+  openMessageInExistingWindow:
+      function MailUtils_openMessageInExistingWindow(aMsgHdr,
+                                                     aViewWrapperToClone) {
+    let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
+                           .getService(Ci.nsIWindowMediator);
+    let messageWindow = windowMediator.getMostRecentWindow("mail:messageWindow");
+    if (messageWindow) {
+      messageWindow.displayMessage(aMsgHdr, aViewWrapperToClone);
+      return true;
+    }
+    return false;
+  },
+
+  /**
+   * Open a new standalone message window with this header.
+   *
+   * @param aMsgHdr the message header to display
+   */
+  openMessageInNewWindow: function MailUtils_openMessageInNewWindow(aMsgHdr) {
+    let windowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+                          .getService(Ci.nsIWindowWatcher);
+    windowWatcher.openWindow(null,
+        "chrome://messenger/content/messageWindow.xul", "_blank",
+        "all,chrome,dialog=no,status,toolbar", aMsgHdr);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/base/modules/Makefile.in
@@ -0,0 +1,51 @@
+#
+# ***** 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 mozilla.org code.
+#
+# 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):
+#   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
+# 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 *****
+
+DEPTH   = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH   = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_JS_MODULES = \
+  MailConsts.js \
+  MailUtils.js \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
--- a/mail/components/nsMailDefaultHandler.js
+++ b/mail/components/nsMailDefaultHandler.js
@@ -16,16 +16,17 @@
  *
  * The Initial Developer of the Original Code is
  * Benjamin Smedberg <benjamin@smedbergs.us>
  *
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   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
@@ -84,16 +85,34 @@ function resolveURIInternal(aCmdLine, aA
   }
   catch (e) {
     Components.utils.reportError(e);
   }
 
   return uri;
 }
 
+function handleIndexerResult(aFile) {
+  // Do this here because xpcshell isn't too happy with this at startup
+  Components.utils.import("resource://app/modules/MailUtils.js");
+  // Make sure the folder tree is initialized
+  MailUtils.discoverFolders();
+
+  // Use the search integration module to convert the indexer result into a
+  // message header
+  Components.utils.import("resource://app/modules/SearchIntegration.js");
+  let msgHdr = SearchIntegration.handleResult(aFile);
+
+  // If we found a message header, open it, otherwise throw an exception
+  if (msgHdr)
+    MailUtils.displayMessage(msgHdr);
+  else
+    throw Components.results.NS_ERROR_FAILURE;
+}
+
 function mayOpenURI(uri)
 {
   var ext = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
     .getService(Components.interfaces.nsIExternalProtocolService);
 
   return ext.isExposedProtocol(uri.scheme);
 }
 
@@ -264,16 +283,28 @@ var nsMailDefaultHandler = {
         dump(e);
       }
     }
 
     if (cmdLine.handleFlag("silent", false)) {
       cmdLine.preventDefault = true;
     }
 
+    if (cmdLine.handleFlag("options", false)) {
+      // Open the options window
+      var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+                             .getService(nsIWindowWatcher);
+      wwatch.openWindow(null,
+          "chrome://messenger/content/preferences/preferences.xul", "_blank",
+          "chrome,dialog=no,all", null);
+    }
+
+    // The URI might be passed as the argument to the file parameter
+    uri = cmdLine.handleFlagWithParam("file", false);
+
     var count = cmdLine.length;
     if (count) {
       var i = 0;
       while (i < count) {
         var curarg = cmdLine.getArgument(i);
         if (!curarg.match(/^-/))
           break;
 
@@ -326,17 +357,62 @@ var nsMailDefaultHandler = {
         try {
           Components.classes["@mozilla.org/newsblog-feed-downloader;1"]
                     .getService(Components.interfaces.nsINewsBlogFeedDownloader)
                     .subscribeToFeed(uri, null, null);
         }
         catch (e) {
           // If feed handling is not installed, do nothing
         }
-      } else {
+      }
+      else if (/\.mozeml$/i.test(uri) || /\.wdseml$/i.test(uri)) {
+        handleIndexerResult(cmdLine.resolveFile(uri));
+        cmdLine.preventDefault = true;
+      }
+      else if (/\.eml$/i.test(uri)) {
+        // Open this eml in a new message window
+        let file = cmdLine.resolveFile(uri);
+        // No point in trying to open a file if it doesn't exist or is empty
+        if (file.exists() && file.fileSize > 0) {
+          // Get the URL for this file
+          let ios = Components.classes["@mozilla.org/network/io-service;1"]
+                              .getService(Components.interfaces.nsIIOService);
+          let fileURL = ios.newFileURI(file)
+                           .QueryInterface(Components.interfaces.nsIFileURL);
+          fileURL.query = "?type=application/x-message-display";
+
+          let wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+                                 .getService(nsIWindowWatcher);
+          wwatch.openWindow(null,
+                            "chrome://messenger/content/messageWindow.xul",
+                            "_blank", "all,chrome,dialog=no,status,toolbar",
+                            fileURL);
+          cmdLine.preventDefault = true;
+        }
+        else {
+          let bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                 .getService(Components.interfaces.nsIStringBundleService)
+                                 .createBundle("chrome://messenger/locale/messenger.properties");
+          let title, message;
+          if (!file.exists()) {
+            title = bundle.GetStringFromName("fileNotFoundTitle");
+            message = bundle.formatStringFromName("fileNotFoundMsg", [file.path], 1);
+          }
+          else {
+            // The file is empty
+            title = bundle.GetStringFromName("fileEmptyTitle");
+            message = bundle.formatStringFromName("fileEmptyMsg", [file.path], 1);
+          }
+
+          let promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                                        .getService(Components.interfaces.nsIPromptService);
+          promptService.alert(null, title, message);
+        }
+      }
+      else {
         openURI(cmdLine.resolveURI(uri));
         // XXX: add error-handling here! (error dialog, if nothing else)
       }
     } else {
       var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                              .getService(nsIWindowWatcher);
 
       var argstring = Components.classes["@mozilla.org/supports-string;1"]
@@ -371,17 +447,18 @@ var nsMailDefaultHandler = {
       var param = cmdLine.getArgument(actionFlagIdx + 1);
       if (cmdLine.length != actionFlagIdx + 2 ||
           /thunderbird.url.(mailto|news):/.test(param))
         throw NS_ERROR_ABORT;
       cmdLine.handleFlag("osint", false)
     }
   },
 
-  helpInfo : "",
+  helpInfo : "  -options             Open the options dialog.\n" +
+             "  -file                Open the specified email file.\n",
 
   /* nsIFactory */
 
   createInstance : function mdh_CI(outer, iid) {
     if (outer != null)
       throw Components.results.NS_ERROR_NO_AGGREGATION;
 
     return this.QueryInterface(iid);
--- a/mail/components/preferences/advanced.xul
+++ b/mail/components/preferences/advanced.xul
@@ -94,17 +94,17 @@
       <preference id="mail.showCondensedAddresses" name="mail.showCondensedAddresses" type="bool"/>
       <preference id="mailnews.mark_message_read.auto"
                   name="mailnews.mark_message_read.auto" type="bool"/>
       <preference id="mailnews.mark_message_read.delay"
                   name="mailnews.mark_message_read.delay" type="bool"
                   onchange="gAdvancedPane.updateMarkAsReadTextbox(this.value);"/>
       <preference id="mailnews.mark_message_read.delay.interval"
                   name="mailnews.mark_message_read.delay.interval" type="int"/>
-      <preference id="mailnews.reuse_message_window" name="mailnews.reuse_message_window" type="bool"/>
+      <preference id="mail.openMessageBehavior" name="mail.openMessageBehavior" type="int"/>
       <preference id="mail.close_message_window.on_delete" 
                   name="mail.close_message_window.on_delete" type="bool"/>
       <!-- Network tab -->
       <preference id="mail.prompt_purge_threshhold"          name="mail.prompt_purge_threshhold"    type="bool"/>
       <preference id="mail.purge_threshhold"                 name="mail.purge_threshhold"    type="int"/>
       <preference id="browser.cache.disk.capacity"
                   name="browser.cache.disk.capacity" type="int"/>
       <!-- Update tab -->
@@ -231,26 +231,28 @@
                   <label id="secondsLabel" value="&secondsLabel.label;"/>
                 </hbox>
               </radiogroup>
             </vbox>
 
             <vbox>
               <hbox>
                 <label value="&openMsgIn.label;"
-                       control="mailnewsDoubleClick2NewWindow"/>
+                       control="mailOpenMessageBehavior"/>
               </hbox>
 
               <hbox>
-                <radiogroup id="mailnewsDoubleClick2NewWindow" class="indent"
-                            preference="mailnews.reuse_message_window"
+                <radiogroup id="mailOpenMessageBehavior" class="indent"
+                            preference="mail.openMessageBehavior"
                             orient="horizontal">
-                  <radio id="new" value="false" label="&reuseExpRadio0.label;"
+                  <radio id="newTab" value="2" label="&openMsgInNewTab.label;"
+                         accesskey="&openMsgInNewTab.accesskey;"/>
+                  <radio id="newWindow" value="0" label="&reuseExpRadio0.label;"
                          accesskey="&reuseExpRadio0.accesskey;"/>
-                  <radio id="existing" value="true"
+                  <radio id="existingWindow" value="1"
                          label="&reuseExpRadio1.label;"
                          accesskey="&reuseExpRadio1.accesskey;"/>
                 </radiogroup>
               </hbox>
               <hbox>
                 <checkbox id="closeMsgWindowOnDelete"
                           label="&closeMsgWindowOnDelete.label;"
                           accesskey="&closeMsgWindowOnDelete.accesskey;"
--- a/mail/components/search/SpotlightIntegration.js
+++ b/mail/components/search/SpotlightIntegration.js
@@ -61,29 +61,53 @@ let SearchIntegration =
   get _profileDir()
   {
     delete this._profileDir;
     return this._profileDir = Cc["@mozilla.org/file/directory_service;1"]
                                 .getService(Ci.nsIProperties)
                                 .get("ProfD", Ci.nsIFile);
   },
 
+  get _metadataDir()
+  {
+    delete this._metadataDir;
+    let metadataDir = Cc["@mozilla.org/file/directory_service;1"]
+                        .getService(Ci.nsIProperties)
+                        .get("Home", Ci.nsIFile);
+    metadataDir.append("Library");
+    metadataDir.append("Caches");
+    metadataDir.append("Metadata");
+    metadataDir.append("Thunderbird");
+    return this._metadataDir = metadataDir;
+  },
+
   /// Spotlight won't index files in the profile dir, but will use ~/Library/Caches/Metadata
   _getSearchPathForFolder: function spotlight_get_search_path(aFolder)
   {
     // Swap the metadata dir for the profile dir prefix in the folder's path
     let folderPath = aFolder.filePath.path;
     let fixedPath = folderPath.replace(this._profileDir.path,
-                                       "~/Library/Caches/Metadata/Thunderbird");
+                                       this._metadataDir.path);
     let searchPath = Cc["@mozilla.org/file/local;1"]
                        .createInstance(Ci.nsILocalFile);
     searchPath.initWithPath(fixedPath);
     return searchPath;
   },
 
+  /// Replace ~/Library/Caches/Metadata with the profile directory, then convert
+  _getFolderForSearchPath: function spotlight_get_folder_for_search_path(aPath)
+  {
+    let folderPath = aPath.path.replace(this._metadataDir.path,
+                                        this._profileDir.path);
+    let folderFile = Cc["@mozilla.org/file/local;1"]
+                      .createInstance(Ci.nsILocalFile);
+    folderFile.initWithPath(folderPath);
+    return MailUtils.getFolderForFileInProfile(folderFile);
+  },
+
   /**
    * These two functions won't do anything, as Spotlight integration is handled
    * using Info.plist files
    */
   register: function spotlight_register()
   {
     return true;
   },
--- a/mail/components/search/WinSearchIntegration.js
+++ b/mail/components/search/WinSearchIntegration.js
@@ -155,16 +155,22 @@ let SearchIntegration =
   },
 
   /// Use the folder's path (i.e., in profile dir) as is
   _getSearchPathForFolder: function winsearch_get_search_path(aFolder)
   {
     return aFolder.filePath;
   },
 
+  /// Use the search path as is
+  _getFolderForSearchPath: function winsearch_get_folder_for_search_path(aDir)
+  {
+    return MailUtils.getFolderForFileInProfile(aDir);
+  },
+
   _init: function winsearch_init()
   {
     this._initLogging();
     // We're currently only enabled on Vista and above
     let sysInfo = Cc["@mozilla.org/system-info;1"]
                     .getService(Ci.nsIPropertyBag2);
     let windowsVersion = sysInfo.getProperty("version");
     if (parseFloat(windowsVersion) < 6)
--- a/mail/components/search/content/searchCommon.js
+++ b/mail/components/search/content/searchCommon.js
@@ -50,16 +50,17 @@
 #endif
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/iteratorUtils.jsm");
 Cu.import("resource://app/modules/gloda/log4moz.js");
+Cu.import("resource://app/modules/MailUtils.js");
 
 let SearchSupport =
 {
   /**
    * URI of last folder indexed. Kept in sync with the pref
    */
   __lastFolderIndexedUri: null,
   set _lastFolderIndexedUri(uri)
@@ -729,16 +730,40 @@ let SearchSupport =
       this._log.info("generating support file for id = " + msgHdr.messageId);
       this._streamListener.startStreaming(msgHdr, reindexTime);
     }
     else
       this._log.info("queueing support file generation for id = " +
                      msgHdr.messageId);
   },
 
+  /**
+   * Handle results from the command line. This method is the inverse of the
+   * _getSupportFile method below.
+   *
+   * @param aFile the file passed in by the command line
+   * @return the nsIMsgDBHdr corresponding to the file passed in
+   */
+  handleResult: function search_handle_result(aFile)
+  {
+    // The file path has two components -- the search path, which needs to be
+    // converted into a folder, and the message ID.
+    let searchPath = aFile.parent;
+    // Strip off ".mozmsgs" from the end (8 characters)
+    searchPath.leafName = searchPath.leafName.slice(0, -8);
+
+    let folder = this._getFolderForSearchPath(searchPath);
+
+    // Get rid of the file extension at the end (7 characters), and unescape
+    let messageID = decodeURIComponent(aFile.leafName.slice(0, -7));
+
+    // Look for the message ID in the folder
+    return folder.msgDatabase.getMsgHdrForMessageID(messageID);
+  },
+
   _getSupportFile: function search_get_support_file(msgHdr)
   {
     let folder = msgHdr.folder;
     if (folder)
     {
       let messageId = encodeURIComponent(msgHdr.messageId);
       this._log.debug("encoded message id = " + messageId);
       let file = this._getSearchPathForFolder(folder);
--- a/mail/installer/windows/packages-static
+++ b/mail/installer/windows/packages-static
@@ -78,16 +78,17 @@ bin\components\msgnews.xpt
 bin\components\msgsearch.xpt
 bin\components\import.xpt
 bin\components\impComm4xMail.xpt
 bin\components\mailview.xpt
 bin\components\mailprofilemigration.xpt
 bin\components\nsActivity.js
 bin\components\nsActivityManager.js
 bin\components\nsActivityManagerUI.js
+bin\components\nsMailNewsCommandLineHandler.js
 bin\components\shellservice.xpt
 bin\components\xpcom_base.xpt
 bin\components\xpcom_system.xpt
 bin\components\xpcom_components.xpt
 bin\components\xpcom_ds.xpt
 bin\components\xpcom_io.xpt
 bin\components\xpcom_thread.xpt
 bin\components\xpcom_xpti.xpt
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -477,16 +477,20 @@ emptyTrashDontAsk=Don't ask me again.
 junkAnalysisPercentComplete=Junk analysis %S complete
 processingJunkMessages=Processing Junk Messages
 
 # Messenger bootstrapping messages
 fileNotFoundTitle = File Not Found
 #LOCALIZATION NOTE(fileNotFoundMsg): %S is the filename
 fileNotFoundMsg = The file %S does not exist.
 
+fileEmptyTitle = File Empty
+#LOCALIZATION NOTE(fileEmptyMsg): %S is the filename
+fileEmptyMsg = The file %S is empty.
+
 # second person direct object pronoun; used in the collapsed header view if
 # the user is in the To or Cc field of a message
 headerFieldYou=You
 
 # Shown when content tabs are being loaded.
 loadingTab=Loading…
 
 applyToCollapsedMsgsTitle=Confirm Delete of Messages in Collapsed Thread(s)
--- a/mail/locales/en-US/chrome/messenger/preferences/advanced.dtd
+++ b/mail/locales/en-US/chrome/messenger/preferences/advanced.dtd
@@ -34,16 +34,18 @@
 <!ENTITY markAsReadNoDelay.accesskey   "o">
 <!-- LOCALIZATION NOTE (markAsReadDelay.label): This will concatenate to
      "After displaying for [___] seconds",
      using (markAsReadDelay.label) and a number (secondsLabel.label). -->
 <!ENTITY markAsReadDelay.label         "After displaying for">
 <!ENTITY markAsReadDelay.accesskey     "d">
 <!ENTITY secondsLabel.label            "seconds">
 <!ENTITY openMsgIn.label               "Open messages in:">
+<!ENTITY openMsgInNewTab.label         "A new tab">
+<!ENTITY openMsgInNewTab.accesskey     "t">
 <!ENTITY reuseExpRadio0.label          "A new message window">
 <!ENTITY reuseExpRadio0.accesskey      "n">
 <!ENTITY reuseExpRadio1.label          "An existing message window">
 <!ENTITY reuseExpRadio1.accesskey      "e">
 <!ENTITY closeMsgWindowOnDelete.label  "Close message window on delete">
 <!ENTITY closeMsgWindowOnDelete.accesskey "C">
 
 <!-- Update -->
--- a/mail/test/mozmill/folder-display/test-deletion-with-multiple-displays.js
+++ b/mail/test/mozmill/folder-display/test-deletion-with-multiple-displays.js
@@ -33,17 +33,19 @@
  * 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 ***** */
 
 /*
  * Test that deleting a message in a given tab or window properly updates both
  *  that tab/window as well as all other tabs/windows.  We also test that the
- *  message tab title updates appropriately through all of this.
+ *  message tab title updates appropriately through all of this. We do all of
+ *  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;
 
@@ -56,17 +58,17 @@ function setupModule(module) {
   folder = create_folder("DeletionA");
   // 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}]);
 }
 
 
-var tabFolder, tabMessage, curMessage, nextMessage;
+var tabFolder, tabMessage, tabMessageBackground, curMessage, nextMessage;
 
 /**
  * The message window controller.  Short names because controllers get used a
  *  lot.
  */
 var msgc;
 
 /**
@@ -81,16 +83,23 @@ function test_open_message_in_all_three_
   curMessage = select_click_row(0);
   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
+  switch_tab(tabFolder);
+
+  // - Open another tab with the message, this time in the background
+  tabMessageBackground = open_selected_message_in_new_tab(true);
+  assert_tab_titled_from(tabMessageBackground, curMessage);
+
   // - 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);
 }
 
 /**
@@ -110,32 +119,38 @@ function test_delete_in_folder_tab() {
 
   // - 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);
 }
 
 /**
  * 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.)
   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);
+
   // - 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?");
@@ -157,28 +172,28 @@ function test_delete_in_message_window()
   // - 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);
 }
 
 /**
  * Delete the last message in that folder, which should close all message
- *  displays.  For comprehensiveness, first open an additional message tab
- *  of the message so that we can test foreground and background closing at the
- *  same time.
+ *  displays.
  */
 function test_delete_last_message_closes_message_displays() {
-  // - open the additional message tab
-  switch_tab(tabFolder);
-  let tabMessage2 = open_selected_message_in_new_tab();
+  // - 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
   press_delete();
 
   // - the message window should have gone away...
--- a/mail/test/mozmill/folder-display/test-message-window.js
+++ b/mail/test/mozmill/folder-display/test-message-window.js
@@ -84,9 +84,9 @@ function test_navigate_to_next_message()
 
 /**
  * Close the window by hitting escape.
  */
 function test_close_message_window() {
   plan_for_window_close(msgc);
   msgc.keypress(null, "VK_ESCAPE", {});
   wait_for_window_close(msgc);
-}
\ No newline at end of file
+}
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-opening-messages.js
@@ -0,0 +1,181 @@
+/* ***** 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 Thunderbird Mail Client.
+ *
+ * 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):
+ *   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
+ * 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 ***** */
+
+/*
+ * Test that we open single and multiple messages from the thread pane
+ * according to the mail.openMessageBehavior preference, and that we have the
+ * correct message headers displayed in whatever we open.
+ *
+ * Currently tested:
+ * - opening single and multiple messages in tabs
+ * - opening a single message in a window. (Multiple messages require a fair
+ *   amount of additional work and are hard to test. We're also assuming here
+ *   that multiple messages opened in windows are just the same function called
+ *   repeatedly.)
+ * - reusing an existing window to show another message
+ */
+var MODULE_NAME = 'test-opening-messages';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+// One folder's enough
+var folder = null;
+
+// Number of messages to open for multi-message tests
+const NUM_MESSAGES_TO_OPEN = 5;
+
+var setupModule = function (module) {
+  let fdh = collector.getModule('folder-display-helpers');
+  fdh.installInto(module);
+  let wh = collector.getModule('window-helpers');
+  wh.installInto(module);
+
+  folder = create_folder("OpeningMessagesA");
+  make_new_sets_in_folder(folder, [{count: 10}]);
+};
+
+/**
+ * Test opening a single message in a new tab.
+ */
+function test_open_single_message_in_tab() {
+  set_open_message_behavior("NEW_TAB");
+  let folderTab = mc.tabmail.currentTabInfo;
+  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  be_in_folder(folder);
+  // Select one message
+  let msgHdr = select_click_row(1);
+  // Open it
+  open_selected_message();
+  // Check that the tab count has increased by 1
+  assert_number_of_tabs_open(preCount + 1);
+  // Check that the currently displayed tab is a message tab (i.e. our newly
+  // opened tab is in the foreground)
+  assert_tab_mode_name(null, "message");
+  // Check that the message header displayed is the right one
+  assert_selected_and_displayed(msgHdr);
+  // Clean up, close the tab
+  close_tab(mc.tabmail.currentTabInfo);
+  switch_tab(folderTab);
+  reset_open_message_behavior();
+}
+
+/**
+ * Test opening multiple messages in new tabs.
+ */
+function test_open_multiple_messages_in_tabs() {
+  set_open_message_behavior("NEW_TAB");
+  let folderTab = mc.tabmail.currentTabInfo;
+  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  be_in_folder(folder);
+
+  // Select a bunch of messages
+  select_click_row(1);
+  let selectedMessages = select_shift_click_row(NUM_MESSAGES_TO_OPEN);
+  // Open them
+  open_selected_messages();
+  // Check that the tab count has increased by the correct number
+  assert_number_of_tabs_open(preCount + NUM_MESSAGES_TO_OPEN);
+  // Check that the currently displayed tab is a message tab (i.e. one of our
+  // newly opened tabs is in the foreground)
+  assert_tab_mode_name(null, "message");
+
+  // Now check whether each of the NUM_MESSAGES_TO_OPEN tabs has the correct
+  // title
+  for (let i = 0; i < NUM_MESSAGES_TO_OPEN; i++)
+    assert_tab_titled_from(mc.tabmail.tabInfo[preCount + i],
+                           selectedMessages[i]);
+
+  // Check whether each tab has the correct message, then close it to load the
+  // previous tab.
+  for (let i = 0; i < NUM_MESSAGES_TO_OPEN; i++) {
+    assert_selected_and_displayed(selectedMessages.pop());
+    close_tab(mc.tabmail.currentTabInfo);
+  }
+  switch_tab(folderTab);
+  reset_open_message_behavior();
+}
+
+/**
+ * Test opening a message in a new window.
+ */
+function test_open_message_in_new_window() {
+  set_open_message_behavior("NEW_WINDOW");
+  be_in_folder(folder);
+
+  // Select a message
+  let msgHdr = select_click_row(1);
+  
+  plan_for_new_window("mail:messageWindow");
+  // Open it
+  open_selected_message();
+  let msgc = wait_for_new_window("mail:messageWindow");
+  wait_for_message_display_completion(msgc, true);
+
+  assert_selected_and_displayed(msgc, msgHdr);
+  // Clean up, close the window
+  close_message_window(msgc);
+  reset_open_message_behavior();
+}
+
+/**
+ * Test reusing an existing window to open a new message.
+ */
+function test_open_message_in_existing_window() {
+  set_open_message_behavior("EXISTING_WINDOW");
+  be_in_folder(folder);
+
+  // Open up a window
+  select_click_row(1);
+  plan_for_new_window("mail:messageWindow");
+  open_selected_message();
+  let msgc = wait_for_new_window("mail:messageWindow");
+  wait_for_message_display_completion(msgc, true);
+
+  // Select another message and open it
+  let msgHdr = select_click_row(2);
+  open_selected_message();
+  // We don't need to pass true here, as open_selected_message should have
+  // started off the load before returning.
+  wait_for_message_display_completion(msgc);
+
+  // Check if our old window displays the message
+  assert_selected_and_displayed(msgc, msgHdr);
+  // Clean up, close the window
+  close_message_window(msgc);
+  reset_open_message_behavior();
+}
--- a/mail/test/mozmill/folder-display/test-right-click-middle-click.js
+++ b/mail/test/mozmill/folder-display/test-right-click-middle-click.js
@@ -144,104 +144,161 @@ function test_right_click_on_existing_mu
 
   close_popup();
   assert_selected_and_displayed([3, 6]);
 }
 
 /**
  * Middle clicking should open a message in a tab, but not affect our selection.
  */
-function test_middle_click_with_nothing_selected() {
+function _middle_click_with_nothing_selected_helper(aBackground) {
   be_in_folder(folder);
 
   select_none();
   assert_nothing_selected();
-
+  let folderTab = mc.tabmail.currentTabInfo;
   let [tabMessage, curMessage] = middle_click_on_row(1);
-  // as of immediately right now, the tab opens in the foreground, but soon
-  //  it will open in the background, so prepare the test for that...
-  switch_tab(tabMessage);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
   assert_selected_and_displayed(curMessage);
   close_tab(tabMessage);
 
   assert_nothing_selected();
 }
 
 /**
  * One-thing selected, middle-click on something else.
  */
-function test_middle_click_with_one_thing_selected() {
+function _middle_click_with_one_thing_selected_helper(aBackground) {
   be_in_folder(folder);
 
   select_click_row(0);
   assert_selected_and_displayed(0);
 
+  let folderTab = mc.tabmail.currentTabInfo;
   let [tabMessage, curMessage] = middle_click_on_row(1);
-  switch_tab(tabMessage);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
   assert_selected_and_displayed(curMessage);
   close_tab(tabMessage);
 
   assert_selected_and_displayed(0);
 }
 
 /**
  * Many things selected, middle-click on something that is not in that
  *  selection.
  */
-function test_middle_click_with_many_things_selected() {
+function _middle_click_with_many_things_selected_helper(aBackground) {
   be_in_folder(folder);
 
   select_click_row(0);
   select_shift_click_row(5);
   assert_selected_and_displayed([0, 5]);
 
+  let folderTab = mc.tabmail.currentTabInfo;
   let [tabMessage, curMessage] = middle_click_on_row(1);
-  switch_tab(tabMessage);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
   assert_selected_and_displayed(curMessage);
   close_tab(tabMessage);
 
   assert_selected_and_displayed([0, 5]);
 }
 
 /**
  * One thing selected, middle-click on that.
  */
-function test_middle_click_on_existing_single_selection() {
+function _middle_click_on_existing_single_selection_helper(aBackground) {
   be_in_folder(folder);
 
   select_click_row(3);
   assert_selected_and_displayed(3);
 
+  let folderTab = mc.tabmail.currentTabInfo;
   let [tabMessage, curMessage] = middle_click_on_row(3);
-  switch_tab(tabMessage);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
   assert_selected_and_displayed(curMessage);
   close_tab(tabMessage);
 
   assert_selected_and_displayed(3);
 }
 
 /**
  * Many things selected, right-click somewhere in the selection.
  */
-function test_middle_click_on_existing_multi_selection() {
+function _middle_click_on_existing_multi_selection_helper(aBackground) {
   be_in_folder(folder);
 
   select_click_row(3);
   select_shift_click_row(6);
   assert_selected_and_displayed([3, 6]);
 
+  let folderTab = mc.tabmail.currentTabInfo;
   let [tabMessage, curMessage] = middle_click_on_row(5);
-  switch_tab(tabMessage);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
   assert_selected_and_displayed(curMessage);
   close_tab(tabMessage);
 
   assert_selected_and_displayed([3, 6]);
 }
 
 /**
+ * Generate background and foreground tests for each middle click test.
+ *
+ * @param aTests an array of test names
+ */
+function _generate_background_foreground_tests(aTests) {
+  let self = this;
+  for each (let [, test] in Iterator(aTests)) {
+    let helperFunc = this["_" + test + "_helper"];
+    this["test_" + test + "_background"] = function() {
+      set_context_menu_background_tabs(true);
+      helperFunc.apply(self, [true]);
+      reset_context_menu_background_tabs();
+    };
+    this["test_" + test + "_foreground"] = function() {
+      set_context_menu_background_tabs(false);
+      helperFunc.apply(self, [false]);
+      reset_context_menu_background_tabs();
+    };
+  }
+}
+
+_generate_background_foreground_tests([
+  "middle_click_with_nothing_selected",
+  "middle_click_with_one_thing_selected",
+  "middle_click_with_many_things_selected",
+  "middle_click_on_existing_single_selection",
+  "middle_click_on_existing_multi_selection"
+]);
+
+/**
  * Right-click on something and delete it, having no selection previously.
  */
 function test_right_click_deletion_nothing_selected() {
   be_in_folder(folder);
 
   select_none();
   assert_selected_and_displayed();
 
--- a/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
@@ -54,27 +54,27 @@ Cu.import('resource://mozmill/stdlib/os.
 
 const MODULE_NAME = 'folder-display-helpers';
 
 const RELATIVE_ROOT = '../shared-modules';
 // we need window-helpers for augment_controller
 const MODULES_REQUIRES = ['window-helpers'];
 
 const nsMsgViewIndex_None = 0xffffffff;
-
+Cu.import('resource://app/modules/MailConsts.js');
 
 const DO_NOT_EXPORT = {
   // magic globals
   MODULE_NAME: true, DO_NOT_EXPORT: true,
   // imported modules
   elib: true, mozmill: true, controller: true, frame: true, os: true,
   // convenience constants
   Ci: true, Cc: true, Cu: true, Cr: true,
   // useful constants
-  nsMsgViewIndex_None: true,
+  nsMsgViewIndex_None: true, MailConsts: true,
   // internal setup functions
   setupModule: true, setupAccountStuff: true,
   // internal setup flags
   initialized: false,
   // other libraries we use
   windowHelper: true
 };
 
@@ -237,43 +237,64 @@ function be_in_folder(aFolder) {
  * Create a new tab displaying a folder, making that tab the current tab.
  *
  * @return The tab info of the current tab (a more persistent identifier for
  *     tabs than the index, which will change as tabs open/close).
  */
 function open_folder_in_new_tab(aFolder) {
   // save the current tab as the 'other' tab
   otherTab = mc.tabmail.currentTabInfo;
-  mc.tabmail.openTab("folder", aFolder);
+  mc.tabmail.openTab("folder", {folder: aFolder});
   wait_for_all_messages_to_load();
   return mc.tabmail.currentTabInfo;
 }
 
 /**
+ * Open the selected message(s) by pressing Enter. The mail.openMessageBehavior
+ * pref is supposed to determine how the messages are opened.
+ */
+function open_selected_messages() {
+  // Focus the thread tree
+  mc.threadTree.focus();
+  // Open whatever's selected
+  press_enter();
+}
+
+var open_selected_message = open_selected_messages;
+
+/**
  * Create a new tab displaying the currently selected message, making that tab
  *  the current tab.  We block until the message finishes loading.
  *
- * @return The tab info of the current tab (a more persistent identifier for
- *     tabs than the index, which will change as tabs open/close).
+ * @param aBackground [optional] If true, then the tab is opened in the
+ *                    background. If false or not given, then the tab is opened
+ *                    in the foreground.
+ *
+ * @return The tab info of the new tab (a more persistent identifier for tabs
+ *     than the index, which will change as tabs open/close).
  */
-function open_selected_message_in_new_tab() {
+function open_selected_message_in_new_tab(aBackground) {
   // get the current tab count so we can make sure the tab actually opened.
   let preCount = mc.tabmail.tabContainer.childNodes.length;
 
   // save the current tab as the 'other' tab
   otherTab = mc.tabmail.currentTabInfo;
 
-  mc.window.MsgOpenNewTabForMessage();
-  wait_for_message_display_completion(mc, true);
+  mc.tabmail.openTab("message", {msgHdr: mc.folderDisplay.selectedMessage,
+      viewWrapperToClone: mc.folderDisplay.view,
+      background: aBackground});
+  // We won't trigger a new message load if we're in the background
+  wait_for_message_display_completion(mc, !aBackground);
 
   // check that the tab count increased
   if (mc.tabmail.tabContainer.childNodes.length != preCount + 1)
     throw new Error("The tab never actually got opened!");
 
-  return mc.tabmail.currentTabInfo;
+  // We append new tabs at the end, so return the last tab
+  return mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1];
 }
 
 /**
  * Create a new window displaying the currently selected message.  We do not
  *  return until the message has finished loading.
  *
  * @return The MozmillController-wrapped new window.
  */
@@ -296,16 +317,56 @@ function switch_tab(aNewTab) {
   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();
 }
 
 /**
+ * Assert that the currently selected tab is the given one.
+ *
+ * @param aTab The tab that should currently be selected.
+ */
+function assert_selected_tab(aTab) {
+  if (mc.tabmail.currentTabInfo != aTab)
+    throw new Error("The currently selected tab should be at index " +
+        mc.tabmail.tabInfo.indexOf(aTab) + ", but is actually at index " +
+        mc.tabmail.tabInfo.indexOf(mc.tabmail.currentTabInfo));
+}
+
+/**
+ * Assert that the given tab has the given mode name. Valid mode names include
+ * "message" and "folder".
+ *
+ * @param aTab A Tab. The currently selected tab if null.
+ * @param aModeName A string that should match the mode name of the tab.
+ */
+function assert_tab_mode_name(aTab, aModeName) {
+  if (!aTab)
+    aTab = mc.tabmail.currentTabInfo;
+
+  if (aTab.mode.type != aModeName)
+    throw new Error("Tab should be of type " + aModeName +
+                    ", but is actually of type " + aTab.mode.type + ".");
+}
+
+/**
+ * Assert that the number of tabs open matches the value given.
+ *
+ * @param aNumber The number of tabs that should be open.
+ */
+function assert_number_of_tabs_open(aNumber) {
+  let actualNumber = mc.tabmail.tabContainer.childNodes.length;
+  if (actualNumber != aNumber)
+    throw new Error("There should be " + aNumber + " tabs open, but there " +
+                    "are actually " + actualNumber + " tabs open.");
+}
+
+/**
  * Assert that the given tab's title is based on the provided folder or
  *  message.
  *
  * @param aTab A Tab.
  * @param aWhat Either an nsIMsgFolder or an nsIMsgDBHdr
  */
 function assert_tab_titled_from(aTab, aWhat) {
   let text;
@@ -331,16 +392,27 @@ function close_tab(aTabToClose) {
   wait_for_message_display_completion();
 
   // check that the tab count decreased
   if (mc.tabmail.tabContainer.childNodes.length != preCount - 1)
     throw new Error("The tab never actually got closed!");
 }
 
 /**
+ * Close a standalone message window.
+ *
+ * @param aController The message window controller
+ */
+function close_message_window(aController) {
+  windowHelper.plan_for_window_close(aController);
+  aController.window.close();
+  windowHelper.wait_for_window_close(aController);
+}
+
+/**
  * Clear the selection.  I'm not sure how we're pretending we did that.
  */
 function select_none() {
   wait_for_message_display_completion();
   mc.dbView.selection.clearSelection();
   // give the event queue a chance to drain...
   controller.sleep(0);
 }
@@ -483,17 +555,19 @@ function right_click_on_row(aViewIndex) 
  * Middle-click on the tree-view in question, presumably opening a new message
  *  tab.
  *
  * @return [The new tab, the message that you clicked on.]
  */
 function middle_click_on_row(aViewIndex) {
   let msgHdr = mc.dbView.getMsgHdrAt(aViewIndex);
   _row_click_helper(aViewIndex, 1);
-  return [mc.tabmail.currentTabInfo, msgHdr];
+  // We append new tabs at the end, so return the last tab
+  return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1],
+          msgHdr];
 }
 
 /**
  * Assuming the context popup is popped-up (via right_click_on_row), select
  *  the deletion option.  If the popup is not popped up, you are out of luck.
  */
 function delete_via_popup() {
   plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted",
@@ -530,16 +604,33 @@ function press_delete(aController) {
   plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted",
                                  "DeleteOrMoveMsgFailed");
   aController.keypress(aController == mc ? mc.eThreadTree : null,
                        "VK_DELETE", {});
   wait_for_folder_events();
 }
 
 /**
+ * Pretend we are pressing the Enter key, triggering opening selected messages.
+ *
+ * @param aController The controller in whose context to do this, defaults to
+ *     |mc| if omitted.
+ */
+function press_enter(aController) {
+  if (aController === undefined)
+    aController = mc;
+  // if something is loading, make sure it finishes loading...
+  wait_for_message_display_completion(aController);
+  aController.keypress(aController == mc ? mc.eThreadTree : null,
+                       "VK_RETURN", {});
+  // this is always going to cause a message load, so wait for that
+  wait_for_message_display_completion(aController);
+}
+
+/**
  * Wait for the |folderDisplay| on aController (defaults to mc if omitted) to
  *  finish loading.  This generally only matters for folders that have an active
  *  search.
  * This method is generally called automatically most of the time, and you
  *  should not need to call it yourself unless you are operating outside the
  *  helper methods in this file.
  */
 function wait_for_all_messages_to_load(aController) {
@@ -1029,16 +1120,59 @@ function make_display_threaded() {
 function make_display_grouped() {
   wait_for_message_display_completion();
   mc.folderDisplay.view.showGroupedBySort = true;
   // drain event queue
   mc.sleep(0);
 }
 
 /**
+ * Set the mail.openMessageBehavior pref.
+ *
+ * @param aPref One of "NEW_WINDOW", "EXISTING_WINDOW" or "NEW_TAB"
+ */
+function set_open_message_behavior(aPref) {
+  let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefService).getBranch(null);
+  prefBranch.setIntPref("mail.openMessageBehavior",
+                        MailConsts.OpenMessageBehavior[aPref]);
+}
+
+/**
+ * Reset the mail.openMessageBehavior pref.
+ */
+function reset_open_message_behavior() {
+  let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefService).getBranch(null);
+  if (prefBranch.prefHasUserValue("mail.openMessageBehavior"))
+    prefBranch.clearUserPref("mail.openMessageBehavior");
+}
+
+/**
+ * Set the mail.contextMenuBackgroundTabs pref.
+ *
+ * @param aPref true/false.
+ */
+function set_context_menu_background_tabs(aPref) {
+  let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefService).getBranch(null);
+  prefBranch.setBoolPref("mail.contextMenuBackgroundTabs", aPref);
+}
+
+/**
+ * Reset the mail.contextMenuBackgroundTabs pref.
+ */
+function reset_context_menu_background_tabs() {
+  let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefService).getBranch(null);
+  if (prefBranch.prefHasUserValue("mail.contextMenuBackgroundTabs"))
+    prefBranch.clearUserPref("mail.contextMenuBackgroundTabs");
+}
+
+/**
  * assert that the multimessage/thread summary view contains
  * the specified number of elements of the specified class.
  *
  * @param aClassName: the class to use to select
  * @param aNumElts: the number of expected elements that have that class
  */
 
 function assert_summary_contains_N_divs(aClassName, aNumElts) {
--- a/mail/test/mozmill/shared-modules/test-window-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-window-helpers.js
@@ -774,9 +774,9 @@ function augment_controller(aController,
   if (aWindowType === undefined)
     aWindowType =
       aController.window.document.documentElement.getAttribute("windowtype");
 
   _augment_helper(aController, AugmentEverybodyWith);
   if (PerWindowTypeAugmentations[aWindowType])
     _augment_helper(aController, PerWindowTypeAugmentations[aWindowType]);
   return aController;
-}
\ No newline at end of file
+}
--- a/mailnews/base/build/nsMsgFactory.cpp
+++ b/mailnews/base/build/nsMsgFactory.cpp
@@ -238,43 +238,16 @@ UnregisterMailnewsContentPolicy(nsICompo
       do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
   if (NS_FAILED(rv)) return rv;
 
   return catman->DeleteCategoryEntry("content-policy",
                                      NS_MSGCONTENTPOLICY_CONTRACTID,
                                      PR_TRUE);
 }
 
-static NS_METHOD
-RegisterCommandLineHandler(nsIComponentManager* compMgr, nsIFile* path,
-                           const char *location, const char *type,
-                           const nsModuleComponentInfo *info)
-{
-  nsCOMPtr<nsICategoryManager> catMan (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
-  NS_ENSURE_TRUE(catMan, NS_ERROR_FAILURE);
-
-  return catMan->AddCategoryEntry("command-line-handler", "m-mail",
-                                  NS_MAILSTARTUPHANDLER_CONTRACTID,
-                                  PR_TRUE, PR_TRUE, nsnull);
-}
-
-static NS_METHOD
-UnregisterCommandLineHandler(nsIComponentManager* compMgr, nsIFile* path,
-                             const char *location,
-                             const nsModuleComponentInfo *info)
-{
-  nsCOMPtr<nsICategoryManager> catMan (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
-  NS_ENSURE_TRUE(catMan, NS_ERROR_FAILURE);
-
-  catMan->DeleteCategoryEntry("command-line-handler", "m-mail",
-                              PR_TRUE);
-
-  return NS_OK;
-}
-
 #ifdef XP_MACOSX
 static NS_METHOD RegisterOSXIntegration(nsIComponentManager *aCompMgr,
                                         nsIFile *aPath,
                                         const char *registryLocation,
                                         const char *componentType,
                                         const nsModuleComponentInfo *info)
 {
   nsresult rv;
@@ -313,18 +286,16 @@ static const nsModuleComponentInfo gComp
     },
     { "Netscape Messenger Window Service", NS_MESSENGERWINDOWSERVICE_CID,
       NS_MESSENGERWINDOWSERVICE_CONTRACTID,
       nsMessengerBootstrapConstructor,
     },
     { "Mail Startup Handler", NS_MESSENGERBOOTSTRAP_CID,
       NS_MAILSTARTUPHANDLER_CONTRACTID,
       nsMessengerBootstrapConstructor,
-      RegisterCommandLineHandler,
-      UnregisterCommandLineHandler,
     },
     { "Mail Session", NS_MSGMAILSESSION_CID,
       NS_MSGMAILSESSION_CONTRACTID,
       nsMsgMailSessionConstructor,
     },
     { "Messenger DOM interaction object", NS_MESSENGER_CID,
       NS_MESSENGER_CONTRACTID,
       nsMessengerConstructor,
--- a/mailnews/base/src/Makefile.in
+++ b/mailnews/base/src/Makefile.in
@@ -170,16 +170,20 @@ DEFINES         += -DMOZ_LDAP_XPCOM
 endif
 
 EXPORTS = \
 		nsMsgRDFDataSource.h \
 		nsMsgRDFUtils.h \
 		nsMailDirServiceDefs.h \
 		$(NULL)
 
+EXTRA_COMPONENTS = \
+		nsMailNewsCommandLineHandler.js \
+		$(NULL)
+
 EXTRA_JS_MODULES = \
 		dbViewWrapper.js \
 		mailViewManager.js \
 		quickSearchManager.js \
 		searchSpec.js \
 		virtualFolderWrapper.js \
 		$(NULL)
 
--- a/mailnews/base/src/dbViewWrapper.js
+++ b/mailnews/base/src/dbViewWrapper.js
@@ -1879,9 +1879,9 @@ DBViewWrapper.prototype = {
       return null;
     for (let [, folder] in Iterator(this._underlyingFolders)) {
       let msgHdr = folder.msgDatabase.getMsgHdrForMessageID(aMessageId);
       if (msgHdr)
         return msgHdr;
     }
     return null;
   },
-};
\ No newline at end of file
+};
new file mode 100644
--- /dev/null
+++ b/mailnews/base/src/nsMailNewsCommandLineHandler.js
@@ -0,0 +1,198 @@
+/* ***** 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 mozilla.org code.
+ *
+ * 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):
+ *   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
+ * 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 ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const MAPI_STARTUP_ARG = "MapiStartup";
+const MESSAGE_ID_PARAM = "?messageid=";
+
+var nsMailNewsCommandLineHandler =
+{
+  get _messenger nsMailNewsCommandLineHandler_getMessenger() {
+    delete this._messenger;
+    return this._messenger = Cc["@mozilla.org/messenger;1"]
+                               .createInstance(Ci.nsIMessenger);
+  },
+
+  /* nsICommandLineHandler */
+
+  /**
+   * Handles the following command line arguments:
+   * - -mail: opens the mail folder view
+   * - -MapiStartup: indicates that this startup is due to MAPI.
+   *   Don't do anything for now.
+   */
+  handle: function nsMailNewsCommandLineHandler_handle(aCommandLine) {
+    // Do this here because xpcshell isn't too happy with this at startup
+    Components.utils.import("resource://app/modules/MailUtils.js");
+    // -mail <URL>
+    let mailURL = null;
+    try {
+      mailURL = aCommandLine.handleFlagWithParam("mail", false);
+    }
+    catch (e) {
+      // We're going to cover -mail without a parameter later
+    }
+
+    if (mailURL && mailURL.length > 0) {
+      let msgHdr = null;
+      if (/^(mailbox|imap|news)-message:\/\//.test(mailURL)) {
+        // This might be a standard message URI, or one with a messageID
+        // parameter. Handle both cases.
+        let messageIDIndex = mailURL.toLowerCase().indexOf(MESSAGE_ID_PARAM);
+        if (messageIDIndex != -1) {
+          // messageID parameter
+          // Convert the message URI into a folder URI
+          let folderURI = mailURL.slice(0, messageIDIndex)
+                                 .replace("-message", "");
+          // Get the message ID
+          let messageID = mailURL.slice(messageIDIndex + MESSAGE_ID_PARAM.length);
+          // Make sure the folder tree is initialized
+          MailUtils.discoverFolders();
+
+          let folder = MailUtils.getFolderForURI(folderURI, true);
+          // The folder might not exist, so guard against that
+          if (folder && messageID.length > 0)
+            msgHdr = folder.msgDatabase.getMsgHdrForMessageID(messageID);
+        }
+        else {
+          // message URI
+          msgHdr = this._messenger.msgHdrFromURI(mailURL);
+        }
+      }
+      else {
+        // Necko URL, so convert it into a message header
+        let ioService = Cc["@mozilla.org/network/io-service;1"]
+                          .getService(Ci.nsIIOService);
+        let neckoURL = null;
+        try {
+          neckoURL = ioService.newURI(mailURL, null, null);
+        }
+        catch (e) {
+          // We failed to convert the URI. Oh well.
+        }
+
+        if (neckoURL instanceof Ci.nsIMsgMessageUrl)
+          msgHdr = neckoURL.messageHeader;
+      }
+
+      if (msgHdr) {
+        aCommandLine.preventDefault = true;
+        MailUtils.displayMessage(msgHdr);
+      }
+      else {
+        dump("Unrecognized URL: " + mailURL + "\n");
+      }
+    }
+
+    // -mail (no parameter)
+    let mailFlag = aCommandLine.handleFlag("mail", false);
+    if (mailFlag) {
+      // Focus the 3pane window if one is present, else open one
+      let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"]
+                             .getService(Ci.nsIWindowMediator);
+      let mail3PaneWindow = windowMediator.getMostRecentWindow("mail:3pane");
+      if (mail3PaneWindow) {
+        mail3PaneWindow.focus();
+      }
+      else {
+        let windowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+                              .getService(Ci.nsIWindowWatcher);
+        windowWatcher.openWindow(null, "chrome://messenger/content/", "_blank",
+            "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,dialog=no",
+            null);
+      }
+      aCommandLine.preventDefault = true;
+    }
+
+    // -MapiStartup
+    aCommandLine.handleFlag(MAPI_STARTUP_ARG, false);
+  },
+
+  helpInfo: "  -mail                Open the mail folder view.\n" +
+            "  -mail <URL>          Open the message specified by this URL.\n",
+
+  /* nsIClassInfo */
+  flags: Ci.nsIClassInfo.SINGLETON,
+  implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
+  getHelperForLanguage: function(language) null,
+  getInterfaces: function(count) {
+    let interfaces = [Ci.nsICommandLineHandler];
+    count.value = interfaces.length;
+    return interfaces;
+  },
+
+  /* nsIFactory */
+  createInstance: function(outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+
+    return this.QueryInterface(iid);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler,
+                                         Ci.nsIClassInfo,
+                                         Ci.nsIFactory])
+};
+
+function mailNewsCommandLineHandlerModule() {}
+mailNewsCommandLineHandlerModule.prototype =
+{
+  // XPCOM registration
+  classDescription: "MailNews Commandline Handler",
+  classID: Components.ID("{2f86d554-f9d9-4e76-8eb7-243f047333ee}"),
+  contractID: "@mozilla.org/messenger/clh;1",
+
+  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIModule]),
+  
+  _xpcom_categories: [{
+    category: "command-line-handler",
+    entry: "m-mail"
+  }],
+
+  _xpcom_factory: nsMailNewsCommandLineHandler
+};
+
+// NSGetModule: Return the nsIModule object.
+function NSGetModule(compMgr, fileSpec)
+{
+  return XPCOMUtils.generateModule([mailNewsCommandLineHandlerModule]);
+}
--- a/mailnews/base/src/nsMessengerBootstrap.cpp
+++ b/mailnews/base/src/nsMessengerBootstrap.cpp
@@ -40,329 +40,36 @@
  * 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 ***** */
 
 #include "nsMessengerBootstrap.h"
 #include "nsCOMPtr.h"
 
-#include "nsDOMCID.h"
-#include "nsMsgBaseCID.h"
+#include "nsISupportsArray.h"
 #include "nsIMsgFolder.h"
-#include "nsIMsgFolderCache.h"
-#include "nsIPrefService.h"
-#include "nsIPrefBranch.h"
-#include "nsIDOMWindow.h"
-#include "nsXPCOM.h"
-#include "nsISupportsPrimitives.h"
 #include "nsIWindowWatcher.h"
-#include "nsIPromptService.h"
-#include "nsIStringBundle.h"
-#include "nsString.h"
-#include "nsIURI.h"
-#include "nsIDialogParamBlock.h"
-#include "nsUnicharUtils.h"
-#include "nsIMsgDatabase.h"
-#include "nsICommandLine.h"
-#include "nsILocalFile.h"
-#include "nsNetUtil.h"
-#include "nsIFileURL.h"
-#include "nsNativeCharsetUtils.h"
-#include "nsIRDFResource.h"
-#include "nsIRDFService.h"
-#include "nsIMsgHdr.h"
 #include "nsMsgUtils.h"
-#include "nsEscape.h"
-#include "nsIMsgFolder.h"
-
-#ifdef XP_MACOSX
-#include "nsDirectoryServiceDefs.h"
-#endif
-
-#define MAPI_STARTUP_ARG "MapiStartup"
+#include "nsISupportsPrimitives.h"
+#include "nsIDOMWindow.h"
 
 NS_IMPL_THREADSAFE_ADDREF(nsMessengerBootstrap)
 NS_IMPL_THREADSAFE_RELEASE(nsMessengerBootstrap)
 
-NS_IMPL_QUERY_INTERFACE2(nsMessengerBootstrap,
-                         nsICommandLineHandler,
-                         nsIMessengerWindowService)
+NS_IMPL_QUERY_INTERFACE1(nsMessengerBootstrap, nsIMessengerWindowService)
 
 nsMessengerBootstrap::nsMessengerBootstrap()
 {
 }
 
 nsMessengerBootstrap::~nsMessengerBootstrap()
 {
 }
 
-NS_IMETHODIMP
-nsMessengerBootstrap::Handle(nsICommandLine* aCmdLine)
-{
-  NS_ENSURE_ARG_POINTER(aCmdLine);
-  nsresult rv;
-
-  nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID));
-  NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIDOMWindow> opened;
-
-#ifndef MOZ_SUITE
-  PRBool found;
-  rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("options"), PR_FALSE, &found);
-  if (NS_SUCCEEDED(rv) && found) {
-    wwatch->OpenWindow(nsnull, "chrome://messenger/content/preferences/preferences.xul", "_blank",
-                      "chrome,dialog=no,all", nsnull, getter_AddRefs(opened));
-    aCmdLine->SetPreventDefault(PR_TRUE);
-  }
-#endif
-
-  nsAutoString mailUrl; // -mail or -mail <some url> 
-  PRBool flag = PR_FALSE;
-  rv = aCmdLine->HandleFlagWithParam(NS_LITERAL_STRING("mail"), PR_FALSE, mailUrl);
-  if (NS_SUCCEEDED(rv))
-    flag = !mailUrl.IsVoid();
-  else 
-    aCmdLine->HandleFlag(NS_LITERAL_STRING("mail"), PR_FALSE, &flag);
-  if (flag)
-  {
-    nsCOMPtr<nsISupportsArray> argsArray = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // create scriptable versions of our strings that we can store in our nsISupportsArray....
-    if (!mailUrl.IsEmpty())
-    {
-      nsCOMPtr<nsISupportsString> scriptableURL (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
-      NS_ENSURE_TRUE(scriptableURL, NS_ERROR_FAILURE);
-      if (StringBeginsWith(mailUrl, NS_LITERAL_STRING("mailbox-message://")) ||
-          StringBeginsWith(mailUrl, NS_LITERAL_STRING("imap-message://")) ||
-          StringBeginsWith(mailUrl, NS_LITERAL_STRING("news-message://")))
-      {
-        nsCOMPtr <nsIMsgDBHdr> msgHdr;
-        nsCAutoString nativeArg;
-        NS_CopyUnicodeToNative(mailUrl, nativeArg);
-        PRInt32 queryIndex = nativeArg.Find("?messageId=", PR_TRUE);
-        if (queryIndex > 0)
-        {
-          nsCString messageId, folderUri;
-          nativeArg.Right(messageId, nativeArg.Length() - queryIndex - 11);
-          nativeArg.Left(folderUri, queryIndex);
-          folderUri.Cut(folderUri.Find("-message"), 8);
-          return OpenMessengerWindowForMessageId(folderUri, messageId);
-        }
-        else
-          GetMsgDBHdrFromURI(nativeArg.get(), getter_AddRefs(msgHdr));
-
-        if (msgHdr)
-        {
-          nsCOMPtr <nsIMsgFolder> folder;
-          nsCString folderUri;
-          nsMsgKey msgKey;
-          msgHdr->GetMessageKey(&msgKey);
-          msgHdr->GetFolder(getter_AddRefs(folder));
-          if (folder)
-          {
-            folder->GetURI(folderUri);
-            rv = DiscoverFoldersIfNeeded(folder);
-            NS_ENSURE_SUCCESS(rv, rv);
-            return OpenMessengerWindowWithUri("mail:messageWindow", folderUri.get(), msgKey);  
-          }
-        }
-      }
-      // check if it's a mail message url, and if so, convert it?
-      scriptableURL->SetData((mailUrl));
-      argsArray->AppendElement(scriptableURL);
-    }
-    wwatch->OpenWindow(nsnull, "chrome://messenger/content/", "_blank",
-                       "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,dialog=no", argsArray, getter_AddRefs(opened));
-    aCmdLine->SetPreventDefault(PR_TRUE);
-    return NS_OK;
-  }
-
-#ifdef XP_WIN
-  // Handle MAPI startup -- do nothing
-  PRBool isMapiStartup = PR_FALSE;
-  aCmdLine->HandleFlag(NS_LITERAL_STRING(MAPI_STARTUP_ARG), PR_FALSE, &isMapiStartup);
-#endif
-
-#ifndef MOZ_SUITE
-  PRInt32 numArgs;
-  aCmdLine->GetLength(&numArgs);
-  if (numArgs > 0)
-  {
-    nsAutoString mailPath;
-
-#ifdef XP_MACOSX
-    // Mac will get -file /path/to/file
-    rv = aCmdLine->HandleFlagWithParam(NS_LITERAL_STRING("file"), PR_FALSE, mailPath);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (StringEndsWith(mailPath, NS_LITERAL_STRING(".mozeml"), nsCaseInsensitiveStringComparator()))
-    {
-      rv = HandleIndexerResult(mailPath);
-      // If we've found a search result, don't pop up a 3pane window
-      if (NS_SUCCEEDED(rv))
-        aCmdLine->SetPreventDefault(PR_TRUE);
-    }
-#else
-    // All other OSes just get a straight path (i.e., no -file)
-    nsAutoString arg;
-    aCmdLine->GetArgument(0, arg);
-
-    mailPath = arg;
-#ifdef XP_WIN
-    if (StringEndsWith(mailPath, NS_LITERAL_STRING(".wdseml"), nsCaseInsensitiveStringComparator()))
-    {
-      rv = aCmdLine->RemoveArguments(0, 0);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      rv = HandleIndexerResult(mailPath);
-      // If we've found a search result, don't pop up a 3pane window
-      if (NS_SUCCEEDED(rv))
-        aCmdLine->SetPreventDefault(PR_TRUE);
-    }
-#endif
-#endif
-
-    if (StringEndsWith(mailPath, NS_LITERAL_STRING(".eml"), nsCaseInsensitiveStringComparator()))
-    {
-      rv = aCmdLine->RemoveArguments(0, 0);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsCOMPtr<nsIFile> file;
-      if (StringBeginsWith(mailPath, NS_LITERAL_STRING("file://"),
-                           nsCaseInsensitiveStringComparator()))
-      {
-        // Get the file from the file:// URL.
-        rv = NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(mailPath),
-                                   getter_AddRefs(file));
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-      else
-      {
-        // Resolve the file from the relative or absolute path.
-        aCmdLine->ResolveFile(mailPath, getter_AddRefs(file));
-      }
-
-      PRBool fileExists = PR_FALSE;
-      if (file) // Absolute paths always resolve a file, check if it exists too.
-        file->Exists(&fileExists);
-
-      // If the file does not exist - shown an alert.
-      if (!fileExists)
-      {
-        nsCOMPtr<nsIStringBundleService> bs = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
-        NS_ENSURE_SUCCESS(rv, rv);
-        nsCOMPtr<nsIStringBundle> bundle;
-        rv = bs->CreateBundle("chrome://messenger/locale/messenger.properties",
-                              getter_AddRefs(bundle));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        nsAutoString title;
-        rv = bundle->GetStringFromName(NS_LITERAL_STRING("fileNotFoundTitle").get(),
-                                       getter_Copies(title));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        nsAutoString msg;
-        const PRUnichar *formatStrings[1] = { mailPath.get() };
-        rv = bundle->FormatStringFromName(NS_LITERAL_STRING("fileNotFoundMsg").get(),
-                                          formatStrings, 1, getter_Copies(msg));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        nsCOMPtr<nsIPromptService> promptService(do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        promptService->Alert(nsnull, title.get(), msg.get());
-        return NS_OK;
-      }
-
-      nsCOMPtr<nsIURI> uri;
-      NS_NewFileURI(getter_AddRefs(uri), file);
-      nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri));
-      NS_ENSURE_TRUE(fileURL, NS_ERROR_FAILURE);
-
-      // create scriptable versions of our strings that we can store in our nsISupportsArray....
-      nsCOMPtr<nsISupportsString> scriptableURL (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
-      NS_ENSURE_TRUE(scriptableURL, NS_ERROR_FAILURE);
-
-      fileURL->SetQuery(NS_LITERAL_CSTRING("?type=application/x-message-display"));
-
-      wwatch->OpenWindow(nsnull, "chrome://messenger/content/messageWindow.xul", "_blank",
-                         "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,dialog=no", fileURL, getter_AddRefs(opened));
-      aCmdLine->SetPreventDefault(PR_TRUE);
-    }
-    return NS_OK;
-  }
-#endif
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsMessengerBootstrap::GetHelpInfo(nsACString& aResult)
-{
-  aResult.Assign(
-    "  -mail                Open the mail folder view.\n"
-#ifndef MOZ_SUITE
-    "  -options             Open the options dialog.\n"
-#endif
-#ifdef XP_MACOSX
-    "  -file                Open the specified email file.\n"
-#endif
-  );
-
-  return NS_OK;
-}
-nsresult nsMessengerBootstrap::DiscoverFoldersIfNeeded(nsIMsgFolder *folder)
-{
-  nsCOMPtr <nsIMsgFolder> parent;
-  folder->GetParent(getter_AddRefs(parent));
-  // check if we've done folder discovery. If not,
-  // do it so we'll have a real folder.
-  if (!parent)
-  {
-    nsCOMPtr <nsIMsgIncomingServer> server;
-    folder->GetServer(getter_AddRefs(server));
-    nsresult rv = server->GetRootFolder(getter_AddRefs(parent));
-    NS_ENSURE_SUCCESS(rv, rv);
-    nsCOMPtr<nsISimpleEnumerator> enumerator;
-    parent->GetSubFolders(getter_AddRefs(enumerator));
-  }
-  return NS_OK;
-}
-
-nsresult nsMessengerBootstrap::OpenMessengerWindowForMessageId(nsCString &folderUri, nsCString &messageId)
-{
-  nsresult rv;
-  nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv));
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsIRDFResource> res;
-  rv = rdf->GetResource(folderUri, getter_AddRefs(res));
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsIMsgFolder> containingFolder;
-  containingFolder = do_QueryInterface(res, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = DiscoverFoldersIfNeeded(containingFolder);
-  NS_ENSURE_SUCCESS(rv, rv);
-  // once we have the folder uri, open the db and search for the message id.
-  nsCOMPtr <nsIMsgDatabase> msgDB;
-  containingFolder->GetMsgDatabase(getter_AddRefs(msgDB));
-  nsCOMPtr<nsIMsgDBHdr> msgHdr;
-  if (msgDB)
-    msgDB->GetMsgHdrForMessageID(messageId.get(), getter_AddRefs(msgHdr));
-  if (msgHdr)
-  {
-    nsMsgKey msgKey;
-    msgHdr->GetMessageKey(&msgKey);
-    rv = OpenMessengerWindowWithUri("mail:messageWindow", folderUri.get(), msgKey);  
-    return rv;
-  }
-  return NS_ERROR_FAILURE;
-}
-
 NS_IMETHODIMP nsMessengerBootstrap::OpenMessengerWindowWithUri(const char *windowType, const char * aFolderURI, nsMsgKey aMessageKey)
 {
   PRBool standAloneMsgWindow = PR_FALSE;
   nsCAutoString chromeUrl("chrome://messenger/content/");
   if (windowType && !strcmp(windowType, "mail:messageWindow"))
   {
     chromeUrl.Append("messageWindow.xul");
     standAloneMsgWindow = PR_TRUE;
@@ -410,76 +117,8 @@ NS_IMETHODIMP nsMessengerBootstrap::Open
 
   // we need to use the "mailnews.reuse_thread_window2" pref
   // to determine if we should open a new window, or use an existing one.
   nsCOMPtr<nsIDOMWindow> newWindow;
   return wwatch->OpenWindow(0, chromeUrl.get(), "_blank",
                             "chrome,all,dialog=no", argsArray,
                              getter_AddRefs(newWindow));
 }
-
-nsresult
-nsMessengerBootstrap::HandleIndexerResult(const nsString &aPath)
-{
-  nsresult rv;
-  // parse file name - get path to containing folder, and message-id of message we're looking for
-  // Then, open that message (in a 3-pane window?)
-  PRInt32 mozmsgsIndex = aPath.Find(NS_LITERAL_STRING(".mozmsgs"));
-  nsString folderPathStr;
-  aPath.Left(folderPathStr, mozmsgsIndex);
-  nsCOMPtr<nsILocalFile> folderPath;
-
-#ifdef XP_MACOSX
-  // On Mac, the .mozeml files are stored outside the profile in
-  // ~/Library/Caches/Metadata.  We have to replace this root with
-  // the profile dir, so we can find the actual mail there.  NOTE:
-  // this works for profiles not in ~/Library/Thunderbird/Profiles.
-  nsCOMPtr<nsIFile> homeDir;
-  rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(homeDir));
-  NS_ENSURE_SUCCESS(rv, rv);
-  // TB Spotlight data goes in ~/Library/Caches/Metadata/Thunderbird
-  homeDir->Append(NS_LITERAL_STRING("Library"));
-  homeDir->Append(NS_LITERAL_STRING("Caches"));
-  homeDir->Append(NS_LITERAL_STRING("Metadata"));
-  homeDir->Append(NS_LITERAL_STRING("Thunderbird"));
-  nsAutoString metadataDirStr;
-  rv = homeDir->GetPath(metadataDirStr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIFile> profileDir;
-  rv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(profileDir));
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsAutoString profileDirStr;
-  rv = profileDir->GetPath(profileDirStr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Swap the Spotlight metadata root and the profile dir root
-  folderPathStr.Replace(0, metadataDirStr.Length(), profileDirStr); 
-
-  folderPath = do_CreateInstance("@mozilla.org/file/local;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = folderPath->InitWithPath(folderPathStr);
-  NS_ENSURE_SUCCESS(rv, rv);
-#endif
-#ifdef XP_WIN
-  // Get the nsILocalFile for this path
-  folderPath = do_CreateInstance("@mozilla.org/file/local;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = folderPath->InitWithPath(folderPathStr);
-  NS_ENSURE_SUCCESS(rv, rv);
-#endif
-
-  nsCString folderUri;
-  rv = FolderUriFromDirInProfile(folderPath, folderUri);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoString unicodeMessageId;
-  // strip off .mozeml/.wdseml at the end as well
-  aPath.Mid(unicodeMessageId, mozmsgsIndex + 9, aPath.Length() - (mozmsgsIndex + 9 + 7));
-  nsCAutoString escapedMessageId;
-  NS_CopyUnicodeToNative(unicodeMessageId, escapedMessageId);
-
-  // unescape messageId
-  nsCAutoString messageId;
-  messageId = NS_UnescapeURL(escapedMessageId, esc_Minimal, messageId);
-
-  return OpenMessengerWindowForMessageId(folderUri, messageId);
-}
--- a/mailnews/base/src/nsMessengerBootstrap.h
+++ b/mailnews/base/src/nsMessengerBootstrap.h
@@ -35,38 +35,27 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 
 #ifndef __nsMessenger_h
 #define __nsMessenger_h
 
 #include "nscore.h"
-#include "nsIServiceManager.h"
 #include "nsIMessengerWindowService.h"
-#include "nsICommandLineHandler.h"
-#include "nsString.h"
-
-class nsIMsgFolder;
 
 #define NS_MESSENGERBOOTSTRAP_CID                 \
 { /* 4a85a5d0-cddd-11d2-b7f6-00805f05ffa5 */      \
   0x4a85a5d0, 0xcddd, 0x11d2,                     \
   {0xb7, 0xf6, 0x00, 0x80, 0x5f, 0x05, 0xff, 0xa5}}
 
 class nsMessengerBootstrap :
-    public nsICommandLineHandler,
     public nsIMessengerWindowService
 {
-  
 public:
   nsMessengerBootstrap();
   virtual ~nsMessengerBootstrap();
-  nsresult DiscoverFoldersIfNeeded(nsIMsgFolder *folder);
-  nsresult OpenMessengerWindowForMessageId(nsCString &folderUri, nsCString &messageId);
-  nsresult HandleIndexerResult(const nsString &aPath);
-  
+
   NS_DECL_ISUPPORTS  
   NS_DECL_NSIMESSENGERWINDOWSERVICE
-  NS_DECL_NSICOMMANDLINEHANDLER
 };
 
 #endif
--- a/mailnews/base/test/unit/test_jsTreeSelection.js
+++ b/mailnews/base/test/unit/test_jsTreeSelection.js
@@ -136,16 +136,22 @@ function run_test() {
   aci(2);
 
   // -- clearSelection
   sel.clearSelection();
   asr();
   aci(2); // should still be the same...
 
   // -- toggleSelect
+  // start from nothing
+  sel.clearSelection();
+  sel.toggleSelect(1);
+  asr([1, 1]);
+  aci(1);
+
   // lower fusion
   sel.select(2);
   sel.toggleSelect(1);
   asr([1, 2]);
   aci(1);
 
   // upper fusion
   sel.toggleSelect(3);
@@ -436,9 +442,38 @@ function run_test() {
   aci(-1);
 
   // broad nuke due to removal
   sel.rangedSelect(5, 10, false);
   sel.adjustSelection(0, -20);
   asr();
   asp(-1);
   aci(-1);
-}
\ No newline at end of file
+
+  // duplicateSelection (please keep this right at the end, as this modifies
+  // sel)
+  // no guarantees for the shift pivot yet, so don't test that
+  let oldSel = sel;
+  let newSel = new JSTreeSelection(fakeBox);
+  // multiple selections
+  oldSel.rangedSelect(1, 3, false);
+  oldSel.rangedSelect(5, 5, true);
+  oldSel.rangedSelect(10, 10, true);
+  oldSel.rangedSelect(6, 7, true);
+
+  oldSel.duplicateSelection(newSel);
+  // from now on we're only going to be checking newSel
+  sel = newSel;
+  asr([1, 3], [5, 7], [10, 10]);
+  aci(7);
+
+  // single selection
+  oldSel.select(4);
+  oldSel.duplicateSelection(newSel);
+  asr([4, 4]);
+  aci(4);
+
+  // nothing selected
+  oldSel.clearSelection();
+  oldSel.duplicateSelection(newSel);
+  asr();
+  aci(4);
+}
--- a/mailnews/base/util/jsTreeSelection.js
+++ b/mailnews/base/util/jsTreeSelection.js
@@ -183,17 +183,22 @@ JSTreeSelection.prototype = {
   },
 
   timedSelect: function JSTreeSelection_timedSelect(aIndex, aDelay) {
     throw new Error("We do not implement timed selection.");
   },
 
   toggleSelect: function JSTreeSelection_toggleSelect(aIndex) {
     this.currentIndex = aIndex;
-    for each (let [iTupe, [low, high]] in Iterator(this._ranges)) {
+    // If nothing's selected, select aIndex
+    if (this._count == 0) {
+      this._count = 1;
+      this._ranges = [[aIndex, aIndex]];
+    }
+    else for each (let [iTupe, [low, high]] in Iterator(this._ranges)) {
       // below the range? add it to the existing range or create a new one
       if (aIndex < low) {
         this._count++;
         // is it just below an existing range? (range fusion only happens in the
         //  high case, not here.)
         if (aIndex == low - 1) {
           this._ranges[iTupe][0] = aIndex;
           break;
@@ -612,21 +617,24 @@ JSTreeSelection.prototype = {
    */
   _fireSelectionChanged: function JSTreeSelection__fireSelectionChanged() {
     // don't fire if we are suppressed; we will fire when un-suppressed
     if (this.selectEventsSuppressed)
       return;
     let view;
     if (this._treeBoxObject && this._treeBoxObject.view)
       view = this._treeBoxObject.view;
-    else 
+    else
       view = this._view;
 
-    view = view.QueryInterface(Ci.nsITreeView);
-    view.selectionChanged();
+    // We might not have a view if we're in the middle of setting up things
+    if (view) {
+      view = view.QueryInterface(Ci.nsITreeView);
+      view.selectionChanged();
+    }
   },
 
   get currentIndex JSTreeSelection_get_currentIndex() {
     if (this._currentIndex == null)
       return -1;
     return this._currentIndex;
   },
   /**
@@ -646,9 +654,34 @@ JSTreeSelection.prototype = {
   currentColumn: null,
 
   get shiftSelectPivot JSTreeSelection_get_shiftSelectPivot() {
     return this._shiftSelectPivot != null ? this._shiftSelectPivot : -1;
   },
 
   QueryInterface: XPCOMUtils.generateQI(
     [Ci.nsITreeSelection]),
+
+  /*
+   * Functions after this aren't part of the nsITreeSelection interface.
+   */
+
+  /**
+   * Duplicate this selection on another nsITreeSelection. This is useful
+   * when you would like to discard this selection for a real tree selection.
+   * We assume that both selections are for the same tree.
+   *
+   * @note We don't transfer the correct shiftSelectPivot over.
+   * @note This will fire a selectionChanged event on the tree view.
+   *
+   * @param aSelection an nsITreeSelection to duplicate this selection onto
+   */
+  duplicateSelection: function JSTreeSelection_duplicateSelection(aSelection) {
+    aSelection.selectEventsSuppressed = true;
+    aSelection.clearSelection();
+    for each (let [iTupe, [low, high]] in Iterator(this._ranges))
+      aSelection.rangedSelect(low, high, iTupe > 0);
+
+    aSelection.currentIndex = this.currentIndex;
+    // This will fire a selectionChanged event
+    aSelection.selectEventsSuppressed = false;
+  },
 };
--- a/mailnews/build/nsMailModule.cpp
+++ b/mailnews/build/nsMailModule.cpp
@@ -622,19 +622,16 @@ static NS_METHOD
 RegisterCommandLineHandlers(nsIComponentManager* compMgr, nsIFile* path,
                             const char *location, const char *type,
                             const nsModuleComponentInfo *info)
 {
   nsresult rv;
   nsCOMPtr<nsICategoryManager> catMan (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
   NS_ENSURE_TRUE(catMan, NS_ERROR_FAILURE);
 
-  rv  = catMan->AddCategoryEntry("command-line-handler", "m-mail",
-                                 NS_MESSENGERBOOTSTRAP_CONTRACTID,
-                                 PR_TRUE, PR_TRUE, nsnull);
   rv |= catMan->AddCategoryEntry("command-line-handler", "m-addressbook",
                                  NS_ABMANAGER_CONTRACTID,
                                  PR_TRUE, PR_TRUE, nsnull);
   rv |= catMan->AddCategoryEntry("command-line-handler", "m-compose",
                                  NS_MSGCOMPOSESERVICE_CONTRACTID,
                                  PR_TRUE, PR_TRUE, nsnull);
   rv |= catMan->AddCategoryEntry("command-line-handler", "m-news",
                                  NS_NNTPSERVICE_CONTRACTID,
@@ -648,18 +645,16 @@ RegisterCommandLineHandlers(nsIComponent
 static NS_METHOD
 UnregisterCommandLineHandlers(nsIComponentManager* compMgr, nsIFile* path,
                               const char *location,
                               const nsModuleComponentInfo *info)
 {
   nsCOMPtr<nsICategoryManager> catMan (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
   NS_ENSURE_TRUE(catMan, NS_ERROR_FAILURE);
 
-  catMan->DeleteCategoryEntry("command-line-handler", "m-mail",
-                              PR_TRUE);
   catMan->DeleteCategoryEntry("command-line-handler", "m-addressbook",
                               PR_TRUE);
   catMan->DeleteCategoryEntry("command-line-handler", "m-compose",
                               PR_TRUE);
   catMan->DeleteCategoryEntry("command-line-handler", "m-news",
                               PR_TRUE);
 
   return NS_OK;
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -387,17 +387,20 @@ pref("ldap_2.version", 3); /* Update kCu
 pref("mailnews.confirm.moveFoldersToTrash", true);
 
 // space-delimited list of extra headers to add to .msf file
 pref("mailnews.customDBHeaders", "");
 
 // close standalone message window when deleting the displayed message
 pref("mail.close_message_window.on_delete", false);
 
+#ifdef MOZ_SUITE
 pref("mailnews.reuse_message_window", true);
+#endif
+
 pref("mailnews.reuse_thread_window2", false);
 pref("mailnews.open_window_warning", 10); // warn user if they attempt to open more than this many messages at once
 
 pref("mailnews.start_page.enabled", true);
 
 pref("mailnews.remember_selected_message", true);
 pref("mailnews.scroll_to_new_message", true);
 
--- a/suite/installer/unix/packages
+++ b/suite/installer/unix/packages
@@ -490,16 +490,17 @@ bin/components/msgnews.xpt
 bin/components/msgsearch.xpt
 bin/components/msgsmime.xpt
 ; JS components
 bin/components/mdn-service.js
 bin/components/newsblog.js
 bin/components/nsAbAutoCompleteMyDomain.js
 bin/components/nsAbAutoCompleteSearch.js
 bin/components/nsAbLDAPAttributeMap.js
+bin/components/nsMailNewsCommandLineHandler.js
 bin/components/nsMsgTraitService.js
 bin/components/nsSMTPProtocolHandler.js
 bin/components/offlineStartup.js
 bin/components/smime-service.js
 
 bin/chrome/messenger.jar
 bin/chrome/messenger.manifest
 bin/chrome/newsblog.jar
--- a/suite/installer/windows/packages
+++ b/suite/installer/windows/packages
@@ -468,16 +468,17 @@ bin\components\msgnews.xpt
 bin\components\msgsearch.xpt
 bin\components\msgsmime.xpt
 ; JS components
 bin\components\mdn-service.js
 bin\components\newsblog.js
 bin\components\nsAbAutoCompleteMyDomain.js
 bin\components\nsAbAutoCompleteSearch.js
 bin\components\nsAbLDAPAttributeMap.js
+bin\components\nsMailNewsCommandLineHandler.js
 bin\components\nsMsgTraitService.js
 bin\components\nsSMTPProtocolHandler.js
 bin\components\offlineStartup.js
 bin\components\smime-service.js
 
 ; chrome
 bin\chrome\messenger.jar
 bin\chrome\messenger.manifest
--- a/suite/mailnews/Makefile.in
+++ b/suite/mailnews/Makefile.in
@@ -36,9 +36,13 @@
 #
 # ***** END LICENSE BLOCK *****
 
 DEPTH=../..
 topsrcdir=@top_srcdir@
 srcdir=@srcdir@
 VPATH=@srcdir@
 
+include $(DEPTH)/config/autoconf.mk
+
+DIRS = modules
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/suite/mailnews/modules/MailUtils.js
@@ -0,0 +1,126 @@
+/* ***** 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 mozilla.org code.
+ *
+ * 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):
+ *   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
+ * 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 ***** */
+
+var EXPORTED_SYMBOLS = ["MailUtils"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
+
+/**
+ * This module has several utility functions for use by both core and
+ * third-party code. Some functions are aimed at code that doesn't have a
+ * window context, while others can be used anywhere.
+ */
+var MailUtils =
+{
+  /**
+   * Discover all folders. This is useful during startup, when you have code
+   * that deals with folders and that executes before the main 3pane window is
+   * open (the folder tree wouldn't have been initialized yet).
+   */
+  discoverFolders: function MailUtils_discoverFolders()
+  {
+    let accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+                           .getService(Ci.nsIMsgAccountManager);
+    let servers = accountManager.allServers;
+    for each (let server in fixIterator(servers, Ci.nsIMsgIncomingServer))
+      server.rootFolder.subFolders;
+  },
+
+  /**
+   * Get the nsIMsgFolder corresponding to this URI. This uses the RDF service
+   * to do the work.
+   *
+   * @param aFolderURI the URI to convert into a folder
+   * @param aCheckFolderAttributes whether to check that the folder either has
+   *                              a parent or isn't a server
+   * @returns the nsIMsgFolder corresponding to this URI, or null if
+   *          aCheckFolderAttributes is true and the folder doesn't have a
+   *          parent or is a server
+   */
+  getFolderForURI: function MailUtils_getFolderForURI(aFolderURI,
+                       aCheckFolderAttributes)
+  {
+    let folder = null;
+    let rdfService = Cc['@mozilla.org/rdf/rdf-service;1']
+                       .getService(Ci.nsIRDFService);
+    folder = rdfService.GetResource(aFolderURI);
+    // This is going to QI the folder to an nsIMsgFolder as well
+    if (folder && folder instanceof Ci.nsIMsgFolder)
+    {
+      if (aCheckFolderAttributes && !(folder.parent || folder.isServer))
+        return null;
+    }
+    else
+    {
+      return null;
+    }
+
+    return folder;
+  },
+
+  /**
+   * Displays this message in a new window.
+   *
+   * @param aMsgHdr the message header to display
+   */
+  displayMessage: function MailUtils_displayMessage(aMsgHdr)
+  {
+    this.openMessageInNewWindow(aMsgHdr);
+  },
+
+  /**
+   * Open a new standalone message window with this header.
+   *
+   * @param aMsgHdr the message header to display
+   */
+  openMessageInNewWindow: function MailUtils_openMessageInNewWindow(aMsgHdr)
+  {
+    // Pass in the message URI as messageWindow.js doesn't handle message headers
+    let messageURI = Cc["@mozilla.org/supports-string;1"]
+                       .createInstance(Ci.nsISupportsString);
+    messageURI.data = aMsgHdr.folder.getUriForMsg(aMsgHdr);
+
+    let windowWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+                          .getService(Ci.nsIWindowWatcher);
+    windowWatcher.openWindow(null,
+        "chrome://messenger/content/messageWindow.xul", "_blank",
+        "all,chrome,dialog=no,status,toolbar", messageURI);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/suite/mailnews/modules/Makefile.in
@@ -0,0 +1,49 @@
+# ***** 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 mozilla.org code.
+#
+# 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):
+#   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
+# 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 *****
+
+DEPTH     = ../../..
+VPATH     = @srcdir@
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_JS_MODULES = \
+  MailUtils.js \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
--- a/suite/mailnews/widgetglue.js
+++ b/suite/mailnews/widgetglue.js
@@ -42,16 +42,18 @@
  * widget-specific wrapper glue. There should be one function for every
  * widget/menu item, which gets some context (like the current selection)
  * and then calls a function/command in commandglue
  */
  
 //The eventual goal is for this file to go away and its contents to be brought into
 //mailWindowOverlay.js.  This is currently being done.
 
+Components.utils.import("resource://app/modules/MailUtils.js");
+
 //NOTE: gMessengerBundle must be defined and set or this Overlay won't work
 
 function ConvertDOMListToResourceArray(nodeList)
 {
     var result = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
 
     for (var i=0; i<nodeList.length; i++) {
         result.AppendElement(nodeList[i].resource);
@@ -316,34 +318,19 @@ function MsgSetFolderCharset()
 // attributes (like if there exists a parent or is it a server) is also passed
 // to this routine. Qualifying against those checks would return an existing 
 // folder. Callers who don't want to check those attributes will specify the
 // same and then this routine will simply return a msgfolder. This scenario
 // applies to a new imap account creation where special folders are created
 // on demand and hence needs to prior check of existence.
 function GetMsgFolderFromUri(uri, checkFolderAttributes)
 {
-    //dump("GetMsgFolderFromUri of " + uri + "\n");
-    var msgfolder = null;
-    try {
-        var resource = GetResourceFromUri(uri);
-        msgfolder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
-        if (checkFolderAttributes) {
-            if (!(msgfolder && (msgfolder.parent || msgfolder.isServer))) {
-                msgfolder = null;
-            }
-        }
-    }
-    catch (ex) {
-        //dump("failed to get the folder resource\n");
-    }
-    return msgfolder;
+  return MailUtils.getFolderForURI(uri, checkFolderAttributes);
 }
 
 function GetResourceFromUri(uri)
 {
     var RDF = Components.classes['@mozilla.org/rdf/rdf-service;1'].getService();
     RDF = RDF.QueryInterface(Components.interfaces.nsIRDFService);
     var resource = RDF.GetResource(uri);
 
     return resource;
 }  
-