Bug 460960 - (smtabmail) Port Thunderbird tabbed interface to MailNews
authorKarsten Düsterloh <mnyromyr@tprac.de>
Mon, 31 Aug 2009 22:46:26 +0100
changeset 3458 7b0ece4c9b5315e990227da5556ca9d54f82a21f
parent 3457 e7c2a5448c9e103dd57a00fb904358248883c80c
child 3459 959519662e65b039c4e048d2e23524ec33005a8c
push idunknown
push userunknown
push dateunknown
bugs460960
Bug 460960 - (smtabmail) Port Thunderbird tabbed interface to MailNews r=IanN sr=Neil
mailnews/mailnews.js
suite/browser/browser-prefs.js
suite/mailnews/commandglue.js
suite/mailnews/jar.mn
suite/mailnews/mail3PaneWindowCommands.js
suite/mailnews/mailContextMenus.js
suite/mailnews/mailWindow.js
suite/mailnews/mailWindowOverlay.js
suite/mailnews/mailWindowOverlay.xul
suite/mailnews/messageWindow.xul
suite/mailnews/messenger.css
suite/mailnews/messenger.xul
suite/mailnews/msgHdrViewOverlay.js
suite/mailnews/msgMail3PaneWindow.js
suite/mailnews/msgViewNavigation.js
suite/mailnews/msgViewPickerOverlay.js
suite/mailnews/search/SearchDialog.js
suite/mailnews/search/SearchDialog.xul
suite/mailnews/searchBar.js
suite/mailnews/tabmail.js
suite/mailnews/tabmail.xml
suite/mailnews/threadPane.js
suite/mailnews/widgetglue.js
suite/themes/classic/mac/messenger/mailWindow1.css
suite/themes/classic/mac/navigator/tabbrowser.css
suite/themes/classic/messenger/folderPane.css
suite/themes/classic/messenger/mailWindow1.css
suite/themes/classic/messenger/threadPane.css
suite/themes/modern/messenger/folderPane.css
suite/themes/modern/messenger/mailWindow1.css
suite/themes/modern/messenger/threadPane.css
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -1,9 +1,9 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** 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/
  *
@@ -387,17 +387,16 @@ 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);
 
 // if true, any click on a column header other than the thread column will unthread the view
--- a/suite/browser/browser-prefs.js
+++ b/suite/browser/browser-prefs.js
@@ -1,9 +1,9 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** 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/
  *
@@ -203,29 +203,42 @@ pref("places.frecency.defaultVisitBonus"
 
 // bonus (in percent) for place types for frecency calculations
 pref("places.frecency.unvisitedBookmarkBonus", 140);
 pref("places.frecency.unvisitedTypedBonus", 200);
 
 // Tabbed browser
 pref("browser.tabs.loadDivertedInBackground", false);
 pref("browser.tabs.loadInBackground", false);
+pref("browser.tabs.opentabfor.doubleclick", false);
 pref("browser.tabs.opentabfor.middleclick", false);
 pref("browser.tabs.opentabfor.urlbar", false);
 pref("browser.tabs.tooltippreview.enable", true);
 pref("browser.tabs.tooltippreview.width", 300);
 pref("browser.tabs.autoHide", true);
 pref("browser.tabs.forceHide", false);
 pref("browser.tabs.warnOnClose", true);
 pref("browser.tabs.warnOnCloseOther", true);
 pref("browser.tabs.warnOnOpen", true);
 pref("browser.tabs.maxOpenBeforeWarn", 15);
 // 0 = append, 1 = replace
 pref("browser.tabs.loadGroup", 1);
 
+// tab width and clipping
+pref("browser.tabs.tabMinWidth", 100);
+pref("browser.tabs.tabMaxWidth", 250);
+pref("browser.tabs.tabClipWidth", 140);
+
+// Where to show tab close buttons:
+// 0  on active tab only
+// 1  on all tabs until tabClipWidth is reached, then active tab only
+// 2  no close buttons at all
+// 3  at the end of the tabstrip
+pref("browser.tabs.closeButtons", 3);
+
 // lets new tab/window load something different than first window
 // -1 - use navigator startup preference
 //  0 - loads blank page
 //  1 - loads home page
 //  2 - loads last page visited
 pref("browser.tabs.loadOnNewTab", 0);
 pref("browser.windows.loadOnNewWindow", 1);
 
--- a/suite/mailnews/commandglue.js
+++ b/suite/mailnews/commandglue.js
@@ -1,10 +1,10 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -143,16 +143,21 @@ function setTitleFromFolder(msgfolder, s
         if (end) title += " " + end;
       }
     }
 
     if (!/Mac/.test(navigator.platform))
       title += " - " + gBrandBundle.getString("brandShortName");
 
     document.title = title;
+
+  // Notify the current tab, it might want to update also.
+  var tabmail = GetTabMail();
+  tabmail.saveCurrentTabState(); // gDBView may have changed!
+  tabmail.setTabTitle();
 }
 
 function UpdateMailToolbar(caller)
 {
   //dump("XXX update mail-toolbar " + caller + "\n");
   document.commandDispatcher.updateCommands('mail-toolbar');
 
   // hook for extra toolbar items
@@ -354,25 +359,26 @@ function RerootFolder(uri, newFolder, vi
         oldFolder.msgDatabase = null;
   }
   // that should have initialized gDBView, now re-root the thread pane
   RerootThreadPane();
 
   UpdateLocationBar(gMsgFolderSelected);
 
   UpdateStatusMessageCounts(gMsgFolderSelected);
-  
+
   UpdateMailToolbar("reroot folder in 3 pane");
   // hook for extra toolbar items
   var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
   observerService.notifyObservers(window, "mail:updateToolbarItems", null);
   // this is to kick off cross-folder searches for virtual folders.
   if (gSearchSession && !gVirtualFolderTerms) // another var might be better...
   {
     viewDebug("doing a xf folder search in rerootFolder\n");
+    gCurrentLoadingFolderURI = "";
     ViewChangeByFolder(newFolder);
     gPreQuickSearchView = null; // don't remember the cross folder search
     ScrollToMessageAfterFolderLoad(newFolder);
   }
 }
 
 function SwitchView(command)
 {
@@ -719,50 +725,16 @@ function CreateDBView(msgFolder, viewTyp
   // suppress message display if appropriate.
   gDBView.suppressMsgDisplay = IsMessagePaneCollapsed();
 
   UpdateSortIndicators(gCurSortType, sortOrder);
   var ObserverService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
   ObserverService.notifyObservers(msgFolder, "MsgCreateDBView", viewType + ":" + viewFlags);
 }
 
-function ChangeMessagePaneVisibility(now_hidden)
-{
-  // We also have to disable the Message/Attachments menuitem.
-  // It will be enabled when loading a message with attachments
-  // (see messageHeaderSink.handleAttachment).
-  var node = document.getElementById("msgAttachmentMenu");
-  if (node && now_hidden)
-    node.setAttribute("disabled", "true");
-
-  if (gDBView) {
-    // clear the subject, collapsing won't automatically do this
-    setTitleFromFolder(GetThreadPaneFolder(), null);
-    // the collapsed state is the state after we released the mouse 
-    // so we take it as it is
-    gDBView.suppressMsgDisplay = now_hidden;
-    // set the subject, uncollapsing won't automatically do this
-    gDBView.loadMessageByUrl("about:blank");
-    gDBView.selectionChanged();
-  }
-  var event = document.createEvent('Events');
-  if (now_hidden) {
-    event.initEvent('messagepane-hide', false, true);
-  }
-  else {
-    event.initEvent('messagepane-unhide', false, true);
-  }
-  document.getElementById("messengerWindow").dispatchEvent(event);
-}
-
-function OnClickThreadAndMessagePaneSplitter()
-{
-  ChangeMessagePaneVisibility(IsMessagePaneCollapsed());
-}
-
 function FolderPaneSelectionChange()
 {
     var folderTree = GetFolderTree();
     var folderSelection = folderTree.view.selection;
 
     // This prevents a folder from being loaded in the case that the user
     // has right-clicked on a folder different from the one that was
     // originally highlighted.  On a right-click, the highlight (selection)
@@ -867,17 +839,18 @@ function FolderPaneSelectionChange()
                   dbFolderInfo = null;
                 }
               }
               catch (ex)
               {
                 dump("failed to get view & sort values.  ex = " + ex +"\n");
               }
             }
-            if (gDBView && gDBView.viewType == nsMsgViewType.eShowQuickSearchResults)
+            // clear cached view if we have no db or a pending quick search
+            if (!gDBView || gDBView.viewType == nsMsgViewType.eShowQuickSearchResults)
             {
               if (gPreQuickSearchView) //close cached view before quick search
               {
                 gPreQuickSearchView.close();
                 gPreQuickSearchView = null;  
               }
               var searchInput = document.getElementById("searchInput");  //reset the search input on folder switch
               if (searchInput) 
@@ -888,18 +861,22 @@ function FolderPaneSelectionChange()
             if (gXFVirtualFolderTerms)
               viewType = nsMsgViewType.eShowVirtualFolderResults;
             else if (gSearchEmailAddress || gVirtualFolderTerms)
               viewType = nsMsgViewType.eShowQuickSearchResults;
             else if (viewType == nsMsgViewType.eShowQuickSearchResults)
               viewType = nsMsgViewType.eShowAllThreads;  //override viewType - we don't want to start w/ quick search
             ChangeFolder(realFolder, msgFolder, viewType, viewFlags, sortType, sortOrder);
            if (gVirtualFolderTerms)
-             gDBView.viewFolder = msgFolder;        
-    }
+             gDBView.viewFolder = msgFolder;
+
+    let tabmail = GetTabMail();
+    tabmail.saveCurrentTabState(); // gDBView may have changed!
+    tabmail.setTabTitle();
+  }
     else
     {
         msgWindow.openFolder = null;
         ClearThreadPane();
     }
 
     if (gAccountCentralLoaded)
       UpdateMailToolbar("gAccountCentralLoaded");
--- a/suite/mailnews/jar.mn
+++ b/suite/mailnews/jar.mn
@@ -19,16 +19,18 @@ messenger.jar:
 % overlay chrome://messenger/content/mailWindowOverlay.xul                     chrome://messenger/content/msgViewPickerOverlay.xul
     content/messenger/msgViewPickerOverlay.xul
     content/messenger/msgViewPickerOverlay.js
     content/messenger/mailViewSetup.js
     content/messenger/mailViewSetup.xul
     content/messenger/mailViewList.xul
     content/messenger/mailViewList.js
     content/messenger/mailWidgets.xml
+    content/messenger/tabmail.xml
+    content/messenger/tabmail.js
     content/messenger/messenger.css
     content/messenger/messenger.xul
     content/messenger/mailWindowOverlay.xul
     content/messenger/mailWindowOverlay.js
     content/messenger/msgSelectOffline.xul
     content/messenger/mailWindow.js
     content/messenger/messageWindow.xul
     content/messenger/messageWindow.js
--- a/suite/mailnews/mail3PaneWindowCommands.js
+++ b/suite/mailnews/mail3PaneWindowCommands.js
@@ -1,10 +1,10 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -457,27 +457,28 @@ var DefaultController =
       case "cmd_settingsOffline":
         return IsAccountOfflineEnabled();
       default:
         return false;
     }
     return false;
   },
 
-	doCommand: function(command)
-	{
+  doCommand: function(command)
+  {
     // if the user invoked a key short cut then it is possible that we got here for a command which is
     // really disabled. kick out if the command should be disabled.
-    if (!this.isCommandEnabled(command)) return;
-   
-		switch ( command )
-		{
-			case "cmd_close":
-				CloseMailWindow();
-				break;
+    if (!this.isCommandEnabled(command))
+      return;
+
+    switch (command)
+    {
+      case "cmd_close":
+        MsgCloseCurrentTab();
+        break;
       case "button_getNewMessages":
 			case "cmd_getNewMessages":
 				MsgGetMessage();
 				break;
       case "cmd_getMsgsForAuthAccounts":
         MsgGetMessagesForAllAuthenticatedAccounts();
         break;
 			case "cmd_getNextNMessages":
@@ -806,25 +807,21 @@ function WhichPaneHasFocus()
 }
 
 function SetupCommandUpdateHandlers()
 {
   // folder pane
   var widget = GetFolderTree();
   if (widget)
     widget.controllers.appendController(FolderPaneController);
-
-  top.controllers.insertControllerAt(0, DefaultController);
 }
 
 // Called from <msgMail3PaneWindow.js>.
 function UnloadCommandUpdateHandlers()
 {
-  top.controllers.removeController(DefaultController);
-
   var widget = GetFolderTree();
   if (widget)
     widget.controllers.removeController(FolderPaneController);
 }
 
 function IsSendUnsentMsgsEnabled(folderResource)
 {
   var msgSendLater =
--- a/suite/mailnews/mailContextMenus.js
+++ b/suite/mailnews/mailContextMenus.js
@@ -1,9 +1,9 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* ***** 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/
  *
@@ -138,17 +138,17 @@ function FillMailContextMenu(aTarget)
   var showMailItems = inThreadPane ||
                       (!gContextMenu.onImage && !gContextMenu.onLink);
 
   // Select-all and copy are only available in the message-pane
   ShowMenuItem("context-selectall", single && !inThreadPane);
   ShowMenuItem("context-copy", !inThreadPane);
 
   ShowMenuItem("mailContext-openNewWindow", inThreadPane && single);
-  ShowMenuItem("mailContext-openNewTab", inThreadPane);
+  ShowMenuItem("mailContext-openNewTab",    inThreadPane && single);
   ShowMenuItem("mailContext-downloadflagged",
                inThreadPane || (numSelected > 1));
   ShowMenuItem("mailContext-downloadselected",
                inThreadPane || (numSelected > 1));
 
   ShowMenuItem("mailContext-editAsNew", showMailItems && oneOrMore);
   ShowMenuItem("mailContext-replySender", showMailItems && single);
   ShowMenuItem("mailContext-replyNewsgroup",
@@ -258,19 +258,22 @@ function FillFolderPaneContextMenu()
                        gMessengerBundle.getString("getMessagesFor"));
     else
       SetMenuItemLabel("folderPaneContext-getMessages",
                        gMessengerBundle.getString("getMessages"));
 
   ShowMenuItem("folderPaneContext-getMessages", (numSelected <= 1) && canGetMessages);
   EnableMenuItem("folderPaneContext-getMessages", true);
 
-  ShowMenuItem("folderPaneContext-openNewWindow", (numSelected <= 1) && !isServer);
+  ShowMenuItem("folderPaneContext-openNewWindow", (numSelected <= 1));
   EnableMenuItem("folderPaneContext-openNewWindow", true);
 
+  ShowMenuItem("folderPaneContext-openNewTab", (numSelected <= 1));
+  EnableMenuItem("folderPaneContext-openNewTab", true);
+
   SetupRenameMenuItem(folder, numSelected, isServer, serverType, specialFolder);
   SetupRemoveMenuItem(folder, numSelected, isServer, serverType, specialFolder);
   SetupCompactMenuItem(folder, numSelected);
 
   ShowMenuItem("folderPaneContext-emptyTrash", (numSelected <= 1) && (specialFolder == 'Trash'));
   EnableMenuItem("folderPaneContext-emptyTrash", true);
   ShowMenuItem("folderPaneContext-emptyJunk", (numSelected <= 1) && (specialFolder == 'Junk'));
   EnableMenuItem("folderPaneContext-emptyJunk", true);
--- a/suite/mailnews/mailWindow.js
+++ b/suite/mailnews/mailWindow.js
@@ -1,10 +1,10 @@
-/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -39,21 +39,19 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
  //This file stores variables common to mail windows
 var messengerContractID        = "@mozilla.org/messenger;1";
 var statusFeedbackContractID   = "@mozilla.org/messenger/statusfeedback;1";
 var mailSessionContractID      = "@mozilla.org/messenger/services/session;1";
 var secureUIContractID         = "@mozilla.org/secure_browser_ui;1";
-var prefContractID             = "@mozilla.org/preferences-service;1";
 var msgWindowContractID      = "@mozilla.org/messenger/msgwindow;1";
 
 var messenger;
-var pref;
 var statusFeedback;
 var msgWindow;
 
 var msgComposeService;
 var accountManager;
 var RDF;
 var msgComposeType;
 var msgComposeFormat;
@@ -94,45 +92,36 @@ function OnMailWindowUnload()
   }
   
   var dbview = GetDBView();
   if (dbview) {
     dbview.close(); 
   }
 
   var mailSession = Components.classes[mailSessionContractID].getService();
-  if(mailSession)
-  {
-    mailSession = mailSession.QueryInterface(Components.interfaces.nsIMsgMailSession);
-    if(mailSession)
-    {
-      mailSession.RemoveFolderListener(folderListener);
-    }
-  }
-
+  if (mailSession instanceof Components.interfaces.nsIMsgMailSession)
+    mailSession.RemoveFolderListener(folderListener);
   mailSession.RemoveMsgWindow(msgWindow);
   messenger.setWindow(null, null);
 
   msgWindow.closeWindow();
 }
 
 function CreateMessenger()
 {
   messenger = Components.classes[messengerContractID].createInstance();
   messenger = messenger.QueryInterface(Components.interfaces.nsIMessenger);
 }
 
 function CreateMailWindowGlobals()
 {
   // get the messenger instance
   CreateMessenger();
-
-  pref = Components.classes[prefContractID]
-                   .getService(Components.interfaces.nsIPrefBranch2);
-
+  // force pref service initialization
+  GetPrefService();
   //Create windows status feedback
   // set the JS implementation of status feedback before creating the c++ one..
   window.MsgStatusFeedback = new nsMsgStatusFeedback();
   // double register the status feedback object as the xul browser window implementation
   window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
         .getInterface(Components.interfaces.nsIWebNavigation)
         .QueryInterface(Components.interfaces.nsIDocShellTreeItem).treeOwner
         .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
@@ -199,28 +188,28 @@ function InitMsgWindow()
 {
   msgWindow.windowCommands = new nsMsgWindowCommands();
   // set the domWindow before setting the status feedback and header sink objects
   msgWindow.domWindow = window;  
   msgWindow.statusFeedback = statusFeedback;
   msgWindow.msgHeaderSink = messageHeaderSink;
   mailSession.AddMsgWindow(msgWindow);
 
-  var messagepane = document.getElementById("messagepane");
+  var messagepane = getMessageBrowser();
   messagepane.docShell.allowAuth = false;
   msgWindow.rootDocShell.allowAuth = true;
   msgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
   // Ensure we don't load xul error pages into the main window
   msgWindow.rootDocShell.useErrorPages = false;
 }
 
 function messagePaneOnResize(event)
 {
   // scale any overflowing images
-  var messagepane = document.getElementById("messagepane");
+  var messagepane = getMessageBrowser();
   var doc = messagepane.contentDocument;
   var imgs = doc.images;
   for each (var img in imgs)
   {
     if (img.className == "moz-attached-image")
     {
       if (img.naturalWidth <= doc.width)
       {
@@ -572,97 +561,16 @@ function loadStartPage()
   }
   catch (ex)
   {
     dump("Error loading start page.\n");
     return;
   }
 }
 
-// When the ThreadPane is hidden via the displayDeck, we should collapse the
-// elements that are only meaningful to the thread pane. When AccountCentral is
-// shown via the displayDeck, we need to switch the displayDeck to show the
-// accountCentralBox, and load the iframe in the AccountCentral box with
-// corresponding page.
-function ShowAccountCentral()
-{
-    try
-    {
-        document.getElementById("displayDeck").selectedPanel = accountCentralBox;
-        var acctCentralPage = pref.getComplexValue("mailnews.account_central_page.url",
-                                                   Components.interfaces.nsIPrefLocalizedString).data;
-        window.frames["accountCentralPane"].location.href = acctCentralPage;
-    }
-    catch (ex)
-    {
-        dump("Error loading AccountCentral page -> " + ex + "\n");
-        return;
-    }
-}
-
-function ShowingAccountCentral()
-{
-    if (!IsFolderPaneCollapsed())
-        GetFolderTree().focus();
-    gAccountCentralLoaded = true;
-}
-
-function HidingAccountCentral()
-{
-    gAccountCentralLoaded = false;
-}
-
-function ShowThreadPane()
-{
-    document.getElementById("displayDeck").selectedPanel =
-        document.getElementById("threadPaneBox");
-}
-
-function ShowingThreadPane()
-{
-    gSearchBox.collapsed = false;
-    var threadPaneSplitter = document.getElementById("threadpane-splitter");
-    threadPaneSplitter.collapsed = false;
-    if (!threadPaneSplitter.hidden && threadPaneSplitter.getAttribute("state") != "collapsed")
-    {
-        GetMessagePane().collapsed = false;
-        // XXX We need to force the tree to refresh its new height
-        // so that it will correctly scroll to the newest message
-        GetThreadTree().boxObject.height;
-    }
-    document.getElementById("key_toggleMessagePane").removeAttribute("disabled");
-}
-
-function HidingThreadPane()
-{
-    ClearThreadPane();
-    GetUnreadCountElement().hidden = true;
-    GetTotalCountElement().hidden = true;
-    GetMessagePane().collapsed = true;
-    document.getElementById("threadpane-splitter").collapsed = true;
-    gSearchBox.collapsed = true;
-    document.getElementById("key_toggleMessagePane").setAttribute("disabled", "true");
-}
-
-function ObserveDisplayDeckChange(event)
-{
-    var selectedPanel = document.getElementById("displayDeck").selectedPanel;
-    var nowSelected = selectedPanel ? selectedPanel.id : "";
-
-    if (nowSelected == "threadPaneBox")
-        ShowingThreadPane();
-    else
-        HidingThreadPane();
-    
-    if (nowSelected == "accountCentralBox")
-        ShowingAccountCentral();
-    else
-        HidingAccountCentral();
-}
-
 // Given the server, open the twisty and the set the selection
 // on inbox of that server. 
 // prompt if offline.
 function OpenInboxForServer(server)
 {
     try {
         ShowThreadPane();
         var inboxFolder = GetInboxFolder(server);
--- a/suite/mailnews/mailWindowOverlay.js
+++ b/suite/mailnews/mailWindowOverlay.js
@@ -1,10 +1,10 @@
-/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -41,24 +41,26 @@
  * 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 ***** */
 
-// Flags from nsMsgMessageFlags.h
-const MDN_DISPOSE_TYPE_DISPLAYED = 0;
-const MSG_DB_LARGE_COMMIT        = 1;
-
-const kClassicMailLayout = 0;
-const kWideMailLayout = 1;
+Components.utils.import("resource://gre/modules/folderUtils.jsm");
+
+const kClassicMailLayout  = 0;
+const kWideMailLayout     = 1;
 const kVerticalMailLayout = 2;
 
+const kMouseButtonLeft   = 0;
+const kMouseButtonMiddle = 1;
+const kMouseButtonRight  = 2;
+
 // Per message header flags to keep track of whether the user is allowing remote
 // content for a particular message.
 // if you change or add more values to these constants, be sure to modify
 // the corresponding definitions in nsMsgContentPolicy.cpp
 const kNoRemoteContentPolicy = 0;
 const kBlockRemoteContent = 1;
 const kAllowRemoteContent = 2;
 
@@ -157,50 +159,59 @@ function InitEditMessagesMenu()
 {
   goSetMenuValue('cmd_delete', 'valueDefault');
   goSetAccessKey('cmd_delete', 'valueDefaultAccessKey');
   document.commandDispatcher.updateCommands('create-menu-edit');
 }
 
 function InitGoMessagesMenu()
 {
+  // deactivate the folders in the go menu if we don't have a folderpane
+  document.getElementById("mailFolderPane")
+          .setAttribute("disabled", IsFolderPaneCollapsed());
   document.commandDispatcher.updateCommands('create-menu-go');
 }
 
 function view_init()
 {
-  var isFeed = IsFeedItem();
-
   if (!gMessengerBundle)
-      gMessengerBundle = document.getElementById("bundle_messenger");
-
-  var message_menuitem = document.getElementById('menu_showMessagePane');
-  if (message_menuitem && !message_menuitem.hidden) {
-      message_menuitem.setAttribute('checked', !IsMessagePaneCollapsed());
-      message_menuitem.setAttribute('disabled', gAccountCentralLoaded);
+    gMessengerBundle = document.getElementById("bundle_messenger");
+
+  var message_menuitem = document.getElementById("menu_showMessagePane");
+  if (message_menuitem && !message_menuitem.hidden)
+  {
+    message_menuitem.setAttribute("checked", !IsMessagePaneCollapsed());
+    message_menuitem.setAttribute("disabled", gAccountCentralLoaded);
   }
 
-  var folderPane_menuitem = document.getElementById('menu_showFolderPane');
+  var threadpane_menuitem = document.getElementById("menu_showThreadPane");
+  if (threadpane_menuitem && !threadpane_menuitem.hidden)
+  {
+    threadpane_menuitem.setAttribute("checked", !IsDisplayDeckCollapsed());
+    threadpane_menuitem.setAttribute("disabled", gAccountCentralLoaded);
+  }
+
+  var folderPane_menuitem = document.getElementById("menu_showFolderPane");
   if (folderPane_menuitem && !folderPane_menuitem.hidden)
-    folderPane_menuitem.setAttribute('checked', !IsFolderPaneCollapsed());
-
-  var sort_menuitem = document.getElementById('viewSortMenu');
-  if (sort_menuitem) {
+    folderPane_menuitem.setAttribute("checked", !IsFolderPaneCollapsed());
+
+  var sort_menuitem = document.getElementById("viewSortMenu");
+  if (sort_menuitem)
     sort_menuitem.setAttribute("disabled", gAccountCentralLoaded);
-  }
-  var view_menuitem = document.getElementById('viewMessageViewMenu');
-  if (view_menuitem) {
+
+  var view_menuitem = document.getElementById("viewMessageViewMenu");
+  if (view_menuitem)
     view_menuitem.setAttribute("disabled", gAccountCentralLoaded);
-  }
-  var threads_menuitem = document.getElementById('viewMessagesMenu');
-  if (threads_menuitem) {
+
+  var threads_menuitem = document.getElementById("viewMessagesMenu");
+  if (threads_menuitem)
     threads_menuitem.setAttribute("disabled", gAccountCentralLoaded);
-  }
 
   // Initialize the Message Body menuitem
+  var isFeed = IsFeedItem();
   document.getElementById('viewBodyMenu').hidden = isFeed;
 
   // Initialize the Show Feed Summary menu
   var viewFeedSummary = document.getElementById('viewFeedSummary');
   var winType = document.documentElement.getAttribute('windowtype');
   if (winType != "mail:3pane")
     viewFeedSummary.hidden = !gShowFeedSummary;
   else
@@ -804,17 +815,17 @@ function PopulateHistoryMenu(menuPopup, 
 
 function NavigateToUri(target)
 {
   var historyIndex = target.getAttribute('value');
   let folderUri = target.folder.URI;
   var msgUri = messenger.getMsgUriAtNavigatePos(historyIndex);
   let msgHdrKey = messenger.msgHdrFromURI(msgUri).messageKey;
   messenger.navigatePos += Number(historyIndex);
-  if (folderURI == GetThreadPaneFolder().URI)
+  if (folderUri == GetThreadPaneFolder().URI)
   {
     gDBView.selectMsgByKey(msgHdrKey);
   }
   else
   {
     gStartMsgKey = msgHdrKey;
     SelectFolder(folderUri);
   }
@@ -1594,28 +1605,26 @@ function MsgOpenNewWindowForFolder(uri, 
   if (uriToOpen) {
    // get the messenger window open service and ask it to open a new window for us
    var mailWindowService = Components.classes["@mozilla.org/messenger/windowservice;1"].getService(Components.interfaces.nsIMessengerWindowService);
    if (mailWindowService)
      mailWindowService.openMessengerWindowWithUri("mail:3pane", uriToOpen, keyToSelect);
   }
 }
 
-// passing in the view, so this will work for search and the thread pane
 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 (IsFeedItem() && GetFeedOpenHandler() == 2) {
     FeedSetContentViewToggle();
     return;
   }
 
   var dbView = GetDBView();
-
   var indices = GetSelectedIndices(dbView);
   var numMessages = indices.length;
 
   gWindowReuse = 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.
@@ -2167,17 +2176,16 @@ function SetUpToolbarButtons(uri)
 }
 
 var gMessageBrowser;
 
 function getMessageBrowser()
 {
   if (!gMessageBrowser)
     gMessageBrowser = document.getElementById("messagepane");
-
   return gMessageBrowser;
 }
 
 function getMarkupDocumentViewer()
 {
   return getMessageBrowser().markupDocumentViewer;
 }
 
@@ -2723,16 +2731,22 @@ function ClearPendingReadTimer()
 function OnMsgParsed(aUrl)
 {
   // If rss feed (has 'content-base' header), show summary or load web
   // page per pref; earliest we have content DOM is here (onMsgParsed).
   FeedSetContentView();
 
   gMessageNotificationBar.setPhishingMsg(aUrl);
 
+  // notify anyone (e.g., extensions) who's interested in when a message is loaded.
+  var msgURI = GetLoadedMessage();
+  var observerService = Components.classes["@mozilla.org/observer-service;1"]
+                                  .getService(Components.interfaces.nsIObserverService);
+  observerService.notifyObservers(msgWindow.msgHeaderSink, "MsgMsgDisplayed", msgURI);
+
   // scale any overflowing images
   var doc = getMessageBrowser().contentDocument;
   var imgs = doc.getElementsByTagName("img");
   for each (var img in imgs)
   {
     if (img.className == "moz-attached-image" && img.naturalWidth > doc.width)
     {
       if (img.hasAttribute("shrinktofit"))
@@ -2890,27 +2904,32 @@ function HandleMDNResponse(aUrl)
   var DNTHeader = mimeHdr.extractHeader("Disposition-Notification-To", false);
   var oldDNTHeader = mimeHdr.extractHeader("Return-Receipt-To", false);
   if (!DNTHeader && !oldDNTHeader)
     return;
 
   // Everything looks good so far, let's generate the MDN response.
   var mdnGenerator = Components.classes["@mozilla.org/messenger-mdn/generator;1"].
                                   createInstance(Components.interfaces.nsIMsgMdnGenerator);
-  mdnGenerator.process(MDN_DISPOSE_TYPE_DISPLAYED, msgWindow, msgFolder, msgHdr.messageKey, mimeHdr, false);
+  mdnGenerator.process(Components.interfaces.nsIMsgMdnGenerator.eDisplayed,
+                       msgWindow,
+                       msgFolder,
+                       msgHdr.messageKey,
+                       mimeHdr,
+                       false);
 
   // Reset mark msg MDN "Sent" and "Not Needed".
   msgHdr.flags = (msgFlags &
                   ~Components.interfaces.nsMsgMessageFlags.MDNReportNeeded);
   msgHdr.OrFlags(Components.interfaces.nsMsgMessageFlags.MDNReportSent);
 
   // Commit db changes.
   var msgdb = msgFolder.msgDatabase;
   if (msgdb)
-    msgdb.Commit(MSG_DB_LARGE_COMMIT);
+    msgdb.Commit(Components.interfaces.nsMsgDBCommitType.kLargeCommit);
 }
 
 function MsgSearchMessages()
 {
   var preselectedFolder = null;
   if ("GetFirstSelectedMsgFolder" in window)
     preselectedFolder = GetFirstSelectedMsgFolder();
 
@@ -3102,18 +3121,17 @@ function FeedSetContentView(val)
   if (showSummary) {
     if (gShowFeedSummaryToggle) {
       if (gDBView && GetNumSelectedMessages() == 1) {
         ReloadMessage();
       }
     }
   }
   else if(contentBase.headerValue) {
-    document.getElementById("messagepane")
-            .loadURI(contentBase.headerValue, null, null);
+    getMessageBrowser().loadURI(contentBase.headerValue, null, null);
     gShowFeedSummaryToggle = false;
   }
 }
 
 function getMailToolbox()
 {
   return document.getElementById("mail-toolbox");
 }
--- a/suite/mailnews/mailWindowOverlay.xul
+++ b/suite/mailnews/mailWindowOverlay.xul
@@ -78,16 +78,21 @@
 
 <stringbundleset id="stringbundleset">
   <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
   <stringbundle id="bundle_offlinePrompts" src="chrome://messenger/locale/offline.properties"/>
   <stringbundle id="bundle_viewZoom"/>
   <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
 </stringbundleset>
 
+<broadcasterset id="mailBroadcasters">
+  <!-- Go Menu -->
+  <broadcaster id="mailFolderPane"/>
+</broadcasterset>
+
 <!-- Performance optimization...we include utilityOverlay.xul which defines some command sets
      which are updated based on events like focus and select. We have our own custom events
      which we use to optmize when we do command updating. To avoid unnecessary command updating,
      we are going to override the events the global edit menu items and select edit menu items
      are updated on with events of our own controlling.
  -->
 
 <commandset id="globalEditMenuItems"
@@ -324,16 +329,20 @@
   <command id="cmd_deleteJunk" oncommand="goDoCommand('cmd_deleteJunk');" disabled="true"/>
   <command id="cmd_search" oncommand="goDoCommand('cmd_search')"/>
 </commandset>
 
 <keyset id="mailKeys">
   <key id="space" key=" " modifiers="shift any" oncommand="SpaceHit(event);"/>
 
   <!-- File Menu -->
+  <key id="key_newTab"
+       key="&newTabCmd.key;"
+       modifiers="accel"
+       oncommand="MsgOpenNewTab();"/>
   <key id="key_newNavigator"/>
   <key id="key_newBlankPage"/>
   <key id="key_close"/>
   <key id="key_quit"/>
   <!-- Edit Menu -->
   <key id="key_undo"/>
   <key id="key_redo"/>
   <key id="key_cut"/>
@@ -400,18 +409,29 @@
   <key keycode="VK_TAB" oncommand="SwitchPaneFocus(event);" modifiers="control,shift"/>
   <key keycode="VK_TAB" oncommand="SwitchPaneFocus(event);" modifiers="control"/>
   <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="control,shift"/>
   <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="control"/>
   <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="shift"/>
   <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);"/>
 
   <!-- View Toggle Keys (F8/F9) -->
-  <key id="key_toggleMessagePane" keycode="VK_F8" oncommand="MsgToggleMessagePane();" disabled="true"/>
-  <key id="key_toggleFolderPane" keycode="VK_F9" oncommand="MsgToggleSplitter('gray_vertical_splitter');" observes="mailDisableKeys"/>
+  <key id="key_toggleFolderPane"
+       keycode="VK_F9"
+       oncommand="MsgToggleFolderPane(true);"
+       observes="mailDisableKeys"/>
+  <key id="key_toggleThreadPane"
+       keycode="VK_F8"
+       modifiers="shift"
+       oncommand="MsgToggleThreadPane();"
+       disabled="true"/>
+  <key id="key_toggleMessagePane"
+       keycode="VK_F8"
+       oncommand="MsgToggleMessagePane(true);"
+       disabled="true"/>
 
   <!-- Tag Keys -->
   <!-- Includes both shifted and not, for Azerty and other layouts where the
        numeric keys are shifted. -->
   <key id="key_tag0" key="&tagCmd0.key;" modifiers="shift any"
        oncommand="RemoveAllMessageTags();"/>
   <key id="key_tag1" key="&tagCmd1.key;" modifiers="shift any"
        oncommand="ToggleMessageTagKey(1);"/>
@@ -438,32 +458,32 @@
        modifiers="accel"
        oncommand="focusElement(document.getElementById('searchInput'));"/>
 
 </keyset>
 
   <popup id="folderPaneContext"
          onpopupshowing="return FillFolderPaneContextMenu();"
          onpopuphiding="if (event.target == this) FolderPaneOnPopupHiding();">
-
     <menuitem id="folderPaneContext-getMessages"
               label="&folderContextGetMessages.label;"
               accesskey="&folderContextGetMessages.accesskey;"
               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();"/>
     <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();"/>
     <menuitem id="folderPaneContext-newsUnsubscribe"
         label="&folderContextUnsubscribe.label;"
         accesskey="&folderContextUnsubscribe.accesskey;"
         oncommand="MsgUnsubscribe();"/>
@@ -542,16 +562,20 @@
               accesskey="&copyCmd.accesskey;"
               command="cmd_copy"/>
     <menuitem id="context-searchselect"
               oncommand="MsgOpenSearch(gContextMenu.searchSelected(), event.shiftKey);"/>
     <menuitem id="mailContext-openNewWindow"
               label="&contextOpenNewWindow.label;"
               accesskey="&contextOpenNewWindow.accesskey;"
               oncommand="MsgOpenNewWindowForMessage();"/>
+    <menuitem id="mailContext-openNewTab"
+              label="&contextOpenNewTab.label;"
+              accesskey="&contextOpenNewTab.accesskey;"
+              oncommand="MsgOpenNewTabForMessage();"/>
     <menuseparator id="mailContext-sep-open"/>
     <menuitem id="mailContext-replySender"
               label="&contextReplySender.label;"
               accesskey="&contextReplySender.accesskey;"
               oncommand="MsgReplySender(event);"/>
     <menuitem id="mailContext-replyNewsgroup"
               label="&contextReplyNewsgroup.label;"
               accesskey="&contextReplyNewsgroup.accesskey;"
@@ -930,26 +954,33 @@
   <menu id="menu_File" >
     <menupopup id="menu_FilePopup" onpopupshowing="file_init();">
       <menu id="menu_New">
         <menupopup id="menu_NewPopup" onpopupshowing="menu_new_init();">
           <menuitem label="&newNewMsgCmd.label;"
                     accesskey="&newNewMsgCmd.accesskey;"
                     key="key_newMessage"
                     oncommand="MsgNewMessage(null);"/>
-          <menuitem id="menu_newFolder" label="&newFolderCmd.label;"
-                     oncommand="MsgNewFolder(NewFolder);"
-                     accesskey="&newFolderCmd.accesskey;"/>
+          <menuitem id="menu_newFolder"
+                    label="&newFolderCmd.label;"
+                    accesskey="&newFolderCmd.accesskey;"
+                    oncommand="MsgNewFolder(NewFolder);"/>
           <menuitem id="menu_newVirtualFolder" label="&newVirtualFolderCmd.label;"
                     oncommand="MsgVirtualFolderProperties(false);"
                     accesskey="&newVirtualFolderCmd.accesskey;"/>
-          <menuitem id="newAccountMenuItem" label="&newAccountCmd.label;"
-                     accesskey="&newAccountCmd.accesskey;"
-                     oncommand="MsgAccountWizard();"/>
+          <menuitem id="newAccountMenuItem"
+                    label="&newAccountCmd.label;"
+                    accesskey="&newAccountCmd.accesskey;"
+                    oncommand="MsgAccountWizard();"/>
           <menuseparator id="newPopupMenuSeparator"/>
+          <menuitem id="menu_newTab"
+                    label="&newTabCmd.label;"
+                    accesskey="&newTabCmd.accesskey;"
+                    key="key_newTab"
+                    oncommand="MsgOpenNewTab();"/>
           <menuitem id="menu_newNavigator"/>
           <menuitem id="menu_newEditor"/>
         </menupopup>
       </menu>
       <menuitem id="openMessageFileMenuitem" label="&openMessageFileCmd.label;"
         key="key_openFileMessage"
         accesskey="&openMessageFileCmd.accesskey;"
         oncommand="MsgOpenFromFile();"/>
@@ -1147,21 +1178,36 @@
         <menupopup id="view_layout_popup" onpopupshowing="InitViewLayoutStyleMenu(event)">
           <menuitem id="messagePaneClassic" type="radio" label="&messagePaneClassic.label;" name="viewlayoutgroup"
                     accesskey="&messagePaneClassic.accesskey;" oncommand="ChangeMailLayout(kClassicMailLayout);"/>
           <menuitem id="messagePaneWide" type="radio" label="&messagePaneWide.label;" name="viewlayoutgroup"
                     accesskey="&messagePaneWide.accesskey;" oncommand="ChangeMailLayout(kWideMailLayout);"/>
           <menuitem id="messagePaneVertical" type="radio" label="&messagePaneVertical.label;" name="viewlayoutgroup"
                     accesskey="&messagePaneVertical.accesskey;" oncommand="ChangeMailLayout(kVerticalMailLayout);"/>
           <menuseparator id="viewMenuAfterPaneVerticalSeparator"/>
-          <menuitem id="menu_showMessagePane" type="checkbox" label="&showMessagePaneCmd.label;" key="key_toggleMessagePane"
-                    accesskey="&showMessagePaneCmd.accesskey;" oncommand="MsgToggleMessagePane();"
+          <menuitem id="menu_showMessagePane"
+                    type="checkbox"
+                    label="&showMessagePaneCmd.label;"
+                    accesskey="&showMessagePaneCmd.accesskey;"
+                    key="key_toggleMessagePane"
+                    oncommand="MsgToggleMessagePane(true);"
                     observes="mailHideMenus"/>
-          <menuitem id="menu_showFolderPane" type="checkbox" label="&showFolderPaneCmd.label;" key="key_toggleFolderPane"
-                    accesskey="&showFolderPaneCmd.accesskey;" oncommand="MsgToggleSplitter('gray_vertical_splitter');"
+          <menuitem id="menu_showThreadPane"
+                    type="checkbox"
+                    label="&showThreadPaneCmd.label;"
+                    accesskey="&showThreadPaneCmd.accesskey;"
+                    key="key_toggleThreadPane"
+                    oncommand="MsgToggleThreadPane();"
+                    observes="mailHideMenus"/>
+          <menuitem id="menu_showFolderPane"
+                    type="checkbox"
+                    label="&showFolderPaneCmd.label;"
+                    accesskey="&showFolderPaneCmd.accesskey;"
+                    key="key_toggleFolderPane"
+                    oncommand="MsgToggleFolderPane(true);"
                     observes="mailHideMenus"/>
         </menupopup>
       </menu>
       <menuseparator id="viewMessagesMenuSeparator" observes="mailHideMenus"/>
       <menu id="viewSortMenu" label="&sortMenu.label;"
             accesskey="&sortMenu.accesskey;" observes="mailHideMenus">
         <menupopup id="menu_viewSortPopup" onpopupshowing="InitViewSortByMenu()">
           <menuitem id="sortByDateMenuitem" type="radio" name="sortby" label="&sortByDateCmd.label;" accesskey="&sortByDateCmd.accesskey;" oncommand="MsgSortThreadPane('byDate')"/>
@@ -1389,16 +1435,17 @@
                 accesskey="&startPageCmd.accesskey;" command="cmd_goStartPage"
                 observes="mailHideMenus"/>
       <menuseparator id="goNextAfterStartPageSeparator" observes="mailHideMenus"/>
     </menupopup>
     <template>
       <rule iscontainer="true" isempty="false">
         <menupopup>
           <menu uri="rdf:*" class="folderMenuItem menu-iconic"
+                observes="mailFolderPane"
               SpecialFolder="rdf:http://home.netscape.com/NC-rdf#SpecialFolder"
               BiffState="rdf:http://home.netscape.com/NC-rdf#BiffState"
               IsServer="rdf:http://home.netscape.com/NC-rdf#IsServer"
               IsSecure="rdf:http://home.netscape.com/NC-rdf#IsSecure"
               ServerType="rdf:http://home.netscape.com/NC-rdf#ServerType"
               HasUnreadMessages="rdf:http://home.netscape.com/NC-rdf#HasUnreadMessages"
               NewMessages="rdf:http://home.netscape.com/NC-rdf#NewMessages"
               SubfoldersHaveUnreadMessages="rdf:http://home.netscape.com/NC-rdf#SubfoldersHaveUnreadMessages"
@@ -1418,16 +1465,17 @@
               <menuseparator/>
             </menupopup>
           </menu>
         </menupopup>
       </rule>
       <rule>
         <menupopup>
           <menuitem uri="rdf:*" value="rdf:*" class="folderMenuItem menuitem-iconic"
+                    observes="mailFolderPane"
               SpecialFolder="rdf:http://home.netscape.com/NC-rdf#SpecialFolder"
               BiffState="rdf:http://home.netscape.com/NC-rdf#BiffState"
               IsServer="rdf:http://home.netscape.com/NC-rdf#IsServer"
               IsSecure="rdf:http://home.netscape.com/NC-rdf#IsSecure"
               ServerType="rdf:http://home.netscape.com/NC-rdf#ServerType"
               HasUnreadMessages="rdf:http://home.netscape.com/NC-rdf#HasUnreadMessages"
               NewMessages="rdf:http://home.netscape.com/NC-rdf#NewMessages"
               SubfoldersHaveUnreadMessages="rdf:http://home.netscape.com/NC-rdf#SubfoldersHaveUnreadMessages"
--- a/suite/mailnews/messageWindow.xul
+++ b/suite/mailnews/messageWindow.xul
@@ -75,16 +75,17 @@
   <script type="application/x-javascript" src="chrome://messenger/content/messageWindow.js"/>
   <script type="application/x-javascript" src="chrome://messenger/content/accountUtils.js"/>
   <script type="application/x-javascript" src="chrome://messenger/content/mailContextMenus.js"/>
   <script type="application/x-javascript" src="chrome://messenger/content/phishingDetector.js"/>
   <script type="application/x-javascript" src="chrome://communicator/content/contentAreaClick.js"/>
   <script type="application/x-javascript" src="chrome://global/content/nsDragAndDrop.js"/>
   <script type="application/x-javascript" src="chrome://messenger/content/msgViewNavigation.js"/>
   <script type="application/x-javascript" src="chrome://messenger/content/widgetglue.js"/>
+  <script type="application/x-javascript" src="chrome://messenger/content/tabmail.js"/>
 
   <commandset id="mailCommands">
     <commandset id="mailFileMenuItems"/>
     <commandset id="mailDownloadCommands"/>
     <commandset id="mailViewMenuItems"/>
     <commandset id="mailEditMenuItems"/>
     <commandset id="mailSearchMenuItems"/>
     <commandset id="mailGoMenuItems"/>
--- a/suite/mailnews/messenger.css
+++ b/suite/mailnews/messenger.css
@@ -199,8 +199,57 @@ folderSummaryMessage
 .foldersTreeChildren::-moz-tree-twisty
 {
   width: 0px;
 }
 
 dummy.usesMailWidgets {
   -moz-binding: url("chrome://messenger/content/mailWidgets.xml#dummy");
 }
+
+/* tabmail */
+
+#tabmail
+{
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail");
+}
+
+.tabmail-tabs {
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-tabs");
+}
+
+.tabmail-arrowscrollbox {
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-arrowscrollbox");
+}
+
+.tabmail-tab {
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-tab");
+}
+
+.tabs-newbutton {
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-new-tab-button");
+  display: none;
+}
+
+.tabmail-tabs[closebuttons="closeatend"] .tabs-newbutton {
+  display: -moz-box;
+}
+
+.tab-close-button,
+.tabs-closebutton {
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-close-tab-button");
+}
+
+.tab-close-button {
+  display: none;
+}
+
+.tabmail-tabs:not([closebuttons="noclose"]):not([closebuttons="closeatend"]) > .tabmail-tab[selected="true"] > .tab-close-button {
+  display: -moz-box;
+}
+
+.tabmail-tabs[closebuttons="alltabs"] .tab-close-button {
+  display: -moz-box;
+}
+
+.tabs-alltabs-popup {
+  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-alltabs-popup");
+}
--- a/suite/mailnews/messenger.xul
+++ b/suite/mailnews/messenger.xul
@@ -55,16 +55,17 @@
 <window id="messengerWindow"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:nc="http://home.netscape.com/NC-rdf#"
         title="&messengerWindow.title;"
         titlemodifier="&titleModifier.label;"
         titlemenuseparator="&titleSeparator.label;"
         onload="OnLoadMessenger()"
         onunload="OnUnloadMessenger()"
+        onclose="return MailWindowIsClosing();"
         style="width: 60em; height: 40em;"
         screenX="10" screenY="10"
         persist="width height screenX screenY sizemode"
         windowtype="mail:3pane">
 
 <stringbundleset id="stringbundleset">
   <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
   <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
@@ -81,16 +82,17 @@
 <script type="application/x-javascript" src="chrome://messenger/content/mailContextMenus.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/messengerdnd.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/accountUtils.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/mail-offline.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/phishingDetector.js"/>
 <script type="application/x-javascript" src="chrome://communicator/content/contentAreaClick.js"/>
 <script type="application/x-javascript" src="chrome://global/content/nsDragAndDrop.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/searchBar.js"/>
+<script type="application/x-javascript" src="chrome://messenger/content/tabmail.js"/>
 
 <commandset id="mailCommands">
   <commandset id="mailFileMenuItems"/>
   <commandset id="mailDownloadCommands"/>
   <commandset id="mailViewMenuItems"/>
   <commandset id="mailEditMenuItems"/>
   <commandset id="mailEditContextMenuItems"/>
   <commandset id="mailSearchMenuItems"/>
@@ -110,121 +112,153 @@
   <commandset id="tasksCommands"/>
 </commandset>
 
 <broadcasterset id="mailBroadcasters">
   <broadcaster id="mailHideMenus"/>
   <broadcaster id="mailDisableKeys"/>
   <!-- File Menu -->
   <broadcaster id="Communicator:WorkMode"/>
-  <!-- Edit Menu -->
 </broadcasterset>
 
 <keyset id="mailKeys">
   <keyset id="tasksKeys"/>
 </keyset>
 
 <popupset id="mainPopupSet"/>
-
 <popup id="mailContext"/>
 <popup id="folderPaneContext"/>
 <popup id="attachmentListContext"/>
 <tooltip id="attachmentListTooltip"/>
-
 <popup id="copyUrlPopup"/>
 <popup id="toolbar-context-menu"/>
 <popup id="messageIdContext"/>
 <popup id="emailAddressPopup"/>
-
 <tooltip id="folderpopup" class="folderSummaryPopup"/>
-
 <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
 
   <toolbox id="mail-toolbox" class="toolbox-top">
     <toolbar id="mail-toolbar-menubar2">
       <toolbaritem id="menubar-items">
         <menubar id="mail-menubar"/>
       </toolbaritem>
     </toolbar>
     <toolbar id="msgToolbar"/>
     <toolbarset id="customToolbars" context="toolbar-context-menu"/>
     <toolbar id="msgLocationToolbar"/>
   </toolbox>
-
-  <!-- The main mail three pane frame -->
-  <box id="mailContent" orient="vertical" flex="1">
-    <box id="messengerBox" orient="horizontal" flex="1" minheight="100" height="100" persist="height">
-      <vbox id="folderPaneBox" minwidth="100" width="200" persist="collapsed width">
-        <tree id="folderTree"
-              treelines="true"
-              keepcurrentinview="true"
-              flex="1"
-              context="folderPaneContext"
-              class="window-focusborder"
-              focusring="false">
-          <treechildren tooltip="folderpopup"/>
-        </tree>
-      </vbox>
+  <panel id="customizeToolbarSheetPopup"/>
 
-      <splitter id="gray_vertical_splitter" collapse="before" persist="state" resizeafter="grow">
-        <grippy/>
-      </splitter>
-
-      <box id="messagesBox" orient="vertical" flex="1">
-        <hbox id="searchBoxHolder"/>
-        <deck id="displayDeck" flex="1" selectedIndex="0"
-              minheight="100" height="100" persist="height"
-              onselect="ObserveDisplayDeckChange(event)">
-          <!-- first panel in displayDeck is Account Central -->
-          <vbox id="accountCentralBox">
-            <iframe name="accountCentralPane" width="150" flex="1" src="about:blank"/>
-          </vbox>
-          <!-- second panel is the threadPane -->
-          <hbox id="threadPaneBox">
-            <tree id="threadTree"
-                  treelines="true"
-                  keepcurrentinview="true"
-                  flex="1"
+  <!-- XXX This extension point (tabmail-container) is only temporary!
+           (See bug 460252 for details.)
+           We will readd a mechanism for sidebar panes in bug 178003.
+    -->
+  <hbox id="tabmail-container" flex="1">
+    <tabbrowser id="tabmail" flex="1" panelcontainer="tabpanelcontainer">
+      <box id="tabmail-buttons" orientation="horizontal"/>
+      <tabpanels id="tabpanelcontainer" flex="1" class="plain" selectedIndex="0">
+        <!-- The main mail three pane frame -->
+        <box id="mailContent" orient="vertical" flex="1">
+          <box id="messengerBox"
+               orient="horizontal"
+               flex="1"
+               minheight="100"
+               height="100"
+               persist="height">
+            <vbox id="folderPaneBox"
                   minwidth="100"
-                  width="100"
-                  persist="width"
-                  context="mailContext"
-                  class="window-focusborder"
-                  focusring="false"/>
-          </hbox>
-          <!-- extensions may overlay in additional panels; don't assume that there are only 2! -->
-        </deck>
+                  width="200"
+                  persist="collapsed width hidden">
+              <tree id="folderTree"
+                    treelines="true"
+                    keepcurrentinview="true"
+                    flex="1"
+                    context="folderPaneContext"
+                    class="window-focusborder"
+                    focusring="false">
+                <treechildren tooltip="folderpopup"/>
+              </tree>
+            </vbox>
 
-        <!-- if you change this id, please change GetThreadAndMessagePaneSplitter() and MsgToggleMessagePane() -->
-        <splitter id="threadpane-splitter" collapse="after" persist="state hidden" collapsed="true"
-                  oncommand="OnClickThreadAndMessagePaneSplitter()">
-          <grippy/>
-        </splitter>
-
-        <vbox id="messagepanebox" flex="2" minheight="100" height="200"
-              minwidth="100" width="200" persist="height width"
-              class="window-focusborder" focusring="false">
-          <deck id="msgNotificationBar"/>
+            <splitter id="folderpane-splitter"
+                      collapse="before"
+                      resizeafter="grow"
+                      persist="state collapsed"
+                      oncommand="MsgToggleFolderPane(false);">
+              <grippy/>
+            </splitter>
 
-          <hbox id="msgHeaderView"/>
+            <box id="messagesBox" orient="vertical" flex="1">
+              <deck id="displayDeck"
+                    flex="1"
+                    selectedIndex="0"
+                    minheight="100"
+                    height="100"
+                    persist="height"
+                    onselect="ObserveDisplayDeckChange(event);">
+                <!-- first panel in displayDeck is Account Central -->
+                <vbox id="accountCentralBox">
+                  <iframe name="accountCentralPane"
+                          width="150"
+                          flex="1"
+                          src="about:blank"/>
+                </vbox>
+                <!-- second panel is the threadPane -->
+                <vbox id="threadPaneBox">
+                  <hbox id="searchBoxHolder"/>
+                  <tree id="threadTree"
+                        treelines="true"
+                        keepcurrentinview="true"
+                        flex="1"
+                        minwidth="100"
+                        width="100"
+                        persist="width"
+                        context="mailContext"
+                        class="window-focusborder"
+                        focusring="false"/>
+                </vbox>
+                <!-- extensions may overlay in additional panels; don't assume that there are only 2! -->
+              </deck>
 
-          <browser id="messagepane"
-                   name="messagepane"
-                   height="0"
-                   flex="1"
-                   minwidth="1"
-                   minheight="1"
-                   tooltip="aHTMLTooltip"
-                   context="mailContext"
-                   disablesecurity="true"
-                   disablehistory="true"
-                   autofind="false"
-                   type="content-primary"
-                   onresize="return messagePaneOnResize(event);"
-                   onclick="return messagePaneOnClick(event);"/>
-        </vbox>
-      </box>
-    </box>
-  </box>
-  <panel id="customizeToolbarSheetPopup"/>
+              <!-- if you change this id, please change GetThreadAndMessagePaneSplitter() and MsgToggleMessagePane() -->
+              <splitter id="threadpane-splitter"
+                        collapse="after"
+                        persist="state collapsed hidden"
+                        collapsed="true"
+                        oncommand="MsgToggleMessagePane(false);">
+                <grippy/>
+              </splitter>
+
+              <vbox id="messagepanebox"
+                    flex="2"
+                    minheight="100"
+                    height="200"
+                    minwidth="100"
+                    width="200"
+                    persist="height width"
+                    class="window-focusborder"
+                    focusring="false">
+                <deck id="msgNotificationBar"/>
+                <hbox id="msgHeaderView"/>
+                <browser id="messagepane"
+                         name="messagepane"
+                         height="0"
+                         flex="1"
+                         minwidth="1"
+                         minheight="1"
+                         tooltip="aHTMLTooltip"
+                         context="mailContext"
+                         disablesecurity="true"
+                         disablehistory="true"
+                         autofind="false"
+                         type="content-primary"
+                         onresize="return messagePaneOnResize(event);"
+                         onclick="return messagePaneOnClick(event);"/>
+              </vbox>
+            </box>
+          </box>
+        </box>
+      </tabpanels>
+    </tabbrowser>
+  </hbox>
 
   <statusbar id="status-bar" class="chromeclass-status mailwindow-statusbar"/>
 </window>
--- a/suite/mailnews/msgHdrViewOverlay.js
+++ b/suite/mailnews/msgHdrViewOverlay.js
@@ -1,10 +1,10 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -258,37 +258,35 @@ function OnLoadMsgHeaderPane()
   gShowReferences = pref.getBoolPref("mailnews.headers.showReferences");
   gShowMessageId = pref.getBoolPref("mailnews.headers.showMessageId");
   gExtraExpandedHeaders = pref.getCharPref("mailnews.headers.extraExpandedHeaders");
 
   pref.addObserver("mail.showCondensedAddresses", MsgHdrViewObserver, false);
 
   initializeHeaderViewTables();
 
-  var toggleHeaderView = document.getElementById("msgHeaderView");
+  var toggleHeaderView = GetHeaderPane();
   var initialCollapsedSetting = toggleHeaderView.getAttribute("state");
   if (initialCollapsedSetting == "true")
     gCollapsedHeaderViewMode = true;   
 
   // dispatch an event letting any listeners know that we have loaded the message pane
   var event = document.createEvent('Events');
   event.initEvent('messagepane-loaded', false, true);
-  var headerViewElement = document.getElementById("msgHeaderView");
-  headerViewElement.dispatchEvent(event);
+  toggleHeaderView.dispatchEvent(event);
 }
 
 function OnUnloadMsgHeaderPane()
 {
   pref.removeObserver("mail.showCondensedAddresses", MsgHdrViewObserver);
 
   // dispatch an event letting any listeners know that we have unloaded the message pane
   var event = document.createEvent('Events');
   event.initEvent('messagepane-unloaded', false, true);
-  var headerViewElement = document.getElementById("msgHeaderView");
-  headerViewElement.dispatchEvent(event);
+  GetHeaderPane().dispatchEvent(event);
 }
 
 const MsgHdrViewObserver =
 {
   observe: function(subject, topic, prefName)
   {
     // verify that we're changing the mail pane config pref
     if (topic == "nsPref:changed")
@@ -713,24 +711,23 @@ function updateHeaderViews()
   }
   else
   {
     showHeaderView(gExpandedHeaderView);
     displayAttachmentsForExpandedView();
   }
 }
 
-function ToggleHeaderView ()
+function ToggleHeaderView()
 {
   var expandedNode = document.getElementById("expandedHeaderView");
   var collapsedNode = document.getElementById("collapsedHeaderView");
-  var toggleHeaderView = document.getElementById("msgHeaderView");
 
   if (gCollapsedHeaderViewMode)
-  {          
+  {
     gCollapsedHeaderViewMode = false;
     // hide the current view
     hideHeaderView(gCollapsedHeaderView);
     // update the current view
     UpdateMessageHeaders();
     
     // now uncollapse / collapse the right views
     expandedNode.collapsed = false;
@@ -744,16 +741,17 @@ function ToggleHeaderView ()
     // update the current view
     UpdateMessageHeaders();
     
     // now uncollapse / collapse the right views
     collapsedNode.collapsed = false;
     expandedNode.collapsed = true;
   }  
 
+  var toggleHeaderView = GetHeaderPane();
   if (gCollapsedHeaderViewMode)
     toggleHeaderView.setAttribute("state", "true");
   else
     toggleHeaderView.setAttribute("state", "false");
 }
 
 // default method for updating a header value into a header entry
 function updateHeaderValue(headerEntry, headerValue)
@@ -908,17 +906,17 @@ function ShowMessageHeaderPane()
     node = document.getElementById("expandedHeaderView");
     if (node)
       node.collapsed = false;
   }
 
   /* workaround for 39655 */
   if (gFolderJustSwitched) 
   {
-    var el = document.getElementById("msgHeaderView");
+    let el = GetHeaderPane();
     el.setAttribute("style", el.getAttribute("style"));
     gFolderJustSwitched = false;    
   }
 }
 
 function HideMessageHeaderPane()
 {
   var node = document.getElementById("collapsedHeaderView");
--- a/suite/mailnews/msgMail3PaneWindow.js
+++ b/suite/mailnews/msgMail3PaneWindow.js
@@ -1,10 +1,10 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -20,16 +20,17 @@
  * Portions created by the Initial Developer are Copyright (C) 1998-1999
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Jan Varga (varga@ku.sk)
  *   Håkan Waara (hwaara@chello.se)
  *   Neil Rashbrook (neil@parkwaycc.co.uk)
  *   Seth Spitzer <sspitzer@netscape.com>
+ *   Karsten Düsterloh <mnyromyr@tprac.de>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -43,20 +44,18 @@
 /* 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";
 
 var gFolderTree;
-var gMessagePane;
 var gSearchInput;
 
-var gThreadAndMessagePaneSplitter = null;
 var gUnreadCount = null;
 var gTotalCount = null;
 
 var gCurrentLoadingFolderURI;
 var gCurrentFolderToReroot;
 var gCurrentLoadingFolderSortType = 0;
 var gCurrentLoadingFolderSortOrder = 0;
 var gCurrentLoadingFolderViewType = 0;
@@ -87,16 +86,18 @@ var gThreadPaneDeleteOrMoveOccurred = fa
 var gHaveLoadedMessage;
 
 var gDisplayStartupPage = false;
 
 function SelectAndScrollToKey(aMsgKey)
 {
   // select the desired message
   // if the key isn't found, we won't select anything
+  if (!gDBView)
+    return false;
   gDBView.selectMsgByKey(aMsgKey);
 
   // is there a selection?
   // if not, bail out.
   var indicies = GetSelectedIndices(gDBView);
   if (!indicies || !indicies.length)
     return false;
 
@@ -121,17 +122,17 @@ function ScrollToMessageAfterFolderLoad(
       scrolled = SelectAndScrollToKey(lastMessageLoaded);
   }
 
   if (!scrolled)
   {
     // if we still haven't scrolled,
     // scroll to the newest, which might be the top or the bottom
     // depending on our sort order and sort type
-    if (gDBView.sortOrder == nsMsgViewSortOrder.ascending)
+    if (gDBView && gDBView.sortOrder == nsMsgViewSortOrder.ascending)
     {
       switch (gDBView.sortType)
       {
         case nsMsgViewSortType.byDate:
         case nsMsgViewSortType.byReceived:
         case nsMsgViewSortType.byId:
         case nsMsgViewSortType.byThread:
          scrolled = ScrollToMessage(nsMsgNavigationType.lastMessage, true, false /* selectMessage */);
@@ -142,37 +143,58 @@ function ScrollToMessageAfterFolderLoad(
     // if still we haven't scrolled,
     // scroll to the top.
     if (!scrolled)
       EnsureRowInThreadTreeIsVisible(0);
   }
 }
 
 // the folderListener object
-var folderListener = {
-    OnItemAdded: function(parentItem, item) { },
+var folderListener =
+{
+  OnItemAdded:   function(parentItem, item) {},
+  OnItemRemoved: function(parentItem, item) {},
 
-    OnItemRemoved: function(parentItem, item) { },
-
-    OnItemPropertyChanged: function(item, property, oldValue, newValue) { },
+  OnItemPropertyChanged:        function(item, property, oldValue, newValue) {},
+  OnItemBoolPropertyChanged:    function(item, property, oldValue, newValue) {},
+  OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) {},
+  OnItemPropertyFlagChanged:    function(item, property, oldFlag,  newFlag)  {},
 
-    OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {
-      if (item == gMsgFolderSelected) {
-        if(property.toString() == "TotalMessages" || property.toString() == "TotalUnreadMessages") {
-          UpdateStatusMessageCounts(gMsgFolderSelected);
-          item = item.QueryInterface(Components.interfaces.nsIRDFResource);
-          UpdateLocationBar(item);
+  OnItemIntPropertyChanged: function(item, property, oldValue, newValue)
+  {
+    // handle the currently visible folder
+    if (item == gMsgFolderSelected)
+    {
+      let prop = property.toString();
+      if (prop == "TotalMessages" || prop == "TotalUnreadMessages")
+      {
+        UpdateStatusMessageCounts(gMsgFolderSelected);
+        item = item.QueryInterface(Components.interfaces.nsIRDFResource);
+        UpdateLocationBar(item);
+      }
+    }
+
+    // check folders shown in tabs
+    if (item instanceof Components.interfaces.nsIMsgFolder)
+    {
+      // find corresponding tabinfos
+      // we may have the folder openened in more than one tab
+      let tabmail = GetTabMail();
+      for (let i = 0; i < tabmail.tabInfo.length; ++i)
+      {
+        // if we never switched away from the tab, we only have just one
+        let tabFolder = tabmail.tabInfo[i].msgSelectedFolder || gMsgFolderSelected;
+        if (tabFolder == item)
+        {
+          // update tab title incl. any icon styles
+          tabmail.setTabTitle(tabmail.tabInfo[i]);
         }
       }
-    },
-
-    OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) { },
-
-    OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) { },
-    OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) { },
+    }
+  },
 
     OnItemEvent: function(folder, event) {
       var eventType = event.toString();
       if (eventType == "FolderLoaded") {
         if (folder) {
           const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
           var scrolled = false;
           var msgFolder = folder.QueryInterface(Components.interfaces.nsIMsgFolder);
@@ -229,17 +251,18 @@ var folderListener = {
             // gSearchNotificationListener.OnSearchDone (see searchBar.js).
             if (!scrolled && !(gMsgFolderSelected.flags & nsMsgFolderFlags.Virtual))
               ScrollToMessageAfterFolderLoad(msgFolder);
             SetBusyCursor(window, false);
           }
           // Folder loading is over,
           // now issue quick search if there is an email address.
           viewDebug("in folder loaded gVirtualFolderTerms = " + gVirtualFolderTerms + "\n");
-          viewDebug("in folder loaded gMsgFolderSelected = " + gMsgFolderSelected.URI + "\n");
+          viewDebug("in folder loaded gMsgFolderSelected = " +
+                    gMsgFolderSelected && gMsgFolderSelected.URI + "\n");
           if (rerootingFolder)
           {
             if (gSearchEmailAddress)
             {
               Search(gSearchEmailAddress);
               gSearchEmailAddress = null;
             }
             else if (gVirtualFolderTerms)
@@ -633,35 +656,36 @@ var gThreePaneIncomingServerListener = {
           // we've made a new selection, we're done
           return;
         }
       }
     }
 }
 
 function UpdateMailPaneConfig() {
-  const dynamicIds = ["messagesBox", "mailContent", "threadPaneBox"];
+  const dynamicIds = ["messagesBox", "mailContent", "messengerBox"];
   var desiredId = dynamicIds[pref.getIntPref("mail.pane_config.dynamic")];
   var messagePane = GetMessagePane();
   if (messagePane.parentNode.id != desiredId) {
     ClearAttachmentList();
     var messagePaneSplitter = GetThreadAndMessagePaneSplitter();
     var desiredParent = document.getElementById(desiredId);
     // See Bug 381992. The ctor for the browser element will fire again when we
     // re-insert the messagePaneBox back into the document.
     // But the dtor doesn't fire when the element is removed from the document.
     // Manually call destroy here to avoid a nasty leak.
     getMessageBrowser().destroy();
     desiredParent.appendChild(messagePaneSplitter);
     desiredParent.appendChild(messagePane);
     messagePaneSplitter.orient = desiredParent.orient;
-    messenger.setWindow(null, null);
-    messenger.setWindow(window, msgWindow);
-    if (gDBView && GetNumSelectedMessages() == 1)
-      gDBView.reloadMessage();
+    // Reroot message display
+    InvalidateTabDBs();
+    let tabmail = GetTabMail();
+    tabmail.currentTabInfo = null;
+    tabmail.updateCurrentTab();
   }
 }
 
 const MailPaneConfigObserver = {
   observe: function observe(subject, topic, prefName) {
     // verify that we're changing the mail pane config pref
     if (topic == "nsPref:changed")
       UpdateMailPaneConfig();
@@ -673,16 +697,22 @@ function OnLoadMessenger()
 {
   AddMailOfflineObserver();
   CreateMailWindowGlobals();
   pref.addObserver("mail.pane_config.dynamic", MailPaneConfigObserver, false);
   UpdateMailPaneConfig();
   Create3PaneGlobals();
   verifyAccounts(null, false);
 
+  // initialize tabmail system - see tabmail.js and tabmail.xml for details
+  let tabmail = GetTabMail();
+  tabmail.registerTabType(gMailNewsTabsType);
+  tabmail.openFirstTab();
+  window.tryToClose = MailWindowIsClosing;
+
   InitMsgWindow();
   messenger.setWindow(window, msgWindow);
 
   InitializeDataSources();
   InitPanes();
 
   MigrateJunkMailSettings();
 
@@ -742,22 +772,56 @@ function OnLoadMessenger()
 }
 
 function OnUnloadMessenger()
 {
   pref.removeObserver("mail.pane_config.dynamic", MailPaneConfigObserver, false);
 
   OnLeavingFolder(gMsgFolderSelected);  // mark all read in current folder
   accountManager.removeIncomingServerListener(gThreePaneIncomingServerListener);
+  GetTabMail().closeTabs();
+
   // FIX ME - later we will be able to use onload from the overlay
   OnUnloadMsgHeaderPane();
+  UnloadPanes();
+  OnMailWindowUnload();
+}
 
-  UnloadPanes();
-
-  OnMailWindowUnload();
+// we probably want to warn if more than one tab is closed
+function MailWindowIsClosing()
+{
+  let reallyClose = true;
+  let numtabs = GetTabMail().tabInfo.length;
+  if (numtabs > 1)
+  {
+    let shouldPrompt = pref.getBoolPref("browser.tabs.warnOnClose");
+    if (shouldPrompt)
+    {
+      let promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                                    .getService(Components.interfaces.nsIPromptService);
+      // default to true: if it were false, we wouldn't get this far
+      let warnOnClose = {value: true};
+      let buttonPressed = promptService.confirmEx(
+        window,
+        gMessengerBundle.getString('tabs.closeWarningTitle'),
+        gMessengerBundle.getFormattedString("tabs.closeWarning", [numtabs], 1),
+        (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0) +
+          (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
+        gMessengerBundle.getString('tabs.closeButton'),
+        null,
+        null,
+        gMessengerBundle.getString('tabs.closeWarningPromptMe'),
+        warnOnClose);
+      reallyClose = (buttonPressed == 0);
+      // don't set the pref unless OK was pressed and it's false
+      if (reallyClose && !warnOnClose.value)
+        pref.setBoolPref("browser.tabs.warnOnClose", false);
+    }
+  }
+  return reallyClose;
 }
 
 function NotifyObservers(aSubject, aTopic, aData)
 {
   Components.classes["@mozilla.org/observer-service;1"]
             .getService(Components.interfaces.nsIObserverService)
             .notifyObservers(aSubject, aTopic, aData);
 }
@@ -778,17 +842,16 @@ function loadStartFolder(initialUri)
     var isLoginAtStartUpEnabled = false;
 
     //First get default account
     try
     {
         if (!initialUri)
         {
             // Startup time.
-
             defaultServer = accountManager.defaultAccount.incomingServer;
 
             // set the initialUri to the server, so we select it
             // so we'll get account central
             initialUri = defaultServer.serverURI;
 
             // Enable check new mail once by turning checkmail pref 'on' to bring
             // all users to one plane. This allows all users to go to Inbox. User can
@@ -1057,23 +1120,16 @@ function GetFolderTree()
 
 function GetSearchInput()
 {
   if (!gSearchInput)
     gSearchInput = document.getElementById("searchInput");
   return gSearchInput;
 }
 
-function GetMessagePane()
-{
-  if (!gMessagePane)
-    gMessagePane = document.getElementById("messagepanebox");
-  return gMessagePane;
-}
-
 function GetMessagePaneFrame()
 {
   return window.content;
 }
 
 function FindInSidebar(currentWindow, id)
 {
   var item = currentWindow.document.getElementById(id);
@@ -1085,47 +1141,30 @@ function FindInSidebar(currentWindow, id
     var frameItem = FindInSidebar(currentWindow.frames[i], id);
     if (frameItem)
       return frameItem;
   }
 
   return null;
 }
 
-function GetThreadAndMessagePaneSplitter()
-{
-  if (!gThreadAndMessagePaneSplitter)
-    gThreadAndMessagePaneSplitter = document.getElementById('threadpane-splitter');
-  return gThreadAndMessagePaneSplitter;
-}
-
 function GetUnreadCountElement()
 {
   if (!gUnreadCount)
     gUnreadCount = document.getElementById('unreadMessageCount');
   return gUnreadCount;
 }
 
 function GetTotalCountElement()
 {
   if (!gTotalCount)
     gTotalCount = document.getElementById('totalMessageCount');
   return gTotalCount;
 }
 
-function IsMessagePaneCollapsed()
-{
-  return GetMessagePane().collapsed;
-}
-
-function IsFolderPaneCollapsed()
-{
-  return GetFolderTree().parentNode.collapsed;
-}
-
 function FindMessenger()
 {
   return messenger;
 }
 
 function ClearThreadPaneSelection()
 {
   try {
@@ -1138,22 +1177,22 @@ function ClearThreadPaneSelection()
   }
   catch (ex) {
     dump("ClearThreadPaneSelection: ex = " + ex + "\n");
   }
 }
 
 function ClearMessagePane()
 {
-  if(gHaveLoadedMessage)
+  if (gHaveLoadedMessage)
   {
     gHaveLoadedMessage = false;
     gCurrentDisplayedMessage = null;
     if (GetMessagePaneFrame().location.href != "about:blank")
-        GetMessagePaneFrame().location.href = "about:blank";
+      GetMessagePaneFrame().location.href = "about:blank";
 
     // hide the message header view AND the message pane...
     HideMessageHeaderPane();
     gMessageNotificationBar.clearMsgNotifications();
     ClearPendingReadTimer();
   }
 }
 
@@ -1171,88 +1210,107 @@ function GetSelectedFolderIndex()
 function ChangeSelectionWithoutContentLoad(event, tree)
 {
     var treeBoxObj = tree.treeBoxObject;
     var treeSelection = tree.view.selection;
 
     var row = treeBoxObj.getRowAt(event.clientX, event.clientY);
     // make sure that row.value is valid so that it doesn't mess up
     // the call to ensureRowIsVisible().
-    if((row >= 0) && !treeSelection.isSelected(row))
+    if ((row >= 0) && !treeSelection.isSelected(row))
     {
         var saveCurrentIndex = treeSelection.currentIndex;
         treeSelection.selectEventsSuppressed = true;
         treeSelection.select(row);
         treeSelection.currentIndex = saveCurrentIndex;
         treeBoxObj.ensureRowIsVisible(row);
         treeSelection.selectEventsSuppressed = false;
 
         // Keep track of which row in the thread pane is currently selected.
-        if(tree.id == "threadTree")
+        if (tree.id == "threadTree")
           gThreadPaneCurrentSelectedIndex = row;
     }
     event.stopPropagation();
 }
 
 function TreeOnMouseDown(event)
 {
-    // Detect right mouse click and change the highlight to the row
-    // where the click happened without loading the message headers in
-    // the Folder or Thread Pane.
-    if (event.button == 2)
-    {
-      gRightMouseButtonDown = true;
-      ChangeSelectionWithoutContentLoad(event, event.target.parentNode);
-    }
-    else
-      gRightMouseButtonDown = false;
+  // Detect right mouse click and change the highlight to the row
+  // where the click happened without loading the message headers in
+  // the Folder or Thread Pane.
+  // Same for middle click, which will open the folder/message in a tab.
+  gRightMouseButtonDown = event.button == kMouseButtonRight;
+  if (!gRightMouseButtonDown)
+    gRightMouseButtonDown = AllowOpenTabOnMiddleClick() &&
+                            event.button == kMouseButtonMiddle;
+  if (gRightMouseButtonDown)
+    ChangeSelectionWithoutContentLoad(event, event.target.parentNode);
 }
 
 function FolderPaneOnClick(event)
 {
-    // we only care about button 0 (left click) events
-    if (event.button != 0)
-        return;
+  // we may want to open the folder in a new tab on middle click
+  if (event.button == kMouseButtonMiddle)
+  {
+    if (AllowOpenTabOnMiddleClick())
+    {
+      MsgOpenNewTabForFolder();
+      RestoreSelectionWithoutContentLoad(GetFolderTree());
+      return;
+    }
+  }
 
-    var folderTree = GetFolderTree();
-    var row = {};
-    var col = {};
-    var elt = {};
-    folderTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, elt);
-    if (row.value == -1) {
-      if (event.originalTarget.localName == "treecol")
-      {
-        // clicking on the name column in the folder pane should not sort
-        event.stopPropagation();
-      }
-    }
-    else if ((event.originalTarget.localName == "slider") ||
-             (event.originalTarget.localName == "scrollbarbutton")) {
+  // otherwise, we only care about left click events
+  if (event.button != kMouseButtonLeft)
+    return;
+
+  var folderTree = GetFolderTree();
+  var row = {}, col = {}, elt = {};
+  folderTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, elt);
+  if (row.value == -1)
+  {
+    if (event.originalTarget.localName == "treecol")
+    {
+      // clicking on the name column in the folder pane should not sort
       event.stopPropagation();
     }
-    else if ((event.detail == 2) && (elt.value != "twisty") &&
-             (folderTree.view.getLevel(row.value) != 0)) {
-      FolderPaneDoubleClick(row.value, event);
-    }
+  }
+  else if ((event.originalTarget.localName == "slider") ||
+           (event.originalTarget.localName == "scrollbarbutton"))
+  {
+    event.stopPropagation();
+  }
+  else if ((event.detail == 2) &&
+           (elt.value != "twisty") &&
+           (folderTree.view.getLevel(row.value) != 0))
+  {
+    FolderPaneDoubleClick(row.value, event);
+  }
 }
 
 function FolderPaneDoubleClick(folderIndex, event)
 {
-    if (!pref.getBoolPref("mailnews.reuse_thread_window2"))
-    {
-      var folderResource = GetFolderResource(GetFolderTree(), folderIndex);
-      // Open a new msg window only if we are double clicking on
-      // folders or newsgroups.
-      MsgOpenNewWindowForFolder(folderResource.Value, -1 /* key */);
+  // In tabmail land, lazyness is dead!
+  // We either open the folder in a tab or a new window...
+  if (AllowOpenTabOnDoubleClick())
+  {
+    MsgOpenNewTabForFolder();
+  }
+  else
+  {
+    let folderResource = GetFolderResource(GetFolderTree(), folderIndex);
+    // Open a new msg window only if we are double clicking on
+    // folders or newsgroups.
+    MsgOpenNewWindowForFolder(folderResource.Value, nsMsgKey_None);
+  }
 
-      // double clicking should not toggle the open / close state of the
-      // folder.  this will happen if we don't prevent the event from
-      // bubbling to the default handler in tree.xml
-      event.stopPropagation();
-    }
+  // Double-clicking should not toggle the open/close state of the folder.
+  // This will happen if we don't prevent the event from bubbling to the
+  // default handler in tree.xml.
+  event.stopPropagation();
 }
 
 function ChangeSelection(tree, newIndex)
 {
     if(newIndex >= 0)
     {
         tree.view.selection.select(newIndex);
         tree.treeBoxObject.ensureRowIsVisible(newIndex);
--- a/suite/mailnews/msgViewNavigation.js
+++ b/suite/mailnews/msgViewNavigation.js
@@ -1,10 +1,10 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
--- a/suite/mailnews/msgViewPickerOverlay.js
+++ b/suite/mailnews/msgViewPickerOverlay.js
@@ -1,10 +1,10 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -139,17 +139,17 @@ function ViewChangeByFolder(aFolder)
 {
   var result = GetMailViewForFolder(aFolder);
   ViewChangeByValue(result);
 }
 
 function GetLabelForValue(aValue)
 {
   var label = "";
-  let viewPickerPopup = document.getElementById("viewPickerPopup");
+  var viewPickerPopup = document.getElementById("viewPickerPopup");
   if (viewPickerPopup)
   {
     // grab the label for the menulist from one of its menuitems
     var selectedItems = viewPickerPopup.getElementsByAttribute("value", aValue);
     if (!selectedItems || !selectedItems.length)
     {
       // we may have a new item
       RefreshAllViewPopups(viewPickerPopup, true);
@@ -172,22 +172,24 @@ function UpdateViewPicker(aValue, aLabel
   {
     viewPicker.value = aValue;
     viewPicker.setAttribute("label", aLabel);
   }
 }
 
 function GetFolderInfo(aFolder)
 {
-  if (aFolder)
+  // accounts may not have a msgDatabase, eg. Movemail or RSS
+  try
   {
     var db = aFolder.msgDatabase;
     if (db)
       return db.dBFolderInfo;
   }
+  catch (ex) {}
   return null;
 }
 
 
 function GetMailViewForFolder(aFolder)
 {
   var val = "";
   var folderInfo = GetFolderInfo(aFolder);
--- a/suite/mailnews/search/SearchDialog.js
+++ b/suite/mailnews/search/SearchDialog.js
@@ -1,10 +1,10 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
--- a/suite/mailnews/search/SearchDialog.xul
+++ b/suite/mailnews/search/SearchDialog.xul
@@ -73,16 +73,18 @@
   <script type="application/javascript"
           src="chrome://messenger/content/mailWindowOverlay.js"/>
   <script type="application/javascript"
           src="chrome://messenger/content/commandglue.js"/>
   <script type="application/javascript"
           src="chrome://messenger/content/SearchDialog.js"/>
   <script type="application/javascript"
           src="chrome://messenger/content/messengerdnd.js"/>
+  <script type="application/javascript"
+          src="chrome://messenger/content/tabmail.js"/>
 
   <commands id="commands">
     <commandset id="mailSearchItems"
                 commandupdater="true"
                 events="mail-search"
                 oncommandupdate="goUpdateSearchItems(this)">
       <command id="cmd_open" oncommand="goDoCommand('cmd_open')" disabled="true"/>
       <command id="button_delete" oncommand="goDoCommand('button_delete')" disabled="true"/>
--- a/suite/mailnews/searchBar.js
+++ b/suite/mailnews/searchBar.js
@@ -1,10 +1,10 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -91,17 +91,17 @@ var gSearchNotificationListener =
          (!gSearchInput || gSearchInput.value == "" || gSearchInput.showingSearchCriteria))
         {
           var vFolder = GetMsgFolderFromUri(gCurrentVirtualFolderUri, false);
           var dbFolderInfo = vFolder.msgDatabase.dBFolderInfo;
           dbFolderInfo.numUnreadMessages = gNumUnreadMessages;
           dbFolderInfo.numMessages = gNumTotalMessages;
           vFolder.updateSummaryTotals(true); // force update from db.
           var msgdb = vFolder.msgDatabase;
-          msgdb.Commit(MSG_DB_LARGE_COMMIT);
+          msgdb.Commit(Components.interfaces.nsMsgDBCommitType.kLargeCommit);
           // now that we have finished loading a virtual folder,
           // scroll to the correct message if there is at least one.
           if (vFolder.getTotalMessages(false) > 0)
             ScrollToMessageAfterFolderLoad(vFolder);
         }
     },
 
     onNewSearch: function()
@@ -188,19 +188,19 @@ function initializeSearchBar()
 
 function onEnterInSearchBar()
 {
    if (!gSearchBundle)
      getDocumentElements();
    viewDebug ("onEnterInSearchBar gSearchInput.value = " /* + gSearchInput.value + " showing criteria = " + gSearchInput.showingSearchCriteria */ +"\n");
    if (gSearchInput.value == ""  /* || gSearchInput.showingSearchCriteria */) 
    {
-     
-     if (gDBView.viewType == nsMsgViewType.eShowQuickSearchResults 
-        || gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
+    let viewType = gDBView && gDBView.viewType;
+    if (viewType == nsMsgViewType.eShowQuickSearchResults ||
+        viewType == nsMsgViewType.eShowVirtualFolderResults)
      {
        statusFeedback.showStatusString("");
        disableQuickSearchClearButton();
 
        viewDebug ("onEnterInSearchBar gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = " 
         + gVirtualFolderTerms + "gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
        var addTerms = gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms;
        if (addTerms)
new file mode 100644
--- /dev/null
+++ b/suite/mailnews/tabmail.js
@@ -0,0 +1,1025 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is SeaMonkey Tabmail code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Karsten Düsterloh <mnromyr@tprac.de>
+ * based upon the tabmail work by
+ *   David Bienvenu <bienvenu@nventure.com>.
+ *   Scott MacGregor <mscott@mozilla.org>
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ * and other code by various artists from all over the place in mailnews.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// common pref object for all windows using tabmail
+// alas, it's a legacy name without a 'g' prefix
+var pref = null;
+
+function GetPrefService()
+{
+  if (!pref)
+    pref = Components.classes["@mozilla.org/preferences-service;1"]
+                     .getService(Components.interfaces.nsIPrefBranch2);
+  return pref;
+}
+
+// Traditionally, mailnews tabs come in two flavours: "folder" and
+// "message" tabs. But these modes are just mere default settings on tab
+// creation, defining layout, URI to load, etc.
+// The user can turn a "message" tab into a "folder" tab just by unhiding
+// the folder pane (F9) and hiding the message pane (F8), and vice versa.
+// Tab title and icon will change accordingly.
+// Both flavours are just instances of the basic "3pane" mode, triggered by
+// a bitwise or combination of these possible pane values:
+const kTabShowNoPane      = 0;
+const kTabShowFolderPane  = 1 << 0;
+const kTabShowMessagePane = 1 << 1;
+const kTabShowThreadPane  = 1 << 2;
+const kTabShowAcctCentral = 1 << 3;
+// predefined mode masks
+const kTabMaskDisplayDeck = kTabShowThreadPane | kTabShowAcctCentral;
+// predefined traditional flavours
+const kTabModeFolder      = kTabShowFolderPane | kTabShowThreadPane | kTabShowMessagePane;
+const kTabModeMessage     = kTabShowMessagePane;  // message tab
+
+
+// global mailnews tab definition object
+var gMailNewsTabsType =
+{
+  name: "mailnews",
+  panelId: "mailContent",
+
+  modes:
+  {
+    "3pane":
+    {
+      isDefault: true,
+      type: "3pane",
+
+      // aTabInfo belongs to the newly created tab,
+      // aModeBits is a combination of kTabShow* layout bits (or null),
+      // aFolderURI designates the folder to select (or null)
+      // aMsgHdr designates the message to select (or null)
+      openTab: function(aTabInfo, aModeBits, aFolderURI, aMsgHdr)
+      {
+        // clone the current 3pane state before overriding parts of it
+        this.saveTabState(aTabInfo);
+
+        // aModeBits must have at least one bit set
+        // if not, we just copy the current state
+        let cloneMode = !aModeBits;
+        if (cloneMode)
+          aModeBits = this.getCurrentModeBits() || kTabModeFolder;
+        aTabInfo.modeBits = aModeBits;
+        // Currently, we only check for kTabModeMessage vs. kTabModeFolder,
+        // but in theory we could distinguish in much more detail!
+        let messageId = null;
+        if (aModeBits == kTabModeMessage || cloneMode)
+        {
+          if (!aMsgHdr && gDBView)
+          {
+            try
+            {
+              // duplicate current message tab if nothing else is specified
+              aMsgHdr = gDBView.hdrForFirstSelectedMessage;
+              // Use the header's folder - this will open a msg in a virtual folder view
+              // in its real folder, which is needed if the msg wouldn't be in a new
+              // view with the same terms - e.g., it's read and the view is unread only.
+              // If we cloned the view, we wouldn't have to do this.
+              if (aTabInfo.switchToNewTab)
+              {
+                // Fix it so we won't try to load the previously loaded message.
+                aMsgHdr.folder.lastMessageLoaded = nsMsgKey_None;
+              }
+              aFolderURI = aMsgHdr.folder.URI;
+            }
+            catch (ex) {}
+          }
+          if (aMsgHdr)
+            messageId = aMsgHdr.messageId;
+          aTabInfo.clearSplitter = true;
+        }
+
+        if (!messageId)
+        {
+          // only sanitize the URL, if possible
+          let clearSplitter = aModeBits == kTabModeFolder;
+          if (!aFolderURI)
+          {
+            // Use GetSelectedMsgFolders() to find out which folder to open
+            // instead of GetLoadedMsgFolder().URI. This is required because on a
+            // right-click, the currentIndex value will be different from the
+            // actual row that is highlighted. GetSelectedMsgFolders() will
+            // return the message that is highlighted.
+            let msgFolder = GetSelectedMsgFolders()[0];
+            aFolderURI = msgFolder.URI;
+            // don't kill the splitter settings for account central
+            clearSplitter &= !msgFolder.isServer;
+          }
+          aMsgHdr = null;
+          aTabInfo.clearSplitter = clearSplitter;
+        }
+        aTabInfo.uriToOpen = aFolderURI;
+        aTabInfo.hdr = aMsgHdr;
+        aTabInfo.selectedMsgId = messageId;
+
+        // call superclass logic
+        this.openTab(aTabInfo);
+      },
+
+      // We can close all mailnews tabs - but one.
+      // Closing the last mailnews tab would destroy our mailnews functionality.
+      canCloseTab: function(aTabInfo)
+      {
+        return aTabInfo.mode.tabs.length > 1;
+      }
+    }
+  },
+
+  // combines the current pane visibility states into a mode bit mask
+  getCurrentModeBits: function()
+  {
+    let modeBits = kTabShowNoPane;
+    if (!IsFolderPaneCollapsed())
+      modeBits |= kTabShowFolderPane;
+    if (!IsDisplayDeckCollapsed())
+    {
+      // currently, the display deck has only two panes
+      if (gAccountCentralLoaded)
+        modeBits |= kTabShowAcctCentral;
+      else
+        modeBits |= kTabShowThreadPane;
+    }
+    if (!IsMessagePaneCollapsed())
+      modeBits |= kTabShowMessagePane;
+    return modeBits;
+  },
+
+  _updatePaneLayout: function(aTabInfo)
+  {
+    // first show all needed panes, then hide all unwanted ones
+    // (we have to keep this order to avoid hiding all panes!)
+    let showFolderPane  = aTabInfo.modeBits & kTabShowFolderPane;
+    let showMessagePane = aTabInfo.modeBits & kTabShowMessagePane;
+    let showDisplayDeck = aTabInfo.modeBits & (kTabShowThreadPane | kTabShowAcctCentral);
+    if (showMessagePane && IsMessagePaneCollapsed())
+      MsgToggleMessagePane(true); // show message pane
+    if (showDisplayDeck && IsDisplayDeckCollapsed())
+      MsgToggleThreadPane();      // show thread pane
+    if (showFolderPane && IsFolderPaneCollapsed())
+      MsgToggleFolderPane(true);  // show folder pane
+    if (!showMessagePane && !IsMessagePaneCollapsed())
+      MsgToggleMessagePane(true); // hide message pane
+    if (!showDisplayDeck && !IsDisplayDeckCollapsed())
+      MsgToggleThreadPane();      // hide thread pane
+    if (!showFolderPane && !IsFolderPaneCollapsed())
+      MsgToggleFolderPane(true);  // hide folder pane
+    UpdateLayoutVisibility();
+  },
+
+  /**
+   * Create the new tab's state, which engenders some side effects.
+   * Part of our contract is that we leave the tab in the selected state.
+   */
+  openTab: function(aTabInfo)
+  {
+    // each tab gets its own messenger instance
+    // for undo/redo, backwards/forwards, etc.
+    messenger = Components.classes["@mozilla.org/messenger;1"]
+                          .createInstance(Components.interfaces.nsIMessenger);
+    messenger.setWindow(window, msgWindow);
+    aTabInfo.messenger = messenger;
+
+    // remember the currently selected folder
+    aTabInfo.msgSelectedFolder = gMsgFolderSelected;
+
+    // show tab if permitted
+    if (aTabInfo.switchToNewTab)
+      this.showTab(aTabInfo);
+  },
+
+  showTab: function(aTabInfo)
+  {
+    // don't allow saveTabState while restoring a tab
+    aTabInfo.lock = true;
+    // set the messagepane as the primary browser for content
+    getMessageBrowser().setAttribute("type", "content-primary");
+
+    if (aTabInfo.uriToOpen)
+    {
+      // 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 = GetFolderTree();
+      folderTree.view.selection.clearSelection();
+      folderTree.view.selection.currentIndex = -1;
+      gMsgFolderSelected = null;
+      msgWindow.openFolder = null;
+
+      // clear gDBView so we won't try to close it
+      gDBView = null;
+
+      // reroot the message sink (we might have switched layout)
+      messenger.setWindow(null, null);
+      messenger.setWindow(window, msgWindow);
+
+      // Clear thread pane selection - otherwise, the tree tries to impose the
+      // the current selection on the new view.
+      let msgHdr = aTabInfo.hdr;
+      let msgId  = aTabInfo.selectedMsgId;
+      SelectFolder(aTabInfo.uriToOpen);
+      let folderResource = RDF.GetResource(aTabInfo.uriToOpen);
+      if (folderResource instanceof Components.interfaces.nsIMsgFolder)
+        aTabInfo.msgSelectedFolder = folderResource;
+      delete aTabInfo.uriToOpen; // destroy after use!
+      // restore our message data
+      aTabInfo.hdr = msgHdr;
+      aTabInfo.selectedMsgId = msgId;
+
+      aTabInfo.dbView = gDBView;
+      UpdateMailToolbar("new tab");
+    }
+
+    // restore layout if present
+    ShowThreadPane();
+    // Some modes (e.g. new message tabs) need to initially hide the splitters,
+    // this is marked by aTabInfo.clearSplitter=true.
+    let clearSplitter = "clearSplitter" in aTabInfo && aTabInfo.clearSplitter;
+    if (clearSplitter)
+    {
+      aTabInfo.messageSplitter.collapsible = true;
+      aTabInfo.folderSplitter.collapsible  = true;
+      delete aTabInfo.clearSplitter;
+    }
+    SetSplitterState(GetThreadAndMessagePaneSplitter(), aTabInfo.messageSplitter);
+    SetSplitterState(GetFolderPaneSplitter(),           aTabInfo.folderSplitter);
+    this._updatePaneLayout(aTabInfo);
+    ClearMessagePane();
+    // force header pane twisty state restoration by toggling from the opposite
+    if (gCollapsedHeaderViewMode != aTabInfo.headerViewMode)
+      ToggleHeaderView();
+
+    // restore globals
+    messenger = aTabInfo.messenger;
+    gDBView = aTabInfo.dbView;
+    gSearchSession = aTabInfo.searchSession;
+    let folderToSelect = aTabInfo.msgSelectedFolder || gDBView && gDBView.msgFolder;
+
+    // restore view state if we had one
+    let folderTree = GetFolderTree();
+    let row = EnsureFolderIndex(folderTree.builderView, folderToSelect);
+    let treeBoxObj = folderTree.treeBoxObject;
+    let folderTreeSelection = treeBoxObj.view.selection;
+
+    // make sure that row.value is valid so that it doesn't mess up
+    // the call to ensureRowIsVisible()
+    if ((row >= 0) && !folderTreeSelection.isSelected(row))
+    {
+      gMsgFolderSelected = folderToSelect;
+      msgWindow.openFolder = folderToSelect;
+      folderTreeSelection.select(row);
+      treeBoxObj.ensureRowIsVisible(row);
+    }
+
+    if (gDBView)
+    {
+      // This sets the thread pane tree's view to the gDBView view.
+      UpdateSortIndicators(gDBView.sortType, gDBView.sortOrder);
+      RerootThreadPane();
+
+      // We don't want to reapply the mailview (threadpane changes by switching
+      // tabs alone would be rather surprising), just update the viewpicker
+      // and resave the new view.
+      UpdateViewPickerByValue(aTabInfo.mailView);
+      SetMailViewForFolder(folderToSelect, aTabInfo.mailView);
+
+      // restore quick search
+      GetSearchInput().value = aTabInfo.searchInput;
+
+      // We need to restore the selection to what it was when we switched away
+      // from this tab. We need to remember the selected keys, instead of the
+      // selected indices, since the view might have changed. But maybe the
+      // selectedIndices adjust as items are added/removed from the (hidden)
+      // view.
+      try
+      {
+        if (aTabInfo.selectedMsgId && aTabInfo.msgSelectedFolder)
+        {
+          // We clear the selection in order to generate an event when we
+          // re-select our message. This destroys aTabInfo.selectedMsgId.
+          let selectedMsgId = aTabInfo.selectedMsgId;
+          ClearThreadPaneSelection();
+          aTabInfo.selectedMsgId = selectedMsgId;
+          let msgDB = aTabInfo.msgSelectedFolder.msgDatabase;
+          let msgHdr = msgDB.getMsgHdrForMessageID(aTabInfo.selectedMsgId);
+          setTimeout(gDBView.selectFolderMsgByKey,
+                     0,
+                     aTabInfo.msgSelectedFolder,
+                     msgHdr.messageKey);
+        }
+        // We do not clear the selection if there was more than one message
+        // displayed.  this leaves our selection intact. there was originally
+        // some claim that the selection might lose synchronization with the
+        // view, but this is unsubstantiated.  said comment came from the
+        // original code that stored information on the selected rows, but
+        // then failed to do anything with it, probably because there is no
+        // existing API call that accomplishes it.
+      }
+      catch (ex)
+      {
+        dump(ex);
+      }
+      GetThreadTree().treeBoxObject.scrollToRow(aTabInfo.firstVisibleRow);
+    }
+    else if (gMsgFolderSelected.isServer)
+    {
+      // Load AccountCentral page here.
+      ShowAccountCentral();
+    }
+    UpdateLocationBar(gMsgFolderSelected);
+    UpdateMailToolbar("tab changed");
+    delete aTabInfo.lock;
+  },
+
+  closeTab: function(aTabInfo)
+  {
+    if (aTabInfo.dbView)
+      aTabInfo.dbView.close();
+    if (aTabInfo.messenger)
+      aTabInfo.messenger.setWindow(null, null);
+  },
+
+  // called when switching away from aTabInfo
+  saveTabState: function(aTabInfo)
+  {
+    if (aTabInfo.lock)
+      return;
+
+    // save message db data and view filters
+    aTabInfo.messenger = messenger;
+    aTabInfo.dbView = gDBView;
+    aTabInfo.searchSession = gSearchSession;
+    aTabInfo.msgSelectedFolder = gMsgFolderSelected;
+    aTabInfo.selectedMsgId = null;
+    if (gDBView)
+    {
+      // save thread pane scroll position
+      aTabInfo.firstVisibleRow = GetThreadTree().treeBoxObject.getFirstVisibleRow();
+
+      let curMsgViewIndex = gDBView.currentlyDisplayedMessage;
+      if (curMsgViewIndex != nsMsgViewIndex_None)
+      {
+        try // there may not be a selected message.
+        {
+          // the currentlyDisplayedMessage is not always the first selected
+          // message, e.g. on a right click for the context menu
+          let curMsgHdr = gDBView.getMsgHdrAt(curMsgViewIndex);
+          aTabInfo.selectedMsgId = curMsgHdr.messageId;
+        }
+        catch (ex) {}
+      }
+      if (!aTabInfo.selectedMsgId)
+        aTabInfo.msgSelectedFolder = gDBView.msgFolder;
+    }
+    aTabInfo.mailView = GetMailViewForFolder(aTabInfo.msgSelectedFolder);
+
+    // remember layout
+    aTabInfo.modeBits = this.getCurrentModeBits();
+    aTabInfo.messageSplitter = GetSplitterState(GetThreadAndMessagePaneSplitter());
+    aTabInfo.folderSplitter  = GetSplitterState(GetFolderPaneSplitter());
+
+    // header pane twisty state
+    aTabInfo.headerViewMode = gCollapsedHeaderViewMode;
+
+    // quick search
+    aTabInfo.searchInput = GetSearchInput().value;
+  },
+
+  onTitleChanged: function(aTabInfo, aTabNode)
+  {
+    // If we have an account, we also always have a "Local Folders" account,
+    let accountCount = Components.classes["@mozilla.org/messenger/account-manager;1"]
+                                 .getService(Components.interfaces.nsIMsgAccountManager)
+                                 .accounts.Count();
+    let multipleRealAccounts = accountCount > 2;
+
+    // clear out specific tab data now, because we might need to return early
+    aTabNode.removeAttribute("SpecialFolder");
+    aTabNode.removeAttribute("ServerType");
+    aTabNode.removeAttribute("IsServer");
+    aTabNode.removeAttribute("IsSecure");
+    aTabNode.removeAttribute("NewMessages");
+    aTabNode.removeAttribute("ImapShared");
+    aTabNode.removeAttribute("BiffState");
+    aTabNode.removeAttribute("MessageType");
+    aTabNode.removeAttribute("Offline");
+    aTabNode.removeAttribute("Attachment");
+    aTabNode.removeAttribute("IMAPDeleted");
+
+    // aTabInfo.msgSelectedFolder may contain the base folder of saved search
+    let msgSelectedFolder = null;
+    if (aTabInfo.uriToOpen)
+    {
+      // select folder for the backgound tab without changing the current one
+      // (stolen from SelectFolder)
+      let folderResource = RDF.GetResource(aTabInfo.uriToOpen);
+      if (folderResource instanceof Components.interfaces.nsIMsgFolder)
+        msgSelectedFolder = folderResource;
+    }
+    else
+    {
+      msgSelectedFolder = (aTabInfo.dbView && aTabInfo.dbView.viewFolder) ||
+                          (aTabInfo.dbView && aTabInfo.dbView.msgFolder) ||
+                          aTabInfo.msgSelectedFolder ||
+                          gMsgFolderSelected;
+    }
+
+    // update the message header only if we're the current tab
+    if (aTabNode.selected)
+    {
+      try
+      {
+        aTabInfo.hdr = aTabInfo.dbView && aTabInfo.dbView.hdrForFirstSelectedMessage;
+      }
+      catch (e)
+      {
+        aTabInfo.hdr = null;
+      }
+    }
+
+    // update tab title and icon state
+    aTabInfo.title = "";
+    if (IsMessagePaneCollapsed() || !aTabInfo.hdr)
+    {
+      // Folder Tab
+      aTabNode.setAttribute("type", "folder"); // override "3pane"
+      if (!msgSelectedFolder)
+      {
+        // nothing to do
+        return;
+      }
+      else
+      {
+        aTabInfo.title = msgSelectedFolder.prettyName;
+        if (!msgSelectedFolder.isServer && multipleRealAccounts)
+          aTabInfo.title += " - " + msgSelectedFolder.server.prettyName;
+      }
+
+      // The user may have changed folders, triggering our onTitleChanged callback.
+      // Update the appropriate attributes on the tab.
+      aTabNode.setAttribute("SpecialFolder", getSpecialFolderString(msgSelectedFolder));
+      aTabNode.setAttribute("ServerType",    msgSelectedFolder.server.type);
+      aTabNode.setAttribute("IsServer",      msgSelectedFolder.isServer);
+      aTabNode.setAttribute("IsSecure",      msgSelectedFolder.server.isSecure);
+      aTabNode.setAttribute("NewMessages",   msgSelectedFolder.hasNewMessages);
+      aTabNode.setAttribute("ImapShared",    msgSelectedFolder.imapShared);
+
+      let biffState = "UnknownMail";
+      switch (msgSelectedFolder.biffState)
+      {
+        case Components.interfaces.nsIMsgFolder.nsMsgBiffState_NewMail:
+          biffState = "NewMail";
+          break;
+        case Components.interfaces.nsIMsgFolder.nsMsgBiffState_NoMail:
+          biffState = "NoMail";
+          break;
+      }
+      aTabNode.setAttribute("BiffState", biffState);
+    }
+    else
+    {
+      // Message Tab
+      aTabNode.setAttribute("type", "message"); // override "3pane"
+      if (aTabInfo.hdr.flags & Components.interfaces.nsMsgMessageFlags.HasRe)
+        aTabInfo.title = "Re: ";
+      if (aTabInfo.hdr.mime2DecodedSubject)
+        aTabInfo.title += aTabInfo.hdr.mime2DecodedSubject;
+      aTabInfo.title += " - " + aTabInfo.hdr.folder.prettyName;
+      if (multipleRealAccounts)
+        aTabInfo.title += " - " + aTabInfo.hdr.folder.server.prettyName;
+
+      // message specific tab data
+      const nsMsgMessageFlags = Components.interfaces.nsMsgMessageFlags;
+      let flags = aTabInfo.hdr.flags;
+      aTabNode.setAttribute("MessageType", msgSelectedFolder.server.type);
+      aTabNode.setAttribute("Offline",     Boolean(flags & nsMsgMessageFlags.Offline));
+      aTabNode.setAttribute("Attachment",  Boolean(flags & nsMsgMessageFlags.Attachment));
+      aTabNode.setAttribute("IMAPDeleted", Boolean(flags & nsMsgMessageFlags.IMAPDeleted));
+    }
+  },
+
+  getBrowser: function(aTabInfo)
+  {
+    // we currently use the messagepane element for all 3pane tab types
+    return getMessageBrowser();
+  },
+
+  //
+  // nsIController implementation
+  //
+  // We ignore the aTabInfo parameter sent by tabmail when calling nsIController
+  // stuff and just delegate the call to the DefaultController by using it as
+  // our proto chain.
+  // XXX remove the MessageWindowController stuff once we kill messageWindow.xul
+  __proto__: "DefaultController" in window && window.DefaultController ||
+             "MessageWindowController" in window && window.MessageWindowController
+};
+
+
+
+//
+//  tabmail support methods
+//
+
+function GetTabMail()
+{
+  return document.getElementById("tabmail");
+}
+
+function MsgOpenNewTab(aType, aModeBits)
+{
+  // duplicate the current tab
+  var tabmail = GetTabMail();
+  if (tabmail)
+    tabmail.openTab(aType, aModeBits);
+}
+
+function MsgOpenNewTabForFolder()
+{
+  // open current folder in full 3pane tab
+  MsgOpenNewTab("3pane", kTabModeFolder);
+}
+
+function MsgOpenNewTabForMessage()
+{
+  // open current message in message tab
+  MsgOpenNewTab("3pane", kTabModeMessage);
+}
+
+function MsgCloseCurrentTab()
+{
+  var tabmail = GetTabMail();
+  if (tabmail.tabInfo.length > 1)
+    tabmail.removeCurrentTab();
+  else
+    CloseMailWindow();
+}
+
+function AllowOpenTabOnMiddleClick()
+{
+  return GetPrefService().getBoolPref("browser.tabs.opentabfor.middleclick");
+}
+
+function AllowOpenTabOnDoubleClick()
+{
+  return GetPrefService().getBoolPref("browser.tabs.opentabfor.doubleclick");
+}
+
+//
+// pane management
+// (maybe we should cache these items in a global object?)
+//
+
+function GetFolderPane()
+{
+  return document.getElementById("folderPaneBox");
+}
+
+function GetThreadPane()
+{
+  return document.getElementById("threadPaneBox");
+}
+
+function GetDisplayDeck()
+{
+  return document.getElementById("displayDeck");
+}
+
+function GetMessagePane()
+{
+  return document.getElementById("messagepanebox");
+}
+
+function GetHeaderPane()
+{
+  return document.getElementById("msgHeaderView");
+}
+
+function GetFolderPaneSplitter()
+{
+  return document.getElementById("folderpane-splitter");
+}
+
+function GetThreadAndMessagePaneSplitter()
+{
+  return document.getElementById("threadpane-splitter");
+}
+
+
+
+//
+// pane visibility management
+//
+// - collapsing the folderpane by clicking its splitter doesn't need
+//   additional processing
+// - collapsing the messagepane by clicking its splitter needs some special
+//   treatment of attachments, gDBView, etc.
+// - the threadpane has no splitter assigned to it
+// - collapsing the messagepane, threadpane or folderpane by <key> needs to
+//   pay attention to the other panes' (and splitters') visibility
+
+function IsMessagePaneCollapsed()
+{
+  return GetMessagePane().collapsed;
+}
+
+function IsDisplayDeckCollapsed()
+{
+  // regard display deck as collapsed in the standalone message window
+  var displayDeck = GetDisplayDeck();
+  return !displayDeck || displayDeck.collapsed;
+}
+
+function IsFolderPaneCollapsed()
+{
+  // regard folderpane as collapsed in the standalone message window
+  var folderPane = GetFolderPane();
+  return !folderPane || folderPane.collapsed;
+}
+
+// Which state is the splitter in? Is it collapsed?
+// How wide/high is the associated pane?
+function GetSplitterState(aSplitter)
+{
+  var next = aSplitter.getAttribute("collapse") == "after";
+  var pane = next ? aSplitter.nextSibling : aSplitter.previousSibling;
+  var vertical = aSplitter.orient == "vertical";
+  var rv =
+  {
+    state:     aSplitter.getAttribute("state"),
+    collapsed: aSplitter.collapsed,
+    // <splitter>s are <hbox>es,
+    // thus the "orient" attribute is usually either unset or "vertical"
+    size:      vertical ? pane.height : pane.width,
+    collapsible: "collapsible" in aSplitter && aSplitter.collapsible
+  };
+  return rv;
+}
+
+function SetSplitterState(aSplitter, aState)
+{
+  // all settings in aState are optional
+  if (!aState)
+    return;
+  if ("state" in aState)
+    aSplitter.setAttribute("state", aState.state);
+  if ("collapsed" in aState)
+    aSplitter.collapsed = aState.collapsed;
+  if ("size" in aState)
+  {
+    let next = aSplitter.getAttribute("collapse") == "after";
+    let pane = next ? aSplitter.nextSibling : aSplitter.previousSibling;
+    let vertical = aSplitter.orient == "vertical";
+    if (vertical)
+    {
+      // vertical splitter orientation
+      pane.height = aState.size;
+    }
+    else
+    {
+      // horizontal splitter orientation
+      pane.width = aState.size;
+    }
+  }
+  if ("collapsible" in aState)
+    aSplitter.collapsible = aState.collapsible;
+}
+
+// If we hit one of the pane splitter <key>s or choose the respective menuitem,
+// we show/hide both the pane *and* the splitter, just like we do for the
+// browser sidebar. Clicking a splitter's grippy, though, will hide the pane
+// but not the splitter.
+function MsgToggleSplitter(aSplitter)
+{
+  var state = aSplitter.getAttribute("state");
+  if (state == "collapsed")
+  {
+    // removing the attribute would hurt persistency
+    aSplitter.setAttribute("state", "open");
+    aSplitter.collapsed = false; // always show splitter when open
+  }
+  else
+  {
+    aSplitter.setAttribute("state", "collapsed");
+    aSplitter.collapsed = true; // hide splitter
+  }
+}
+
+function MsgCollapseSplitter(aSplitter, aCollapse)
+{
+  if (!("collapsible" in aSplitter))
+    aSplitter.collapsible = true;
+  aSplitter.collapsed = aCollapse && aSplitter.collapsible;
+}
+
+// helper function for UpdateLayoutVisibility
+function UpdateFolderPaneFlex(aTuneLayout)
+{
+  var folderBox = GetFolderPane();
+  var messagesBox = document.getElementById("messagesBox");
+  if (aTuneLayout)
+  {
+    // tune folderpane layout
+    folderBox.setAttribute("flex", "1");
+    messagesBox.removeAttribute("flex");
+  }
+  else
+  {
+    // restore old layout
+    folderBox.removeAttribute("flex");
+    messagesBox.setAttribute("flex", "1");
+  }
+}
+
+// we need to finetune the pane and splitter layout in certain circumstances
+function UpdateLayoutVisibility()
+{
+  var modeBits = gMailNewsTabsType.getCurrentModeBits();
+  var folderPaneVisible  = modeBits & kTabShowFolderPane;
+  var messagePaneVisible = modeBits & kTabShowMessagePane;
+  var threadPaneVisible  = modeBits & kTabShowThreadPane;
+  var displayDeckVisible = modeBits & kTabMaskDisplayDeck;
+  var onlyFolderPane     = modeBits == kTabShowFolderPane;
+  var onlyMessagePane    = modeBits == kTabShowMessagePane;
+  var onlyDisplayDeck    = modeBits == kTabShowThreadPane ||
+                           modeBits == kTabShowAcctCentral;
+  var onlyOnePane = onlyFolderPane || onlyMessagePane || onlyDisplayDeck;
+  var showFolderSplitter  = false;
+  var showMessageSplitter = false;
+  var layout = pref.getIntPref("mail.pane_config.dynamic");
+  switch (layout)
+  {
+    case kClassicMailLayout:
+      // if only the folderpane is visible it has to flex,
+      // while the messagesbox must not
+      UpdateFolderPaneFlex(onlyFolderPane);
+      if (!onlyOnePane)
+      {
+        showFolderSplitter  = folderPaneVisible;
+        showMessageSplitter = threadPaneVisible && messagePaneVisible;
+      }
+      break;
+
+    case kWideMailLayout:
+      // if only the messagepane is visible, collapse the rest
+      let messengerBox = document.getElementById("messengerBox");
+      messengerBox.collapsed = onlyMessagePane;
+      // a hidden displaydeck must not flex, while the folderpane has to
+      if (!onlyMessagePane)
+        UpdateFolderPaneFlex(!displayDeckVisible);
+      if (!onlyOnePane)
+      {
+        showFolderSplitter  = folderPaneVisible && displayDeckVisible;
+        showMessageSplitter = messagePaneVisible;
+      }
+      break;
+
+    case kVerticalMailLayout:
+      // if the threadpane is hidden, we need to hide its outer box as well
+      let messagesBox = document.getElementById("messagesBox");
+      messagesBox.collapsed = !displayDeckVisible;
+      // if only the folderpane is visible, it needs to flex
+      UpdateFolderPaneFlex(onlyFolderPane);
+      if (!onlyOnePane)
+      {
+        showFolderSplitter  = folderPaneVisible;
+        showMessageSplitter = messagePaneVisible;
+      }
+      break;
+  }
+
+  // set splitter visibility
+  // if the pane was hidden by clicking the splitter grippy,
+  // the splitter must not hide
+  MsgCollapseSplitter(GetFolderPaneSplitter(),           !showFolderSplitter);
+  MsgCollapseSplitter(GetThreadAndMessagePaneSplitter(), !showMessageSplitter);
+
+  // disable location bar if only message pane is visible
+  document.getElementById("locationFolders").disabled = onlyMessagePane;
+  // disable mailviews and search if threadpane is invisble
+  var viewPicker = document.getElementById("viewPicker");
+  viewPicker.disabled = !threadPaneVisible;
+  viewPicker.previousSibling.disabled = !threadPaneVisible;
+  GetSearchInput().disabled = !threadPaneVisible;
+}
+
+function ChangeMessagePaneVisibility()
+{
+  var hidden = IsMessagePaneCollapsed();
+  // We also have to disable the Message/Attachments menuitem.
+  // It will be enabled when loading a message with attachments
+  // (see messageHeaderSink.handleAttachment).
+  if (hidden)
+  {
+    let node = document.getElementById("msgAttachmentMenu");
+    if (node)
+      node.setAttribute("disabled", "true");
+  }
+
+  if (gDBView)
+  {
+    // clear the subject, collapsing won't automatically do this
+    setTitleFromFolder(GetThreadPaneFolder(), null);
+    // the collapsed state is the state after we released the mouse
+    // so we take it as it is
+    gDBView.suppressMsgDisplay = hidden;
+    // set the subject, uncollapsing won't automatically do this
+    gDBView.loadMessageByUrl("about:blank");
+    gDBView.selectionChanged();
+  }
+  var event = document.createEvent("Events");
+  if (hidden)
+    event.initEvent("messagepane-hide", false, true);
+  else
+    event.initEvent("messagepane-unhide", false, true);
+  document.getElementById("messengerWindow").dispatchEvent(event);
+}
+
+function MsgToggleMessagePane(aToggleManually)
+{
+  // don't hide all three panes at once
+  if (IsDisplayDeckCollapsed() && IsFolderPaneCollapsed())
+    return;
+  // toggle the splitter manually if it wasn't clicked and remember that
+  var splitter = GetThreadAndMessagePaneSplitter();
+  if (aToggleManually)
+    MsgToggleSplitter(splitter);
+  splitter.collapsible = aToggleManually;
+  ChangeMessagePaneVisibility();
+  UpdateLayoutVisibility();
+}
+
+function MsgToggleFolderPane(aToggleManually)
+{
+  // don't hide all three panes at once
+  if (IsDisplayDeckCollapsed() && IsMessagePaneCollapsed())
+    return;
+  // toggle the splitter manually if it wasn't clicked and remember that
+  var splitter = GetFolderPaneSplitter();
+  if (aToggleManually)
+    MsgToggleSplitter(splitter);
+  splitter.collapsible = aToggleManually;
+  UpdateLayoutVisibility();
+}
+
+function MsgToggleThreadPane()
+{
+  // don't hide all three panes at once
+  if (IsFolderPaneCollapsed() && IsMessagePaneCollapsed())
+    return;
+  var threadPane = GetDisplayDeck();
+  threadPane.collapsed = !threadPane.collapsed;
+  // we only get here by hitting a key, so always hide border splitters
+  UpdateLayoutVisibility();
+}
+
+// When the ThreadPane is hidden via the displayDeck, we should collapse the
+// elements that are only meaningful to the thread pane. When AccountCentral is
+// shown via the displayDeck, we need to switch the displayDeck to show the
+// accountCentralBox and load the iframe in the AccountCentral box with the
+// corresponding page.
+function ShowAccountCentral()
+{
+  try
+  {
+    GetDisplayDeck().selectedPanel = accountCentralBox;
+    let acctCentralPage = pref.getComplexValue("mailnews.account_central_page.url",
+                                               Components.interfaces.nsIPrefLocalizedString).data;
+    window.frames["accountCentralPane"].location.href = acctCentralPage;
+  }
+  catch (ex)
+  {
+    dump("Error loading AccountCentral page -> " + ex + "\n");
+    return;
+  }
+}
+
+function ShowingAccountCentral()
+{
+  if (!IsFolderPaneCollapsed())
+    GetFolderTree().focus();
+  gAccountCentralLoaded = true;
+}
+
+function HidingAccountCentral()
+{
+  gAccountCentralLoaded = false;
+}
+
+function ShowThreadPane()
+{
+  GetDisplayDeck().selectedPanel = GetThreadPane();
+}
+
+function ShowingThreadPane()
+{
+  gSearchBox.collapsed = false;
+  var threadPaneSplitter = GetThreadAndMessagePaneSplitter();
+  threadPaneSplitter.collapsed = false;
+  if (!threadPaneSplitter.hidden && threadPaneSplitter.getAttribute("state") != "collapsed")
+  {
+    GetMessagePane().collapsed = false;
+    // XXX We need to force the tree to refresh its new height
+    // so that it will correctly scroll to the newest message
+    GetThreadTree().boxObject.height;
+  }
+  document.getElementById("key_toggleThreadPane").removeAttribute("disabled");
+  document.getElementById("key_toggleMessagePane").removeAttribute("disabled");
+}
+
+function HidingThreadPane()
+{
+  ClearThreadPane();
+  GetUnreadCountElement().hidden = true;
+  GetTotalCountElement().hidden = true;
+  GetMessagePane().collapsed = true;
+  GetThreadAndMessagePaneSplitter().collapsed = true;
+  gSearchBox.collapsed = true;
+  document.getElementById("key_toggleThreadPane").setAttribute("disabled", "true");
+  document.getElementById("key_toggleMessagePane").setAttribute("disabled", "true");
+}
+
+var gCurrentDisplayDeckId = "";
+function ObserveDisplayDeckChange(aEvent)
+{
+  var selectedPanel = GetDisplayDeck().selectedPanel;
+  var nowSelected = selectedPanel ? selectedPanel.id : "";
+  // onselect fires for every mouse click inside the deck, so ObserveDisplayDeckChange
+  // is getting called every time we click on a message in the thread pane.
+  // Only show/hide elements if the selected deck is actually changing.
+  if (nowSelected != gCurrentDisplayDeckId)
+  {
+    if (nowSelected == "threadPaneBox")
+      ShowingThreadPane();
+    else
+      HidingThreadPane();
+
+    if (nowSelected == "accountCentralBox")
+    {
+      ShowingAccountCentral();
+    }
+    else
+    {
+      HidingAccountCentral();
+    }
+    gCurrentDisplayDeckId = nowSelected;
+  }
+}
+
+function InvalidateTabDBs()
+{
+  // enforce reloading the tab's dbView
+  var tabInfos = GetTabMail().tabInfo;
+  for (let i = 0; i < tabInfos.length; ++i)
+  {
+    let tabInfo = tabInfos[i];
+    // only reroot 3pane tabs
+    if (tabInfo.mode.type == "3pane")
+    {
+      // don't change URI if already set -
+      // we might try to read from an invalid msgSelectedFolder
+      if (!("uriToOpen" in tabInfo))
+        tabInfo.uriToOpen = tabInfo.msgSelectedFolder.URI;
+    }
+  }
+}
--- a/suite/mailnews/tabmail.xml
+++ b/suite/mailnews/tabmail.xml
@@ -1,62 +1,61 @@
 <?xml version="1.0"?>
-# ***** 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 tab email
-#
-# The Initial Developer of the Original Code is
-#   David Bienvenu <bienvenu@nventure.com>.
-# Portions created by the Initial Developer are Copyright (C) 2007
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#  Scott MacGregor <mscott@mozilla.org>
-#  Andrew Sutherland <asutherland@asutherland.org>
-#
-# 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 *****
+<!-- ***** 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 tab email
+   -
+   - The Initial Developer of the Original Code is
+   -   David Bienvenu <bienvenu@nventure.com>.
+   - Portions created by the Initial Developer are Copyright (C) 2007
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -  Scott MacGregor <mscott@mozilla.org>
+   -  Andrew Sutherland <asutherland@asutherland.org>
+   -  Karsten Düsterloh <mnromyr@tprac.de>
+   -
+   - 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 ***** -->
 
 <!DOCTYPE bindings [
-<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
-%messengerDTD;
-<!ENTITY % tabMailDTD SYSTEM "chrome://messenger/locale/tabmail.dtd" >
- %tabMailDTD;
- <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
- %globalDTD;
+  <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+  %messengerDTD;
+  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+  %globalDTD;
 ]>
 
 <bindings id="tabmailBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
 
-  <!-- Thunderbird's tab UI mechanism.
+  <!-- SeaMonkey's clone of Thunderbird's tab UI mechanism.
     -
     - We expect to be instantiated with the following children:
     - * One "tabpanels" child element whose id must be placed in the
     -   "panelcontainer" attribute on the element we are being bound to. We do
     -   this because it is important to allow overlays to contribute panels.
     -   When we attempted to have the immediate children of the bound element
     -   be propagated through use of the "children" tag, we found that children
     -   contributed by overlays did not propagate.
@@ -76,53 +75,53 @@
     - * 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.
     - * setTabTitle([aOptionalTabInfo]): Tells us that the title of the current
     -   tab (if no argument is provided) or provided tab needs to be updated.
     -   This will result in a call to the tab mode's logic to update the title.
     -   In the event this is not for the current tab, the caller is responsible
     -   for ensuring that the underlying tab mode is capable of providing a tab
-    -   title when it is in the background.  (The is currently not the case for
-    -   "folder" and "mail" modes because of their implementation.)
+    -   title when it is in the background.
     - * removeCurrentTab(): Close the current tab.
-    - * removeTabByNode(aTabElement): Close the tab whose tabmail-tab bound
+    - * removeTab(aTabElement): Close the tab whose tabmail-tab bound
     -   element is passed in.
     - Changing the currently displayed tab is accomplished by changing
     -  tabmail.tabContainer's selectedIndex or selectedItem property.
     -
     - Tab contributing code should define a tab type object and register it
     -  with us by calling registerTabType.  Each tab type can provide multiple
     -  tab modes.  The rationale behind this organization is that Thunderbird
     -  historically/currently uses a single 3-pane view to display both
     -  three-pane folder browsing and single message browsing across multiple
     -  tabs.  Each tab type has the ability to use a single tab panel for all
     -  of its display needs.  So Thunderbird's "mail" tab type covers both the
     -  "folder" (3-pane folder-based browsing)  and "message" (just a single
-    -  message) tab modes.  Likewise, calendar/lightning currently displays both
-    -  its calendar and tasks in the same panel.  A tab type can also create a
-    -  new tabpanel for each tab as it is created.  In that case, the tab type 
-    -  should probably only have a single mode unless there are a number of
-    -  similar modes that can gain from code sharing.
+    -  message) tab modes, while SeaMonkey integrates both flavours into just
+    -  one "3pane" mode.  Likewise, calendar/lightning currently displays
+    -  both its calendar and tasks in the same panel.  A tab type can also
+    -  create a new tabpanel for each tab as it is created.  In that case, the
+    -  tab type should probably only have a single mode unless there are a
+    -  number of similar modes that can gain from code sharing.
     - The tab type definition should include the following attributes:
     - * name: The name of the tab-type, mainly to aid in debugging.
     - * panelId or perTabPanel: If using a single tab panel, the id of the
     -     panel must be provided in panelId.  If using one tab panel per tab,
     -     perTabPanel should be the XUL element name that should be created for
     -     each tab.  If a reason arises, in the future we might support
     -     this being a helper function to create and return the element.
     - * modes: An object whose attributes are mode names (which are
     -     automatically propagated to a 'name' attribute for debugging) and
     -     values are objects with the following attributes...
     - * any of the openTab/closeTab/saveTabState/showTab/onTitleChanged
     -     functions as described on the mode definitions.  These will only be
     -     called if the mode does not provide the functions.  Note that because
     -     the 'this' variable passed to the functions will always reference the
     -     tab type definition (rather than the mode definition), the mode
-    -     functions can defer to the tab type functions by calling 
+    -     functions can defer to the tab type functions by calling
     -     this.functionName().  (This should prove convenient.)
     - Mode definition attributes:
     - * type: The "type" attribute to set on the displayed tab for CSS purposes.
     -     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.
@@ -130,1292 +129,1454 @@
     -     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.
     -     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
-    -     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.
-    - * 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
-    -     restore its state (if required).
-    - * onTitleChanged(aTab): Called when someone calls tabmail.setTabTitle() to
-    -     hint that the tab's title needs to be updated.  This function should
-    -     update aTab.title if it can.
-    - Mode definition functions to do with menu/toolbar commands:
-    - * supportsCommand(aTab, aCommand): Called when a menu or toolbar needs to
-    -     be updated. Return true if you support that command in
+    - * openTab(aTabInfo, arguments...): Called when a tab of the given mode is
+    -     in the process of being opened.  aTabInfo 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.
+    - * canCloseTab(aTabInfo): Optional function.
+    -     Return true (false) if the tab is (not) allowed to close.
+    -     A tab's default permission is stored in aTabInfo.canClose.
+    - * closeTab(aTabInfo): Called when aTabInfo is being closed.  The tab need
+    -     not be currently displayed.  You are responsible for properly cleaning
+    -     up any state you preserved in aTabInfo.
+    - * saveTabState(aTabInfo): Called when aTabInfo is being switched away from
+    -     so that you can preserve its state on aTabInfo.  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(aTabInfo): Called when aTabInfo is being displayed and you
+    -     should restore its state (if required).
+    - * onTitleChanged(aTabInfo): Called when someone calls
+    -     tabmail.setTabTitle() to hint that the tab's title needs to be
+    -     updated.  This function should update aTabInfo.title if it can.
+    - * getBrowser(aTabInfo): This function should return the browser element
+    -     for your tab if there is one (return null or don't define this
+    -     function otherwise). It is used for some toolkit functions that
+    -     require a global "getBrowser" function, e.g. ZoomManager.
+    -
+    - Mode definition functions for menu/toolbar commands (see nsIController):
+    - * supportsCommand(aCommand, aTabInfo): Called when a menu or toolbar needs
+    -     to be updated. Return true if you support that command in
     -     isCommandEnabled and doCommand, return false otherwise.
-    - * isCommandEnabled(aTab, aCommand): Called when a menu or toolbar needs
-    -     to be updated. Return true if the command can be executed at the
+    - * isCommandEnabled(aCommand, aTabInfo): Called when a menu or toolbar
+    -     needs to be updated. Return true if the command can be executed at the
     -     current time, false otherwise.
-    - * doCommand(aTab, aCommand): Called when a menu or toolbar command is to
-    -     be executed. Perform the action appropriate to the command.
-    - * onEvent(aTab, aEvent): This can be used to handle different events on
-    -     the window.
-    - * getBrowser(aTab): This function should return the browser element for
-    -     your tab if there is one (return null or don't define this function
-    -     otherwise). It is used for some toolkit functions that require a
-    -     global "getBrowser" function, e.g. ZoomManager.
+    - * doCommand(aCommand, aTabInfo): Called when a menu or toolbar command is
+    -     to be executed. Perform the action appropriate to the command.
+    - * onEvent(aEvent, aTabInfo): This can be used to handle different events
+    -     on the window.
     -
     - Tab monitoring code is expected to be used for widgets on the screen
     -  outside of the tab box that need to update themselves as the active tab
     -  changes.  This is primarily intended to be used for the ThunderBar; if
     -  you are not the ThunderBar and this sounds appealing to you, please
     -  solicit discussion on your needs on the mozilla.dev.apps.thunderbird
     -  newsgroup.
     - Tab monitoring code (un)registers itself via (un)registerTabMonitor.
     -  The following functions should be provided on the monitor object:
-    - * onTabTitleChanged(aTab): Called when the tab's title changes.
-    - * onTabSwitched(aTab, aOldTab): Called when a new tab is made active.  If
-    -     this is the first tab ever, aOldTab will be null, otherwise aOldTab
-    -     will be the previously active tab.
+    - * onTabTitleChanged(aTabInfo): Called when the tab's title changes.
+    - * onTabSwitched(aTabInfo, aOldTabInfo): Called when a new tab is made
+    -     active.  If this is the first tab ever, aOldTabInfo will be null,
+    -     otherwise aOldTabInfo will be the previously active tab.
     -->
-  <binding id="tabmail">
+  <binding id="tabmail"
+           extends="chrome://navigator/content/tabbrowser.xml#tabbrowser">
     <resources>
-      <stylesheet src="chrome://messenger/skin/tabmail.css"/>
+      <stylesheet src="chrome://navigator/skin/tabbrowser.css"/>
     </resources>
     <content>
-      <xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
-                  onselect="if (!('updateCurrentTab' in this.parentNode) || event.target.localName != 'tabs')
-                            return; this.parentNode.updateCurrentTab();">
+      <xul:tabbox anonid="tabbox"
+                  flex="1"
+                  eventnode="document"
+                  xbl:inherits="handleCtrlPageUpDown"
+                  onselect="if (event.target.localName == 'tabs' &amp;&amp;
+                                'updateCurrentTab' in this.parentNode)
+                              this.parentNode.updateCurrentTab();">
         <xul:hbox class="tab-drop-indicator-bar" collapsed="true">
           <xul:hbox class="tab-drop-indicator" mousethrough="always"/>
         </xul:hbox>
-        <xul:hbox class="tabmail-strip" collapsed="false" tooltip="_child" context="_child"
+        <xul:hbox class="tabbrowser-strip tabmail-strip"
+                  tooltip="_child"
+                  context="_child"
                   anonid="strip"
                   ondraggesture="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
                   ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
                   ondragdrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
                   ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
-          <xul:tooltip onpopupshowing="return CreateToolbarTooltip(document, event);"/>
+          <xul:tooltip onpopupshowing="var tabmail = this.parentNode.parentNode.parentNode;
+                                       return tabmail.FillTabmailTooltip(document, event);"/>
           <xul:menupopup anonid="tabContextMenu"
-                onpopupshowing="
-                  var tabmail = this.parentNode.parentNode.parentNode;
-                  return tabmail.onTabContextMenuShowing(document.popupNode);">
-            <xul:menuitem label="&closeTabCmd.label;" accesskey="&closeTabCmd.accesskey;"
+                         onpopupshowing="var tabmail = this.parentNode.parentNode.parentNode;
+                                         tabmail.mContextTab = document.popupNode;">
+            <xul:menuitem label="&closeTabCmd.label;"
+                          accesskey="&closeTabCmd.accesskey;"
                           oncommand="var tabmail = this.parentNode.parentNode.parentNode.parentNode;
-                                     tabmail.removeTabByNode(document.popupNode);"/>
+                                     tabmail.removeTab(tabmail.mContextTab);"/>
           </xul:menupopup>
-          <xul:tabs class="tabmail-tabs" flex="1"
+          <xul:tabs class="tabbrowser-tabs tabmail-tabs"
+                    flex="1"
                     anonid="tabcontainer"
                     setfocus="false"
                     onclick="this.parentNode.parentNode.parentNode.onTabClick(event);">
-            <xul:tab selected="true" validate="never" type="folder"
-                     maxwidth="250" width="0" minwidth="100" flex="100"
-                     class="tabmail-tab" crop="end"/>
+            <xul:tab selected="true"
+                     validate="never"
+                     type="3pane"
+                     maxwidth="250"
+                     width="0"
+                     minwidth="100"
+                     flex="100"
+                     class="tabbrowser-tab tabmail-tab"
+                     crop="end"/>
             <children/>
           </xul:tabs>
         </xul:hbox>
         <!-- Remember, user of this binding, you need to provide tabpanels!  -->
         <children includes="tabpanels"/>
       </xul:tabbox>
     </content>
 
-    <implementation implements="nsIController">
+    <implementation implements="nsIController, nsIObserver">
       <constructor>
-        window.controllers.insertControllerAt(0, this);
+        <![CDATA[
+          window.controllers.insertControllerAt(0, this);
+          const kAutoHide = "browser.tabs.autoHide";
+          this.mAutoHide = this.mPrefs.getBoolPref(kAutoHide);
+          this.mPrefs.addObserver(kAutoHide, this, false);
+        ]]>
       </constructor>
+
       <destructor>
-        window.controllers.removeController(this);
+        <![CDATA[
+          this.mPrefs.removeObserver("browser.tabs.autoHide", this);
+          window.controllers.removeController(this);
+        ]]>
       </destructor>
+
+      <field name="mPrefs">
+        Components.classes["@mozilla.org/preferences-service;1"]
+                  .getService(Components.interfaces.nsIPrefBranch2);
+      </field>
+
       <field name="currentTabInfo">
         null
       </field>
+
       <field name="tabTypes" readonly="true">
         new Object()
       </field>
+
       <field name="tabModes" readonly="true">
         new Object()
       </field>
+
       <field name="defaultTabMode">
         null
       </field>
+
       <field name="tabInfo" readonly="true">
         new Array()
       </field>
+
       <field name="tabStrip" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "strip");
       </field>
+
       <field name="tabContainer" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
       </field>
+
       <field name="panelContainer" readonly="true">
         document.getElementById(this.getAttribute("panelcontainer"));
       </field>
+
+      <field name="mContextTab">
+        null
+      </field>
+
+      <!-- _mAutoHide/mAutoHide reflect the current autoHide pref value -->
+      <field name="_mAutoHide">false</field>
+      <property name="mAutoHide" onget="return this._mAutoHide;">
+        <setter>
+          <![CDATA[
+            if (val != this._mAutoHide)
+            {
+              if (this.tabContainer.childNodes.length == 1)
+                this.mStripVisible = !val;
+              this._mAutoHide = val;
+            }
+            return val;
+          ]]>
+        </setter>
+      </property>
+
+      <!-- mStripVisible reflects the actual XUL autoHide state -->
+      <property name="mStripVisible"
+                onget="return !this.tabStrip.collapsed;"
+                onset="return this.tabStrip.collapsed = !val;"/>
+
+      <method name="registerTabType">
+        <parameter name="aTabType"/>
+        <body>
+          <![CDATA[
+            if (aTabType.name in this.tabTypes)
+              return;
+            this.tabTypes[aTabType.name] = aTabType;
+            for (let [modeName, modeDetails] in Iterator(aTabType.modes))
+            {
+              modeDetails.name = modeName;
+              modeDetails.tabType = aTabType;
+              modeDetails.tabs = [];
+              this.tabModes[modeName] = modeDetails;
+              if (modeDetails.isDefault)
+                this.defaultTabMode = modeDetails;
+            }
+            aTabType.panel = document.getElementById(aTabType.panelId);
+          ]]>
+        </body>
+      </method>
+
       <field name="tabMonitors" readonly="true">
         new Array()
       </field>
-      <method name="registerTabType">
-        <parameter name="aTabType"/>
-        <body><![CDATA[
-          if (aTabType.name in this.tabTypes)
-            return;
-        
-          this.tabTypes[aTabType.name] = aTabType;
-          for (let [modeName, modeDetails] in Iterator(aTabType.modes)) {
-            modeDetails.name = modeName;
-            modeDetails.tabType = aTabType;
-            modeDetails.tabs = [];
-            this.tabModes[modeName] = modeDetails;
-            if (modeDetails.isDefault)
-              this.defaultTabMode = modeDetails;
-          }
-          
-          aTabType.panel = document.getElementById(aTabType.panelId);
-        ]]></body>
-      </method>
+
       <method name="registerTabMonitor">
         <parameter name="aTabMonitor"/>
-        <body><![CDATA[
-          if (this.tabMonitors.indexOf(aTabMonitor) == -1)
-            this.tabMonitors.push(aTabMonitor);
-        ]]></body>
+        <body>
+          <![CDATA[
+            if (this.tabMonitors.indexOf(aTabMonitor) == -1)
+              this.tabMonitors.push(aTabMonitor);
+          ]]>
+        </body>
       </method>
+
       <method name="unregisterTabMonitor">
         <parameter name="aTabMonitor"/>
-        <body><![CDATA[
-          if (this.tabMonitors.indexOf(aTabMonitor) >= 0)
-            this.tabMonitors.splice(this.tabMonitors.indexOf(aTabMonitor), 1);
-        ]]></body>
-      </method>
-      <method name="openFirstTab">
-        <body><![CDATA[
-          // From the moment of creation, our XBL binding already has a visible
-          //  tab.  We need to create a tab information structure for this tab.
-          //  In the process we also generate a synthetic tab title changed
-          //  event to ensure we have an accurate title.  We assume the tab
-          //  contents will set themselves up correctly.
-          if (this.tabInfo.length == 0) {
-            let firstTab = {mode: this.defaultTabMode, canClose: false};
-            firstTab.mode.tabs.push(firstTab);
-           
-            this.tabInfo[0] = this.currentTabInfo = firstTab;
-            this.setTabTitle(firstTab);
-
-            if (this.tabMonitors.length) {
-              for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
-                tabMonitor.onTabSwitched(tab, null);
-            }
-          }
-        ]]></body>
+        <body>
+          <![CDATA[
+            if (this.tabMonitors.indexOf(aTabMonitor) >= 0)
+              this.tabMonitors.splice(this.tabMonitors.indexOf(aTabMonitor), 1);
+          ]]>
+        </body>
       </method>
-      <method name="openTab">
-        <parameter name="aTabModeName"/>
-        <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;
-          }
-
-          // If the mode wants us to, we should switch to an existing tab
-          // rather than open a new one.
-          let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
-                                   tabMode.tabType.shouldSwitchTo;
-
-          if (shouldSwitchToFunc) {
-            let args = Array.prototype.slice.call(arguments, 1);
-            let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, args);
-            if (tabIndex >= 0) {
-              this.selectTabByIndex(null, tabIndex);
-              return;
-            }
-          }
-
-          // we need to save the state before it gets corrupted
-          this.saveCurrentTabState();
-
-          let tab = {mode: tabMode, canClose: true};
-          tabMode.tabs.push(tab);
-
-          var t = document.createElementNS(
-            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-            "tab");
-          t.setAttribute("crop", "end");
-          t.maxWidth = this.tabContainer.mTabMaxWidth;
-          t.minWidth = this.tabContainer.mTabMinWidth;
-          t.width = 0;
-          t.setAttribute("flex", "100");
-          t.setAttribute("validate", "never");
-          t.className = "tabmail-tab";
-          this.tabContainer.appendChild(t);
-          this.tabContainer.appendChild(t);
-          if (this.tabContainer.collapsed) {
-            this.tabContainer.collapsed = false;
-            this.tabContainer.adjustTabstrip();
-          }
-
-          let oldTab = 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;
-
-          // 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;
-          }
-          else
-            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) {
-            for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
-              tabMonitor.onTabSwitched(tab, oldTab);
-          }
-          
-          t.setAttribute("label", tab.title);
-          
-          let docTitle = tab.title;
-#ifndef XP_MACOSX
-          docTitle += " - " + gBrandBundle.getString("brandFullName");
-#endif
-          document.title = docTitle;
-          
-          // for styling purposes, apply the type to the tab...
-          t.setAttribute('type', tab.mode.type);
-        ]]></body>
-      </method>
-      <method name="selectTabByMode">
-        <parameter name="aTabModeName"/>
-        <body><![CDATA[
-          let tabMode = this.tabModes[aTabModeName];
-          if (tabMode.tabs.length) {
-            let desiredTab = tabMode.tabs[0];
-            this.tabContainer.selectedIndex = this.tabInfo.indexOf(desiredTab);
-          }
-        ]]></body>
-      </method>
-      <method name="selectTabByIndex">
-        <parameter name="aEvent"/>
-        <parameter name="aIndex"/>
-        <body><![CDATA[
-          // count backwards for aIndex < 0
-          if (aIndex < 0)
-            aIndex += this.tabInfo.length;
-
-          if (aIndex >= 0 &&
-              aIndex < this.tabInfo.length &&
-              aIndex != this.tabContainer.selectedIndex) {
-            this.tabContainer.selectedIndex = aIndex;
-          }
-
-          if (aEvent) {
-            aEvent.preventDefault();
-            aEvent.stopPropagation();
-          }
-        ]]></body>
-      </method>
-      <method name="closeTabs">
+      <method name="openFirstTab">
         <body>
           <![CDATA[
-            for (var i = 0; i < this.tabInfo.length; i++) {
-              let tab = this.tabInfo[i];
-              
-              let tabCloseFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
-              tabCloseFunc.call(tab.mode.tabType, tab);
+            // From the moment of creation, our XBL binding already has a
+            // visible tab. We need to create a tab information structure for
+            // this tab. In the process we also generate a synthetic "tab title
+            // changed" event to ensure we have an accurate title.
+            // Note: for mail tabs, the title gets only set later when the
+            // folder or message is loaded, as we don't have a gDBView yet!
+            // We assume the tab contents will set themselves up correctly.
+            if (!this.tabInfo.length)
+            {
+              let firstTabInfo = {mode: this.defaultTabMode, canClose: true};
+              let firstTabNode = this.tabContainer.firstChild;
+              firstTabInfo.mode.tabs.push(firstTabInfo);
+              this.tabInfo[0] = this.currentTabInfo = firstTabInfo;
+              this.setTabTitle(firstTabInfo);
+              if (this.tabMonitors.length)
+              {
+                for (let [i, tabMonitor] in Iterator(this.tabMonitors))
+                  tabMonitor.onTabSwitched(firstTabInfo, null);
+              }
             }
           ]]>
         </body>
       </method>
-      <method name="removeTabByNode">
-        <parameter name="aTabNode"/>
+
+      <method name="openTab">
+        <parameter name="aTabModeName"/>
+        <!-- parameter name="aMoreParameters..."/-->
         <body>
           <![CDATA[
-            // Find and locate the tab in our list.
-            let iTab, numTabs = this.tabContainer.childNodes.length;
-            for (iTab = 0; iTab < numTabs; iTab++)
-              if (this.tabContainer.childNodes[iTab] == aTabNode)
-                break;
-            let tab = this.tabInfo[iTab];
+            if (!aTabModeName)
+              aTabModeName = this.currentTabInfo.mode.type;
 
-            if (!tab.canClose)
+            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)
+            {
+              // show the first tab of this mode
+              this.tabContainer.selectedIndex = this.tabInfo.indexOf(tabMode.tabs[0]);
               return;
-            
-            let closeFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
-            closeFunc.call(tab.mode.tabType, tab);
-             
-            this.tabInfo.splice(iTab, 1);
-            tab.mode.tabs.splice(tab.mode.tabs.indexOf(tab), 1);
-            this.tabContainer.removeChild(aTabNode);
-            if (this.tabContainer.selectedIndex == -1)
-              this.tabContainer.selectedIndex = (iTab == --numTabs) ?
-                iTab - 1 : iTab;
-            if (this.currentTabInfo == tab)
-              this.updateCurrentTab();
-              
-            if (tab.panel) {
-              this.panelContainer.removeChild(tab.panel);
-              delete tab.panel;
+            }
+
+            // If the mode wants us to, we should switch to an existing tab
+            // rather than open a new one.
+            let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
+                                     tabMode.tabType.shouldSwitchTo;
+
+            if (shouldSwitchToFunc)
+            {
+              let args = Array.prototype.slice.call(arguments, 1);
+              let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, args);
+              if (tabIndex >= 0)
+              {
+                this.selectTabByIndex(tabIndex);
+                return;
+              }
             }
-            if (numTabs == 1 && this.tabContainer.mAutoHide)
-              this.tabContainer.collapsed = true;
-          ]]>
-        </body>
-      </method>
-      <!-- getBrowserForSelectedTab is required as some toolkit functions
-           require a getBrowser() function. -->
-      <method name="getBrowserForSelectedTab">
-        <body><![CDATA[
-          if (!this.currentTabInfo)
-            this.currentTabInfo = this.tabInfo[0];
+
+            // we need to save the state before it gets corrupted
+            this.saveCurrentTabState();
+
+            let tabInfo = {mode: tabMode, canClose: true};
+            tabMode.tabs.push(tabInfo);
 
-          let tab = this.currentTabInfo;
+            let t = document.createElementNS(
+              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+              "tab");
+            t.setAttribute("crop", "end");
+            t.maxWidth = this.tabContainer.mTabMaxWidth;
+            t.minWidth = this.tabContainer.mTabMinWidth;
+            t.width = 0;
+            t.setAttribute("flex", "100");
+            t.setAttribute("validate", "never");
+            t.className = "tabbrowser-tab tabmail-tab";
+            // for styling purposes, apply the type to the tab
+            // (this attribute may be overwritten by mode functions)
+            t.setAttribute("type", tabInfo.mode.type);
+            this.tabContainer.appendChild(t);
+            if (!this.mStripVisible)
+            {
+              this.mStripVisible = true;
+              this.tabContainer.adjustTabstrip();
+            }
+
+            // Open new tabs in the background?
+            let switchToNewTab = !this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
+            tabInfo.switchToNewTab = switchToNewTab;
 
-          let browserFunc = tab.mode.getBrowser || tab.mode.tabType.getBrowser;
-          if (browserFunc)
-            return browserFunc.call(tab.mode.tabType, tab);
+            // the order of the following statements is important
+            let oldTabInfo = this.currentTabInfo;
+            this.tabInfo[this.tabContainer.childNodes.length - 1] = tabInfo;
 
-          return null;
-        ]]></body>
-      </method>
-      <method name="removeCurrentTab">
-        <body><![CDATA[
-          this.removeTabByNode(
-            this.tabContainer.childNodes[this.tabContainer.selectedIndex]);
-        ]]></body>
-      </method>
-      <!--  UpdateCurrentTab - called in response to changing the current tab -->
-      <method name="updateCurrentTab">
-        <body>
-          <![CDATA[
-            if (this.currentTabInfo != this.tabInfo[this.tabContainer.selectedIndex])
+            if (switchToNewTab)
+            {
+              this.currentTabInfo = tabInfo;
+              // 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
+            let selectedPanel;
+            if (tabInfo.mode.tabType.perTabPanel)
+            {
+              // should we create the element for them, or will they do it?
+              if (typeof(tabInfo.mode.tabType.perTabPanel) == "string")
+              {
+                tabInfo.panel = document.createElementNS(
+                  "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+                  tabInfo.mode.tabType.perTabPanel);
+              }
+              else
+              {
+                tabInfo.panel = tabInfo.mode.tabType.perTabPanel(tabInfo);
+              }
+              this.panelContainer.appendChild(tabInfo.panel);
+              selectedPanel = tabInfo.panel;
+            }
+            else
             {
-              if (this.currentTabInfo)
-                this.saveCurrentTabState();
-              
-              let oldTab = this.currentTabInfo;
-              let tab = this.currentTabInfo =
-                this.tabInfo[this.tabContainer.selectedIndex];
+              selectedPanel = tabInfo.mode.tabType.panel;
+            }
+            if (switchToNewTab)
+              this.panelContainer.selectedPanel = selectedPanel;
 
-              this.panelContainer.selectedPanel = tab.panel ||
-                                                  tab.mode.tabType.panel;
+            let tabOpenFunc = tabInfo.mode.openTab ||
+                              tabInfo.mode.tabType.openTab;
+            let args = [tabInfo].concat(Array.prototype.slice.call(arguments, 1));
+            if (tabOpenFunc)
+              tabOpenFunc.apply(tabInfo.mode.tabType, args);
 
-              let showTabFunc = tab.mode.showTab || tab.mode.tabType.showTab;
-              showTabFunc.call(tab.mode.tabType, tab);
+            if (!switchToNewTab)
+            {
+              // if the new tab isn't made current,
+              // its title won't change automatically
+              this.setTabTitle(tabInfo);
+            }
 
-              if (this.tabMonitors.length) {
-                for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
-                  tabMonitor.onTabSwitched(tab, oldTab);
-              }
+            if (this.tabMonitors.length)
+            {
+              if (switchToNewTab)
+                for (let [i, tabMonitor] in Iterator(this.tabMonitors))
+                  tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
+            }
 
-
-              let docTitle = tab.title;
-#ifndef XP_MACOSX
-              docTitle += " - " + gBrandBundle.getString("brandFullName");
-#endif
+            t.setAttribute("label", tabInfo.title);
+            if (switchToNewTab)
+            {
+              let docTitle = tabInfo.title;
+              if (!/Mac/.test(navigator.platform))
+                docTitle += " - " + gBrandBundle.getString("brandFullName");
               document.title = docTitle;
 
               // Update the toolbar status - we don't need to do menus as they
               // do themselves when we open them.
               UpdateMailToolbar("tabmail");
             }
           ]]>
         </body>
       </method>
+
+      <method name="selectTabByMode">
+        <parameter name="aTabModeName"/>
+        <body>
+          <![CDATA[
+            let tabMode = this.tabModes[aTabModeName];
+            if (tabMode.tabs.length)
+            {
+              let desiredTab = tabMode.tabs[0];
+              this.tabContainer.selectedIndex = this.tabInfo.indexOf(desiredTab);
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="selectTabByIndex">
+        <parameter name="aIndex"/>
+        <body>
+          <![CDATA[
+            // count backwards for aIndex < 0
+            if (aIndex < 0)
+              aIndex += this.tabInfo.length;
+            if (aIndex >= 0 &&
+                aIndex < this.tabInfo.length &&
+                aIndex != this.tabContainer.selectedIndex)
+            {
+              this.tabContainer.selectedIndex = aIndex;
+            }
+
+            if (aEvent)
+            {
+              aEvent.preventDefault();
+              aEvent.stopPropagation();
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="closeTabs">
+        <body>
+          <![CDATA[
+            for (let i = 0; i < this.tabInfo.length; i++)
+            {
+              let tabInfo = this.tabInfo[i];
+              let tabCloseFunc = tabInfo.mode.closeTab ||
+                                 tabInfo.mode.tabType.closeTab;
+              if (tabCloseFunc)
+                tabCloseFunc.call(tabInfo.mode.tabType, tabInfo);
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="removeTab">
+        <parameter name="aTabNode"/>
+        <!-- parameter name="aMoreParameters..."/-->
+        <body>
+          <![CDATA[
+            // Find and locate the tab in our list.
+            let iTab, numTabs = this.tabContainer.childNodes.length;
+            for (iTab = 0; iTab < numTabs; iTab++)
+              if (this.tabContainer.childNodes[iTab] == aTabNode)
+                break;
+            let tabInfo = this.tabInfo[iTab];
+
+            // ask the tab type implementation if we're allowed to close the tab
+            let canClose = tabInfo.canClose;
+            let canCloseFunc = tabInfo.mode.canCloseTab ||
+                               tabInfo.mode.tabType.canCloseTab;
+            if (canCloseFunc)
+              canClose = canCloseFunc.call(tabInfo.mode.tabType, tabInfo);
+            if (!canClose)
+              return;
+
+            let closeFunc = tabInfo.mode.closeTab ||
+                            tabInfo.mode.tabType.closeTab;
+            if (closeFunc)
+              closeFunc.call(tabInfo.mode.tabType, tabInfo);
+
+            this.tabInfo.splice(iTab, 1);
+            tabInfo.mode.tabs.splice(tabInfo.mode.tabs.indexOf(tabInfo), 1);
+            this.tabContainer.removeChild(aTabNode);
+            if (this.tabContainer.selectedIndex == -1)
+              this.tabContainer.selectedIndex = (iTab == --numTabs) ? iTab - 1 : iTab;
+            if (this.currentTabInfo == tabInfo)
+              this.updateCurrentTab();
+
+            if (tabInfo.panel)
+            {
+              this.panelContainer.removeChild(tabInfo.panel);
+              delete tabInfo.panel;
+            }
+            if (numTabs == 1 && this.mAutoHide)
+              this.mStripVisible = false;
+          ]]>
+        </body>
+      </method>
+
+      <method name="removeCurrentTab">
+        <body>
+          <![CDATA[
+            this.removeTab(this.tabContainer.selectedItem);
+          ]]>
+        </body>
+      </method>
+
+      <!--  UpdateCurrentTab - called in response to changing the current tab -->
+      <method name="updateCurrentTab">
+        <body>
+          <![CDATA[
+            if (this.currentTabInfo != this.tabInfo[this.tabContainer.selectedIndex])
+            {
+              if (this.currentTabInfo)
+                this.saveCurrentTabState();
+              let oldTabInfo = this.currentTabInfo;
+              let tabInfo = this.currentTabInfo = this.tabInfo[this.tabContainer.selectedIndex];
+              this.panelContainer.selectedPanel = tabInfo.panel ||
+                                                  tabInfo.mode.tabType.panel;
+              let showTabFunc = tabInfo.mode.showTab ||
+                                tabInfo.mode.tabType.showTab;
+              if (showTabFunc)
+                showTabFunc.call(tabInfo.mode.tabType, tabInfo);
+              if (this.tabMonitors.length)
+              {
+                for (let [i, tabMonitor] in Iterator(this.tabMonitors))
+                  tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
+              }
+
+              let docTitle = tabInfo.title;
+              if (!/Mac/.test(navigator.platform))
+                docTitle += " - " + gBrandBundle.getString("brandFullName");
+              document.title = docTitle;
+
+              // Update the toolbar status - we don't need to do menus as they
+              // do themselves when we open them.
+              UpdateMailToolbar("tabmail");
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="saveTabState">
+        <parameter name="aTabInfo"/>
+        <body>
+          <![CDATA[
+            if (!aTabInfo)
+              return;
+            let saveTabFunc = aTabInfo.mode.saveTabState ||
+                              aTabInfo.mode.tabType.saveTabState;
+            if (saveTabFunc)
+              saveTabFunc.call(aTabInfo.mode.tabType, aTabInfo);
+          ]]>
+        </body>
+      </method>
+
       <method name="saveCurrentTabState">
         <body>
           <![CDATA[
             if (!this.currentTabInfo)
               this.currentTabInfo = this.tabInfo[0];
-            let tab = this.currentTabInfo;
-            
             // save the old tab state before we change the current tab
-            let saveTabFunc = tab.mode.saveTabState ||
-                              tab.mode.tabType.saveTabState;
-            saveTabFunc.call(tab.mode.tabType, tab);
+            this.saveTabState(this.currentTabInfo);
           ]]>
         </body>
       </method>
+
       <method name="onTabClick">
         <parameter name="event"/>
         <body>
           <![CDATA[
             // a middle mouse button click on a tab is a short cut for closing a tab
-            if (event.button != 1 || event.target.localName != 'tab')
+            if (event.button != 1 || event.target.localName != "tab")
               return;
-            this.removeTabByNode(event.target);
+            this.removeTab(event.target);
             event.stopPropagation();
           ]]>
         </body>
       </method>
+
       <method name="setTabTitle">
-        <parameter name="aTab"/>
+        <parameter name="aTabInfo"/>
         <body>
           <![CDATA[
             // First find the tab and its index.
-            let tab;
+            let tabInfo;
             let index;
-            if (aTab) {
-              tab = aTab;
-              for (index = 0; index < this.tabInfo.length; ++index) {
-                if (tab == this.tabInfo[index])
+            if (aTabInfo)
+            {
+              tabInfo = aTabInfo;
+              for (index = 0; index < this.tabInfo.length; ++index)
+              {
+                if (tabInfo == this.tabInfo[index])
                   break;
               }
             }
-            else {
+            else
+            {
               index = this.tabContainer.selectedIndex;
-              tab = this.tabInfo[index];
+              tabInfo = this.tabInfo[index];
             }
 
-            // on startup, we may not have a tab...
-            if (tab)
+            if (tabInfo)
             {
-              let tabNode =
-                this.tabContainer.childNodes[index];
-
-              let titleChangeFunc = tab.mode.onTitleChanged ||
-                                    tab.mode.tabType.onTitleChanged;
+              let tabNode = this.tabContainer.childNodes[index];
+              let titleChangeFunc = tabInfo.mode.onTitleChanged ||
+                                    tabInfo.mode.tabType.onTitleChanged;
               if (titleChangeFunc)
-                titleChangeFunc.call(tab.mode.tabType, tab, tabNode);
-
-              if (this.tabMonitors.length) {
-                for each (let [index, tabMonitor] in Iterator(this.tabMonitors))
-                  tabMonitor.onTabTitleChanged(tab);
+                titleChangeFunc.call(tabInfo.mode.tabType, tabInfo, tabNode);
+              if (this.tabMonitors.length)
+              {
+                for (let [i, tabMonitor] in Iterator(this.tabMonitors))
+                  tabMonitor.onTabTitleChanged(tabInfo);
               }
-
-              tabNode.setAttribute("label", tab.title);
+              tabNode.setAttribute("label", tabInfo.title);
 
               // Update the window title if we're the displayed tab.
-              if (index == this.tabContainer.selectedIndex) {
-                let docTitle = tab.title;
-#ifndef XP_MACOSX
-                docTitle += " - " + gBrandBundle.getString("brandFullName");
-#endif
+              if (index == this.tabContainer.selectedIndex)
+              {
+                let docTitle = tabInfo.title;
+                if (!/Mac/.test(navigator.platform))
+                  docTitle += " - " + gBrandBundle.getString("brandFullName");
                 document.title = docTitle;
+
+                // Update the toolbar status - we don't need to do menus as they
+                // do themselves when we open them.
+                UpdateMailToolbar("tabmail");
               }
             }
           ]]>
         </body>
       </method>
-      <method name="onTabContextMenuShowing">
-        <parameter name="aTabNode"/>
+
+      <method name="FillTabmailTooltip">
+        <parameter name="aDocument"/>
+        <parameter name="aEvent"/>
         <body>
           <![CDATA[
-            // To determine whether the 'Close Tab' context menu should be
-            // visible according to the tab node that was being clicked on.
-            // If the tab node is not closable, the context menu should not
-            // be shown.
-            if (aTabNode.localName != "tab")
-              return false;
-
-            for (let iTab = 0; iTab < this.tabContainer.childNodes.length; iTab++) {
-              if (this.tabContainer.childNodes[iTab] == aTabNode)
-                return this.tabInfo[iTab].canClose;
+            aEvent.stopPropagation();
+            let tn = aDocument.tooltipNode;
+            if (tn.localName != "tab")
+              return false; // Not a tab, so cancel the tooltip.
+            if (tn.hasAttribute("label"))
+            {
+              aEvent.target.setAttribute("label", tn.getAttribute("label"));
+              return true;
             }
             return false;
           ]]>
         </body>
       </method>
+
+      <!-- getBrowserForSelectedTab is required as some toolkit functions
+           require a getBrowser() function. -->
+      <method name="getBrowserForSelectedTab">
+        <body>
+          <![CDATA[
+            if (!this.currentTabInfo)
+              this.currentTabInfo = this.tabInfo[0];
+            let tabInfo = this.currentTabInfo;
+            let browserFunc = tabInfo.mode.getBrowser ||
+                              tabInfo.mode.tabType.getBrowser;
+            if (!browserFunc)
+              return null;
+            return browserFunc.call(tabInfo.mode.tabType, tabInfo);
+          ]]>
+        </body>
+      </method>
+
+      <!-- nsIObserver implementation -->
+
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body>
+          <![CDATA[
+            const kAutoHide = "browser.tabs.autoHide";
+            if (aTopic == "nsPref:changed" && aData == kAutoHide)
+              this.mAutoHide = this.mPrefs.getBoolPref(kAutoHide);
+          ]]>
+        </body>
+      </method>
+
+      <!-- nsIController implementation -->
+
       <method name="supportsCommand">
         <parameter name="aCommand"/>
         <body>
-           <![CDATA[
-             let tab = this.currentTabInfo;
-
-             // This can happen if we're starting up and haven't got a tab
-             // loaded yet.
-             if (!tab)
-               return false;
+          <![CDATA[
+            // return early on startup when we haven't got a tab loaded yet
+            let tabInfo = this.currentTabInfo;
+            if (!tabInfo)
+              return false;
 
-             let supportsCommandFunc = tab.mode.supportsCommand ||
-                                       tab.mode.tabType.supportsCommand;
-             if (supportsCommandFunc)
-               return supportsCommandFunc.call(tab.mode.tabType, tab, aCommand);
-
-             return false;
-           ]]>
+            let supportsCommandFunc = tabInfo.mode.supportsCommand ||
+                                      tabInfo.mode.tabType.supportsCommand;
+            if (!supportsCommandFunc)
+              return false;
+            return supportsCommandFunc.call(tabInfo.mode.tabType,
+                                            aCommand,
+                                            tabInfo);
+          ]]>
         </body>
       </method>
+
       <method name="isCommandEnabled">
         <parameter name="aCommand"/>
         <body>
-           <![CDATA[
-             let tab = this.currentTabInfo;
-
-             // This can happen if we're starting up and haven't got a tab
-             // loaded yet.
-             if (!tab)
-               return false;
+          <![CDATA[
+            // return early on startup when we haven't got a tab loaded yet
+            let tabInfo = this.currentTabInfo;
+            if (!tabInfo)
+              return false;
 
-             let isCommandEnabledFunc = tab.mode.isCommandEnabled ||
-                                        tab.mode.tabType.isCommandEnabled;
-             if (isCommandEnabledFunc)
-               return isCommandEnabledFunc.call(tab.mode.tabType, tab, aCommand);
-
-             return false;
-           ]]>
+            let isCommandEnabledFunc = tabInfo.mode.isCommandEnabled ||
+                                       tabInfo.mode.tabType.isCommandEnabled;
+            if (!isCommandEnabledFunc)
+              return false;
+            return isCommandEnabledFunc.call(tabInfo.mode.tabType,
+                                             aCommand,
+                                             tabInfo);
+          ]]>
         </body>
       </method>
+
       <method name="doCommand">
         <parameter name="aCommand"/>
         <body>
-           <![CDATA[
-             let tab = this.currentTabInfo;
+          <![CDATA[
+            // return early on startup when we haven't got a tab loaded yet
+            let tabInfo = this.currentTabInfo;
+            if (!tabInfo)
+              return;
 
-             // This can happen if we're starting up and haven't got a tab
-             // loaded yet.
-             if (!tab)
-               return;
-
-             let doCommandFunc = tab.mode.doCommand ||
-                                 tab.mode.tabType.doCommand;
-             if (doCommandFunc)
-               doCommandFunc.call(tab.mode.tabType, tab, aCommand);
+            let doCommandFunc = tabInfo.mode.doCommand ||
+                                tabInfo.mode.tabType.doCommand;
+            if (!doCommandFunc)
+              return;
+            doCommandFunc.call(tabInfo.mode.tabType,
+                               aCommand,
+                               tabInfo);
            ]]>
         </body>
       </method>
+
       <method name="onEvent">
         <parameter name="aEvent"/>
         <body>
-           <![CDATA[
-             let tab = this.currentTabInfo;
-
-             // This can happen if we're starting up and haven't got a tab
-             // loaded yet.
-             if (!tab)
-               return;
+          <![CDATA[
+            // return early on startup when we haven't got a tab loaded yet
+            let tabInfo = this.currentTabInfo;
+            if (!ttabInfo)
+              return;
 
-             let onEventFunc = tab.mode.onEvent ||
-                               tab.mode.tabType.onEvent;
-             if (onEventFunc)
-               return onEventFunc.call(tab.mode.tabType, tab, aEvent);
-
-             return false;
-           ]]>
+            let onEventFunc = tabInfo.mode.onEvent ||
+                              tabInfo.mode.tabType.onEvent;
+            if (!onEventFunc)
+              return;
+            return onEventFunc.call(tabInfo.mode.tabType,
+                                    aCommand,
+                                    tabInfo);
+          ]]>
         </body>
       </method>
     </implementation>
   </binding>
-  
- <binding id="tabmail-tab" display="xul:box"
+
+  <binding id="tabmail-tab"
+           display="xul:box"
            extends="chrome://global/content/bindings/tabbox.xml#tab">
     <content chromedir="&locale.dir;"
-             closetabtext="&closeTab.label;">
-      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
+             closetabtext="&tabmailClose.label;">
+      <xul:hbox class="tab-middle box-inherit"
+                xbl:inherits="align,dir,pack,orient,selected"
+                flex="1">
         <xul:image class="tab-icon" xbl:inherits="validate,src=image"/>
-        <xul:label class="tab-text" xbl:inherits="value=label,accesskey,crop,disabled" flex="1"/>
+        <xul:label class="tab-text"
+                   xbl:inherits="value=label,accesskey,crop,disabled"
+                   flex="1"/>
       </xul:hbox>
-      <xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button"/>
+      <xul:toolbarbutton anonid="close-button"
+                         tooltiptext="&tabmailClose.tooltip;"
+                         tabindex="-1"
+                         class="tabs-closebutton tab-close-button"/>
     </content>
 
     <implementation>
-      <field name="mOverCloseButton">false</field>
       <field name="mCorrespondingMenuitem">null</field>
     </implementation>
-
-    <handlers>
-      <handler event="mouseover">
-        var anonid = event.originalTarget.getAttribute("anonid");
-        if (anonid == "close-button")
-          this.mOverCloseButton = true;
-      </handler>
-      <handler event="mouseout">
-        var anonid = event.originalTarget.getAttribute("anonid");
-        if (anonid == "close-button")
-          this.mOverCloseButton = false;
-      </handler>
-      <handler event="mousedown" button="0" phase="capturing">
-      <![CDATA[
-        if (this.mOverCloseButton)
-          event.stopPropagation();
-      ]]>
-      </handler>
-    </handlers>
   </binding>
 
-  <binding id="tabmail-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+  <binding id="tabmail-arrowscrollbox"
+           extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
     <content>
-      <xul:toolbarbutton class="scrollbutton-up" collapsed="true"
+      <xul:toolbarbutton class="scrollbutton-up"
+                         collapsed="true"
                          xbl:inherits="orient"
                          anonid="scrollbutton-up"
                          onmousedown="_startScroll(-1);"
                          onmouseup="_stopScroll();"
                          onmouseout="_stopScroll();"
                          chromedir="&locale.dir;"/>
-      <xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
+      <xul:scrollbox xbl:inherits="orient,align,pack,dir"
+                     flex="1"
+                     anonid="scrollbox">
         <children/>
       </xul:scrollbox>
       <xul:stack align="center" pack="end" class="scrollbutton-down-stack">
-        <xul:hbox flex="1" class="scrollbutton-down-box" 
-                           collapsed="true" anonid="down-box"/>
-        <xul:hbox flex="1" class="scrollbutton-down-box-animate" 
-                           collapsed="true" anonid="down-box-animate"/>
-        <xul:toolbarbutton class="scrollbutton-down" collapsed="true"
+        <xul:hbox flex="1"
+                  class="scrollbutton-down-box"
+                  collapsed="true"
+                  anonid="down-box"/>
+        <xul:hbox flex="1"
+                  class="scrollbutton-down-box-animate"
+                  collapsed="true"
+                  anonid="down-box-animate"/>
+        <xul:toolbarbutton class="scrollbutton-down"
+                           collapsed="true"
                            xbl:inherits="orient"
                            anonid="scrollbutton-down"
                            onmousedown="_startScroll(1);"
                            onmouseup="_stopScroll();"
                            onmouseout="_stopScroll();"
                            chromedir="&locale.dir;"/>
       </xul:stack>
     </content>
+
     <implementation>
       <field name="_scrollButtonDownBox">
         document.getAnonymousElementByAttribute(this, "anonid", "down-box");
       </field>
       <field name="_scrollButtonDownBoxAnimate">
         document.getAnonymousElementByAttribute(this, "anonid", "down-box-animate");
       </field>
     </implementation>
+
     <handlers>
-      <handler event="underflow"><![CDATA[
-        // filter underflow events which were dispatched on nested scrollboxes
-        if (event.target != this)
-          return;
-
-        // Ignore vertical events.
-        if (event.detail == 0) {
-          return;
-        }
-
-        this._scrollButtonDownBox.collapsed = true;
-        this._scrollButtonDownBoxAnimate.collapsed = true;
-      ]]></handler>
-
-      <handler event="overflow"><![CDATA[
-        // filter underflow events which were dispatched on nested scrollboxes
-        if (event.target != this)
-          return;
+      <handler event="underflow" phase="target">
+        <![CDATA[
+          // Ignore vertical events.
+          if (event.detail == 0)
+            return;
+          this._scrollButtonDownBox.collapsed = true;
+          this._scrollButtonDownBoxAnimate.collapsed = true;
+        ]]>
+      </handler>
 
-        // Ignore vertical events.
-        if (event.detail == 0) {
-          return;
-        }
-
-        this._scrollButtonDownBox.collapsed = false;
-        this._scrollButtonDownBoxAnimate.collapsed = false;
-      ]]></handler>
+      <handler event="overflow" phase="target">
+        <![CDATA[
+          // Ignore vertical events.
+          if (event.detail == 0)
+            return;
+          this._scrollButtonDownBox.collapsed = false;
+          this._scrollButtonDownBoxAnimate.collapsed = false;
+        ]]>
+      </handler>
 
-      <handler event="UpdatedScrollButtonsDisabledState"><![CDATA[
-        // filter underflow events which were dispatched on nested scrollboxes
-        if (event.target != this)
-          return;
+      <handler event="UpdatedScrollButtonsDisabledState">
+        <![CDATA[
+          // filter underflow events which were dispatched on nested scrollboxes
+          if (event.target != this)
+            return;
 
-        // fix for bug #352353
-        // unlike the scrollup button on the tab strip (which is a 
-        // simple toolbarbutton) the scrolldown button is 
-        // a more complicated stack of boxes and a toolbarbutton
-        // so that we can animate when a tab is opened offscreen.
-        // in order to style the box with the actual background image
-        // we need to manually set the disable state to match the
-        // disable state of the toolbarbutton.
-        this._scrollButtonDownBox
-            .setAttribute("disabled", this._scrollButtonDown.disabled);
-      ]]></handler>
-
+          // fix for bug #352353
+          // unlike the scrollup button on the tab strip (which is a
+          // simple toolbarbutton) the scrolldown button is
+          // a more complicated stack of boxes and a toolbarbutton
+          // so that we can animate when a tab is opened offscreen.
+          // in order to style the box with the actual background image
+          // we need to manually set the disable state to match the
+          // disable state of the toolbarbutton.
+          this._scrollButtonDownBox
+              .setAttribute("disabled", this._scrollButtonDown.disabled);
+        ]]>
+      </handler>
     </handlers>
   </binding>
 
   <binding id="tabmail-tabs"
            extends="chrome://global/content/bindings/tabbox.xml#tabs">
     <content>
-      <xul:arrowscrollbox anonid="arrowscrollbox" class="tabmail-arrowscrollbox" flex="1"
-                          xbl:inherits="smoothscroll" orient="horizontal" style="min-width: 1px;">
-        <children includes="tab"/>
-      </xul:arrowscrollbox>
-      <children/>
-      <xul:stack align="center" pack="end" class="tabs-alltabs-stack">
-        <xul:hbox flex="1" class="tabs-alltabs-box" anonid="alltabs-box"/>
-        <xul:hbox flex="1" class="tabs-alltabs-box-animate" 
-                  anonid="alltabs-box-animate"/>
-        <xul:toolbarbutton class="tabs-alltabs-button" type="menu"
-                           anonid="alltabs-button"
-                           tooltipstring="&listAllTabs.label;">
-          <xul:menupopup class="tabs-alltabs-popup"
-                         anonid="alltabs-popup" 
-                         position="after_end"/>
-        </xul:toolbarbutton>
+      <xul:stack flex="1" class="tabs-stack">
+        <xul:vbox>
+          <xul:spacer flex="1"/>
+          <xul:hbox class="tabs-bottom" align="center"/>
+        </xul:vbox>
+        <xul:stack>
+          <xul:spacer class="tabs-left tabs-right"/>
+          <xul:hbox>
+            <xul:hbox class="tabs-newbutton-box"
+                      align="center"
+                      pack="start"
+                      anonid="tabstrip-newbutton">
+              <xul:toolbarbutton class="new-button tabs-newbutton"
+                                 tooltiptext="&tabmailNewButton.tooltip;"/>
+            </xul:hbox>
+            <xul:arrowscrollbox anonid="arrowscrollbox"
+                                class="tabbrowser-arrowscrollbox tabmail-arrowscrollbox"
+                                flex="1"
+                                xbl:inherits="smoothscroll"
+                                orient="horizontal"
+                                style="min-width: 1px;">
+              <children includes="tab"/>
+            </xul:arrowscrollbox>
+            <children/>
+            <xul:hbox class="tabs-closebutton-box"
+                      align="center"
+                      pack="end"
+                      anonid="tabstrip-closebutton">
+              <xul:toolbarbutton class="close-button tabs-closebutton"
+                                 tooltiptext="&tabmailCloseButton.tooltip;"/>
+            </xul:hbox>
+            <xul:stack align="center" pack="end" class="tabs-alltabs-stack">
+              <xul:hbox flex="1" class="tabs-alltabs-box" anonid="alltabs-box"/>
+              <xul:hbox flex="1"
+                        class="tabs-alltabs-box-animate"
+                        anonid="alltabs-box-animate"/>
+              <xul:toolbarbutton class="tabs-alltabs-button"
+                                 type="menu"
+                                 anonid="alltabs-button"
+                                 tooltipstring="&tabmailAllTabs.tooltip;">
+                <xul:menupopup class="menulist-menupopup tabs-alltabs-popup"
+                               anonid="alltabs-popup"
+                               position="after_end"/>
+              </xul:toolbarbutton>
+            </xul:stack>
+          </xul:hbox>
+        </xul:stack>
       </xul:stack>
-      <xul:hbox class="tabs-closebutton-box" align="center" pack="end" anonid="tabstrip-closebutton">
-        <xul:toolbarbutton class="close-button tabs-closebutton"/>
-      </xul:hbox>
     </content>
-    <implementation implements="nsITimerCallback, nsIDOMEventListener">
+
+    <implementation implements="nsITimerCallback, nsIDOMEventListener, nsIObserver">
       <constructor>
         <![CDATA[
-          var pb2 =
-              Components.classes['@mozilla.org/preferences-service;1'].
-              getService(Components.interfaces.nsIPrefBranch2);
-
-          try {
-            this.mTabMinWidth = pb2.getIntPref("mail.tabs.tabMinWidth");
-          } catch (e) {
-          }
-          try {
-            this.mTabMaxWidth = pb2.getIntPref("mail.tabs.tabMaxWidth");
-          } catch (e) {
-          }
-          try {
-            this.mTabClipWidth = pb2.getIntPref("mail.tabs.tabClipWidth");
-          } catch (e) {
-          }
-          try {
-            this.mCloseButtons = pb2.getIntPref("mail.tabs.closeButtons");
-          } catch (e) {
-          }
-          try {
-            this.mAutoHide = pb2.getBoolPref("mail.tabs.autoHide");
-          } catch (e) {
-          }
-
-          if (this.mAutoHide)
-            this.collapsed = true;
-
+          this.mTabMinWidth  = this.mPrefs.getIntPref ("browser.tabs.tabMinWidth");
+          this.mTabMaxWidth  = this.mPrefs.getIntPref ("browser.tabs.tabMaxWidth");
+          this.mTabClipWidth = this.mPrefs.getIntPref ("browser.tabs.tabClipWidth");
+          this.mCloseButtons = this.mPrefs.getIntPref ("browser.tabs.closeButtons");
           this.firstChild.minWidth = this.mTabMinWidth;
           this.firstChild.maxWidth = this.mTabMaxWidth;
           this.adjustTabstrip();
-
-          pb2.addObserver("mail.tabs.", this._prefObserver, false);
-
+          this.mPrefs.addObserver("browser.tabs.", this, false);
           window.addEventListener("resize", this, false);
 
           // Listen to overflow/underflow events on the tabstrip,
           // we cannot put these as xbl handlers on the entire binding because
           // they would also get called for the all-tabs popup scrollbox.
-          // Also, we can't rely on event.target becuase these are all
+          // Also, we can't rely on event.target because these are all
           // anonymous nodes.
-          this.mTabstrip.addEventListener("overflow", this, false);
+          this.mTabstrip.addEventListener("overflow",  this, false);
           this.mTabstrip.addEventListener("underflow", this, false);
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
-          var pb2 =
-              Components.classes['@mozilla.org/preferences-service;1'].
-              getService(Components.interfaces.nsIPrefBranch2);
-          pb2.removeObserver("mail.tabs.", this._prefObserver);
+          this.mPrefs.removeObserver("browser.tabs.", this);
 
           // Release timer to avoid reference cycles.
-          if (this._animateTimer) {
+          if (this._animateTimer)
+          {
             this._animateTimer.cancel();
             this._animateTimer = null;
           }
-
           this.mTabstrip.removeEventListener("overflow", this, false);
           this.mTabstrip.removeEventListener("underflow", this, false);
         ]]>
       </destructor>
 
+      <field name="mPrefs">
+        Components.classes["@mozilla.org/preferences-service;1"]
+                  .getService(Components.interfaces.nsIPrefBranch2);
+      </field>
+
       <field name="mTabstripWidth">0</field>
 
       <field name="mTabstrip">
         document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
       </field>
 
       <field name="mTabstripClosebutton">
         document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
       </field>
 
-      <field name="_prefObserver">({
-        tabbox: this,
-  
-        observe: function(subject, topic, data)
-        {
-          if (topic == "nsPref:changed") {
-            subject.QueryInterface(Components.interfaces.nsIPrefBranch);
-            switch (data) {
-            case "mail.tabs.closeButtons":
-              this.tabbox.mCloseButtons = subject.getIntPref("mail.tabs.closeButtons");
-              this.tabbox.adjustTabstrip();
-              break;
-            case "mail.tabs.autoHide":
-              this.tabbox.mAutoHide = subject.getBoolPref("mail.tabs.autoHide");
-              break;
-            }
-          }
-        },
-  
-        QueryInterface: function(aIID)
-        {
-          if (aIID.equals(Components.interfaces.nsIObserver) ||
-              aIID.equals(Components.interfaces.nsISupports))
-            return this;
-          throw Components.results.NS_NOINTERFACE;
-        }
-        });
-      </field>
       <field name="mTabMinWidth">100</field>
       <field name="mTabMaxWidth">250</field>
       <field name="mTabClipWidth">140</field>
-      <field name="mCloseButtons">1</field>
-
-      <field name="_mAutoHide">false</field>
-      <property name="mAutoHide" onget="return this._mAutoHide;">
-        <setter><![CDATA[
-          if (val != this._mAutoHide) {
-            if (this.childNodes.length == 1)
-              this.collapsed = val;
-            this._mAutoHide = val;
-          }
-          return val;
-        ]]></setter>
-      </property>
-
+      <field name="mCloseButtons">3</field>
       <method name="adjustTabstrip">
-        <body><![CDATA[
-          // modes for tabstrip
-          // 0 - activetab  = close button on active tab only
-          // 1 - alltabs    = close buttons on all tabs
-          // 2 - noclose    = no close buttons at all
-          // 3 - closeatend = close button at the end of the tabstrip
-          switch (this.mCloseButtons) {
-          case 0:
-            this.setAttribute("closebuttons", "activetab");
-            break;
-          case 1:
-            var width = this.firstChild.boxObject.width;
-            // 0 width is an invalid value and indicates
-            // an item without display, so ignore.
-            if (width > this.mTabClipWidth || width == 0)
-              this.setAttribute("closebuttons", "alltabs");
-            else
-              this.setAttribute("closebuttons", "activetab");
-            break;
-          case 2:
-          case 3:
-            this.setAttribute("closebuttons", "noclose");
-            break;
-          }
-          this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
-        ]]></body>
+        <body>
+          <![CDATA[
+            // modes for tabstrip
+            // 0 - activetab  = close button on active tab only
+            // 1 - alltabs    = close buttons on all tabs
+            // 2 - noclose    = no close buttons at all
+            // 3 - closeatend = close button at the end of the tabstrip
+            switch (this.mCloseButtons)
+            {
+              case 0:
+                this.setAttribute("closebuttons", "activetab");
+                break;
+              case 1:
+                let width = this.firstChild.boxObject.width;
+                // 0 width is an invalid value and indicates
+                // an item without display, so ignore.
+                if (width > this.mTabClipWidth || width == 0)
+                  this.setAttribute("closebuttons", "alltabs");
+                else
+                  this.setAttribute("closebuttons", "activetab");
+                break;
+              case 2:
+                this.setAttribute("closebuttons", "noclose");
+                break;
+              case 3:
+                this.setAttribute("closebuttons", "closeatend");
+                break;
+            }
+            this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
+          ]]>
+        </body>
       </method>
-        
-      <field name="_mPrefs">null</field>
-      <property name="mPrefs" readonly="true">
-        <getter>
-        <![CDATA[
-          if (!this._mPrefs) {
-            this._mPrefs =
-              Components.classes['@mozilla.org/preferences-service;1'].
-              getService(Components.interfaces.nsIPrefBranch2);
-          }
-          return this._mPrefs;
-        ]]>
-        </getter>
-      </property>
-        
+
       <method name="_handleTabSelect">
-        <body><![CDATA[
-          this.mTabstrip.ensureElementIsVisible(this.selectedItem);
-        ]]></body>
+        <body>
+          <![CDATA[
+            this.mTabstrip.ensureElementIsVisible(this.selectedItem);
+          ]]>
+        </body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
-        <body><![CDATA[
-          switch (aEvent.type) {
-            case "overflow":
-              this.setAttribute("overflow", "true");
-              this.mTabstrip.scrollBoxObject
-                            .ensureElementIsVisible(this.selectedItem);
-              break;
-            case "underflow":
-              this.removeAttribute("overflow");
-              break;
-            case "resize":
-              var width = this.mTabstrip.boxObject.width;
-              if (width != this.mTabstripWidth) {
-                this.adjustTabstrip();
-                // XXX without this line the tab bar won't budge
-                this.mTabstrip.scrollByPixels(1);
-                this._handleTabSelect();
-                this.mTabstripWidth = width;
-              }
-              break;
-          }
-        ]]></body>
+        <body>
+          <![CDATA[
+            switch (aEvent.type)
+            {
+              case "overflow":
+                this.setAttribute("overflow", "true");
+                this.mTabstrip.scrollBoxObject
+                              .ensureElementIsVisible(this.selectedItem);
+                break;
+              case "underflow":
+                this.removeAttribute("overflow");
+                break;
+              case "resize":
+                let width = this.mTabstrip.boxObject.width;
+                if (width != this.mTabstripWidth)
+                {
+                  this.adjustTabstrip();
+                  // XXX without this line the tab bar won't budge
+                  this.mTabstrip.scrollByPixels(1);
+                  this._handleTabSelect();
+                  this.mTabstripWidth = width;
+                }
+                break;
+            }
+          ]]>
+        </body>
       </method>
 
       <field name="mAllTabsPopup">
-        document.getAnonymousElementByAttribute(this, 
-                                                "anonid", "alltabs-popup");
+        document.getAnonymousElementByAttribute(this, "anonid", "alltabs-popup");
       </field>
 
       <field name="mAllTabsBoxAnimate">
-        document.getAnonymousElementByAttribute(this, 
-                                                "anonid",
-                                                "alltabs-box-animate");
+        document.getAnonymousElementByAttribute(this, "anonid", "alltabs-box-animate");
       </field>
 
       <field name="mDownBoxAnimate">
         this.mTabstrip._scrollButtonDownBoxAnimate;
       </field>
 
       <field name="mAllTabsButton">
-        document.getAnonymousElementByAttribute(this, 
-                                                "anonid", "alltabs-button");
+        document.getAnonymousElementByAttribute(this, "anonid", "alltabs-button");
       </field>
 
       <field name="_animateTimer">null</field>
       <field name="_animateStep">-1</field>
       <field name="_animateDelay">25</field>
       <field name="_animatePercents">
        [1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
         0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
         0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
         0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
         0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
         0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
       </field>
 
       <method name="_stopAnimation">
-        <body><![CDATA[
-          if (this._animateStep != -1) {
-            if (this._animateTimer)
-              this._animateTimer.cancel();
+        <body>
+          <![CDATA[
+            if (this._animateStep != -1)
+            {
+              if (this._animateTimer)
+                this._animateTimer.cancel();
 
-            this._animateStep = -1;
-            this.mAllTabsBoxAnimate.style.opacity = 0.0;
-            this.mDownBoxAnimate.style.opacity = 0.0;
-          }
-        ]]></body>
+              this._animateStep = -1;
+              this.mAllTabsBoxAnimate.style.opacity = 0.0;
+              this.mDownBoxAnimate.style.opacity = 0.0;
+            }
+          ]]>
+        </body>
       </method>
-      
-      <method name="_notifyBackgroundTab">
-        <parameter name="aTab"/>
-        <body><![CDATA[
-          var tsbo = this.mTabstrip.scrollBoxObject;
-          var tsboStart = tsbo.screenX;
-          var tsboEnd = tsboStart + tsbo.width;
 
-          var ctbo = aTab.boxObject;
-          var ctboStart = ctbo.screenX;
-          var ctboEnd = ctboStart + ctbo.width;
+      <method name="_notifyBackgroundTab">
+        <parameter name="aTabNode"/>
+        <body>
+          <![CDATA[
+            let tsbo = this.mTabstrip.scrollBoxObject;
+            let tsboStart = tsbo.screenX;
+            let tsboEnd = tsboStart + tsbo.width;
+            let ctbo = aTabNode.boxObject;
+            let ctboStart = ctbo.screenX;
+            let ctboEnd = ctboStart + ctbo.width;
 
-          // only start the flash timer if the new tab (which was loaded in
-          // the background) is not completely visible
-          if (tsboStart > ctboStart || ctboEnd > tsboEnd) {
-            this._animateStep = 0;
+            // only start the flash timer if the new tab (which was loaded in
+            // the background) is not completely visible
+            if (tsboStart > ctboStart || ctboEnd > tsboEnd)
+            {
+              this._animateStep = 0;
 
-            if (!this._animateTimer) 
-              this._animateTimer =
-                Components.classes["@mozilla.org/timer;1"]
-                          .createInstance(Components.interfaces.nsITimer);
-            else
-               this._animateTimer.cancel();
+              if (!this._animateTimer)
+
+                this._animateTimer =
+                  Components.classes["@mozilla.org/timer;1"]
+                            .createInstance(Components.interfaces.nsITimer);
+              else
+                 this._animateTimer.cancel();
 
-            this._animateTimer.initWithCallback(this,
-                         this._animateDelay,
-                         Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
-          }
-        ]]></body>
+              this._animateTimer.initWithCallback(this,
+                           this._animateDelay,
+                           Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+            }
+          ]]>
+        </body>
       </method>
-      
+
       <method name="notify">
         <parameter name="aTimer"/>
-        <body><![CDATA[
-          if (!document)
-            aTimer.cancel();
+        <body>
+          <![CDATA[
+            if (!document)
+              aTimer.cancel();
+
+            let percent = this._animatePercents[this._animateStep];
+            this.mAllTabsBoxAnimate.style.opacity = percent;
+            this.mDownBoxAnimate.style.opacity = percent;
+
+            if (this._animateStep < (this._animatePercents.length - 1))
+              this._animateStep++;
+            else
+              this._stopAnimation();
+          ]]>
+        </body>
+      </method>
 
-          var percent = this._animatePercents[this._animateStep];
-          this.mAllTabsBoxAnimate.style.opacity = percent;
-          this.mDownBoxAnimate.style.opacity = percent;
+      <!-- nsIObserver implementation -->
 
-          if (this._animateStep < (this._animatePercents.length - 1))
-            this._animateStep++;
-          else
-            this._stopAnimation();
-        ]]></body>
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body>
+          <![CDATA[
+            const kCloseButtons = "browser.tabs.closeButtons";
+            if (aTopic == "nsPref:changed" && aData == kCloseButtons)
+            {
+              this.mCloseButtons = this.mPrefs.getIntPref(kCloseButtons);
+              this.adjustTabstrip();
+            }
+          ]]>
+        </body>
       </method>
     </implementation>
+
     <handlers>
       <handler event="TabSelect" action="this._handleTabSelect();"/>
-      <handler event="mouseover"><![CDATA[
-        if (event.originalTarget == this.mAllTabsButton) {
-          this.mAllTabsButton
-              .setAttribute("tooltiptext",
-                            this.mAllTabsButton.getAttribute("tooltipstring"));
-        }
-        else
-          this.mAllTabsButton.removeAttribute("tooltiptext");
-      ]]></handler>
+
+      <handler event="mouseover">
+        <![CDATA[
+          if (event.originalTarget == this.mAllTabsButton)
+          {
+            this.mAllTabsButton
+                .setAttribute("tooltiptext",
+                              this.mAllTabsButton.getAttribute("tooltipstring"));
+          }
+          else
+          {
+            this.mAllTabsButton.removeAttribute("tooltiptext");
+          }
+        ]]>
+      </handler>
     </handlers>
   </binding>
-  
+
   <!-- alltabs-popup binding
        This binding relies on the structure of the tabbrowser binding.
        Therefore it should only be used as a child of the tabs element.
        This binding is exposed as a pseudo-public-API so themes can customize
        the tabbar appearance without having to be scriptable
        (see globalBindings.xml in Pinstripe for example).
   -->
   <binding id="tabmail-alltabs-popup"
            extends="chrome://global/content/bindings/popup.xml#popup">
     <implementation implements="nsIDOMEventListener">
-      <field name="_xulWindow">
-        null
-      </field>
-
-      <constructor><![CDATA[
-        // We cannot cache the XULBrowserWindow object itself since it might
-        // be set after this binding is constructed.
-        try {
-          this._xulWindow = 
-            window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                  .getInterface(Components.interfaces.nsIWebNavigation)
-                  .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
-                  .treeOwner
-                  .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                  .getInterface(Components.interfaces.nsIXULWindow);
-        }
-        catch(ex) { }
-      ]]></constructor>
-
-      <method name="_menuItemOnCommand">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          var tabcontainer = document.getBindingParent(this);
-          tabcontainer.selectedItem = aEvent.target.tab;
-        ]]></body>
-      </method>
-
-      <method name="_tabOnAttrModified">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          var menuItem = aEvent.target.mCorrespondingMenuitem;
-          if (menuItem) {
-            var attrName = aEvent.attrName;
-            switch (attrName) {
-              case "label":
-              case "crop":
-              case "busy":
-              case "image":
-              case "selected":
-                if (aEvent.attrChange == aEvent.REMOVAL)
-                  menuItem.removeAttribute(attrName);
-                else
-                  menuItem.setAttribute(attrName, aEvent.newValue);
-            }
-          }
-        ]]></body>
-      </method>
-
       <method name="_tabOnTabClose">
         <parameter name="aEvent"/>
-        <body><![CDATA[
-          var menuItem = aEvent.target.mCorrespondingMenuitem;
-          if (menuItem)
-            this.removeChild(menuItem);
-        ]]></body>
+        <body>
+          <![CDATA[
+            let menuItem = aEvent.target.mCorrespondingMenuitem;
+            if (menuItem)
+              this.removeChild(menuItem);
+          ]]>
+        </body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (!aEvent.isTrusted)
-            return;
-
-          switch (aEvent.type) {
-            case "command":
-              this._menuItemOnCommand(aEvent);
-              break;
-            case "DOMAttrModified":
-              this._tabOnAttrModified(aEvent);
-              break;
-            case "TabClose":
-              this._tabOnTabClose(aEvent);
-              break;
-            case "TabOpen":
-              this._createTabMenuItem(aEvent.originalTarget);
-              break;
-            case "scroll":
-              this._updateTabsVisibilityStatus();
-              break;
-          }
-        ]]></body>
+        <body>
+          <![CDATA[
+            switch (aEvent.type)
+            {
+              case "TabClose":
+                this._tabOnTabClose(aEvent);
+                break;
+              case "TabOpen":
+                this._createTabMenuItem(aEvent.originalTarget);
+                break;
+              case "scroll":
+                this._updateTabsVisibilityStatus();
+                break;
+            }
+          ]]>
+        </body>
       </method>
 
       <method name="_updateTabsVisibilityStatus">
-        <body><![CDATA[
-          var tabContainer = document.getBindingParent(this);
-          // We don't want menu item decoration unless there is overflow.
-          if (tabContainer.getAttribute("overflow") != "true")
-            return;
+        <body>
+          <![CDATA[
+            let tabContainer = document.getBindingParent(this);
+            // We don't want menu item decoration unless there is overflow.
+            if (tabContainer.getAttribute("overflow") != "true")
+              return;
 
-          var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
-          for (var i = 0; i < this.childNodes.length; i++) {
-            var curTabBO = this.childNodes[i].tab.boxObject;
-            if (curTabBO.screenX >= tabstripBO.screenX &&
-                curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
-              this.childNodes[i].setAttribute("tabIsVisible", "true"); 
-            else
-              this.childNodes[i].removeAttribute("tabIsVisible");
-          }
-        ]]></body>
+            let tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
+            for (let i = 0; i < this.childNodes.length; i++)
+            {
+              let curTabBO = this.childNodes[i].tab.boxObject;
+              if (curTabBO.screenX >= tabstripBO.screenX &&
+                  curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+                this.childNodes[i].setAttribute("tabIsVisible", "true");
+              else
+                this.childNodes[i].removeAttribute("tabIsVisible");
+            }
+          ]]>
+        </body>
       </method>
 
       <method name="_createTabMenuItem">
-        <parameter name="aTab"/>
-        <body><![CDATA[
-          var menuItem = document.createElementNS(
-            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 
-            "menuitem");
+        <parameter name="aTabNode"/>
+        <body>
+          <![CDATA[
+            let menuItem = document.createElementNS(
+              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+              "menuitem");
+            menuItem.setAttribute("class", "menuitem-iconic alltabs-item");
+            menuItem.setAttribute("label", aTabNode.label);
+            menuItem.setAttribute("crop",  aTabNode.getAttribute("crop"));
+            menuItem.setAttribute("image", aTabNode.getAttribute("image"));
 
-          menuItem.setAttribute("class", "menuitem-iconic alltabs-item");
-
-          menuItem.setAttribute("label", aTab.label);
-          menuItem.setAttribute("crop", aTab.getAttribute("crop"));
-          menuItem.setAttribute("image", aTab.getAttribute("image"));
+            if (aTabNode.hasAttribute("busy"))
+              menuItem.setAttribute("busy", aTabNode.getAttribute("busy"));
+            if (aTabNode.selected)
+              menuItem.setAttribute("selected", "true");
 
-          if (aTab.hasAttribute("busy"))
-            menuItem.setAttribute("busy", aTab.getAttribute("busy"));
-          if (aTab.selected)
-            menuItem.setAttribute("selected", "true");
-
-          // Keep some attributes of the menuitem in sync with its
-          // corresponding tab (e.g. the tab label)
-          aTab.mCorrespondingMenuitem = menuItem;
-          aTab.addEventListener("DOMAttrModified", this, false);
-          aTab.addEventListener("TabClose", this, false);
-          menuItem.tab = aTab;
-          menuItem.addEventListener("command", this, false);
-
-          this.appendChild(menuItem);
-          return menuItem;
-        ]]></body>
+            // Keep some attributes of the menuitem in sync with its
+            // corresponding tab (e.g. the tab label)
+            aTabNode.mCorrespondingMenuitem = menuItem;
+            document.addBroadcastListenerFor(aTabNode, menuItem, "label");
+            document.addBroadcastListenerFor(aTabNode, menuItem, "crop");
+            document.addBroadcastListenerFor(aTabNode, menuItem, "image");
+            document.addBroadcastListenerFor(aTabNode, menuItem, "busy");
+            document.addBroadcastListenerFor(aTabNode, menuItem, "selected");
+            aTabNode.addEventListener("TabClose", this, false);
+            menuItem.tab = aTabNode;
+            menuItem.addEventListener("command", this, false);
+            this.appendChild(menuItem);
+            return menuItem;
+          ]]>
+        </body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="popupshowing">
-      <![CDATA[
-        // set up the menu popup
-        var tabcontainer = document.getBindingParent(this);
-        var tabs = tabcontainer.childNodes;
+        <![CDATA[
+          // set up the menu popup
+          let tabcontainer = document.getBindingParent(this);
+          let tabs = tabcontainer.childNodes;
 
-        // Listen for changes in the tab bar.
-        var tabbrowser = document.getBindingParent(tabcontainer);
-        tabbrowser.addEventListener("TabOpen", this, false);
-        tabcontainer.mTabstrip.addEventListener("scroll", this, false);
+          // Listen for changes in the tab bar.
+          let tabbrowser = document.getBindingParent(tabcontainer);
+          tabbrowser.addEventListener("TabOpen", this, false);
+          tabcontainer.mTabstrip.addEventListener("scroll", this, false);
 
-        // if an animation is in progress and the user
-        // clicks on the "all tabs" button, stop the animation
-        tabcontainer._stopAnimation();
+          // if an animation is in progress and the user
+          // clicks on the "all tabs" button, stop the animation
+          tabcontainer._stopAnimation();
 
-        for (var i = 0; i < tabs.length; i++) {
-          this._createTabMenuItem(tabs[i]);
-        }
-        this._updateTabsVisibilityStatus();
-      ]]></handler>
+          for (let i = 0; i < tabs.length; i++)
+            this._createTabMenuItem(tabs[i]);
+          this._updateTabsVisibilityStatus();
+        ]]>
+      </handler>
 
       <handler event="popuphiding">
-      <![CDATA[
-        // clear out the menu popup and remove the listeners
-        while (this.hasChildNodes()) {
-          var menuItem = this.lastChild;
-          menuItem.removeEventListener("command", this, false);
-          menuItem.tab.removeEventListener("DOMAttrModified", this, false);
-          menuItem.tab.removeEventListener("TabClose", this, false);
-          menuItem.tab.mCorrespondingMenuitem = null;
-          this.removeChild(menuItem);
-        }
-        var tabcontainer = document.getBindingParent(this);
-        tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
-        document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this, false);
-      ]]></handler>
+        <![CDATA[
+          // clear out the menu popup and remove the listeners
+          while (this.hasChildNodes())
+          {
+            let menuItem = this.lastChild;
+            document.removeBroadcastListenerFor(menuItem.tab, menuItem, "label");
+            document.removeBroadcastListenerFor(menuItem.tab, menuItem, "crop");
+            document.removeBroadcastListenerFor(menuItem.tab, menuItem, "image");
+            document.removeBroadcastListenerFor(menuItem.tab, menuItem, "busy");
+            document.removeBroadcastListenerFor(menuItem.tab, menuItem, "selected");
+            menuItem.removeEventListener("command", this, false);
+            menuItem.tab.removeEventListener("TabClose", this, false);
+            menuItem.tab.mCorrespondingMenuitem = null;
+            this.removeChild(menuItem);
+          }
+          let tabcontainer = document.getBindingParent(this);
+          tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
+          document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this, false);
+        ]]>
+      </handler>
+
+      <handler event="command">
+        <![CDATA[
+          let tabcontainer = document.getBindingParent(this);
+          tabcontainer.selectedItem = event.target.tab;
+        ]]>
+      </handler>
     </handlers>
   </binding>
-  <!-- close-tab-button binding
-       This binding relies on the structure of the tabbrowser binding.
-       Therefore it should only be used as a child of the tab or the tabs
+
+  <!-- new-tab-button/close-tab-button binding
+       These bindings rely on the structure of the tabbrowser binding.
+       Therefore they should only be used as a child of the tab or the tabs
        element (in both cases, when they are anonymous nodes of <tabbrowser>).
-       This binding is exposed as a pseudo-public-API so themes can customize
+       These bindings are exposed as pseudo-public-APIs, so themes can customize
        the tabbar appearance without having to be scriptable
        (see globalBindings.xml in Pinstripe for example).
   -->
+  <binding id="tabmail-new-tab-button"
+           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
+    <handlers>
+      <handler event="command">
+        <![CDATA[
+          let bindingParent = document.getBindingParent(this);
+          if (bindingParent)
+          {
+            let tabmail = document.getBindingParent(bindingParent);
+            if (bindingParent.localName == "tabs")
+            {
+              // new-tab-button only appears in the tabstrip
+              // duplicate the current tab
+              tabmail.openTab();
+            }
+          }
+        ]]>
+      </handler>
+      <handler event="dblclick" button="0" phase="capturing">
+        <![CDATA[
+          event.stopPropagation();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
   <binding id="tabmail-close-tab-button"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
     <handlers>
-      <handler event="click" button="0"><![CDATA[
-        var bindingParent = document.getBindingParent(this);
-        if (bindingParent) {
-          var tabbedBrowser = document.getBindingParent(bindingParent);
-          if (bindingParent.localName == "tab") {
-            /* The only sequence in which a second click event (i.e. dblclik)
-             * can be dispatched on an in-tab close button is when it is shown
-             * after the first click (i.e. the first click event was dispatched
-             * on the tab). This happens when we show the close button only on
-             * the active tab. (bug 352021)
-             * The only sequence in which a third click event can be dispatched
-             * on an in-tab close button is when the tab was opened with a
-             * double click on the tabbar. (bug 378344)
-             * In both cases, it is most likely that the close button area has
-             * been accidentally clicked, therefore we do not close the tab.
-             */
-            if (event.detail > 1)
-              return;
+      <handler event="command">
+        <![CDATA[
+          let bindingParent = document.getBindingParent(this);
+          if (bindingParent)
+          {
+            let tabmail = document.getBindingParent(bindingParent);
+            if (bindingParent.localName == "tab")
+            {
+              /* The only sequence in which a second click event (i.e. dblclik)
+               * can be dispatched on an in-tab close button is when it is shown
+               * after the first click (i.e. the first click event was dispatched
+               * on the tab). This happens when we show the close button only on
+               * the active tab. (bug 352021)
+               * The only sequence in which a third click event can be dispatched
+               * on an in-tab close button is when the tab was opened with a
+               * double click on the tabbar. (bug 378344)
+               * In both cases, it is most likely that the close button area has
+               * been accidentally clicked, therefore we do not close the tab.
+               */
+              if (event.detail > 1)
+                return;
 
-            tabbedBrowser.removeTabByNode(bindingParent);
-            tabbedBrowser._blockDblClick = true;
+              tabmail.removeTab(bindingParent);
+              tabmail._blockDblClick = true;
 
-            /* XXXmano hack (see bug 343628):
-             * Since we're removing the event target, if the user
-             * double-clicks this button, the dblclick event will be dispatched
-             * with the tabbar as its event target (and explicit/originalTarget),
-             * which treats that as a mouse gesture for opening a new tab.
-             * In this context, we're manually blocking the dblclick event
-             * (see onTabBarDblClick).
-             */
-            var clickedOnce = false;
-            function enableDblClick(event) {
-              if (event.detail == 1 && !clickedOnce) {
-                clickedOnce = true;
-                return;
+              /* XXXmano hack (see bug 343628):
+               * Since we're removing the event target, if the user
+               * double-clicks this button, the dblclick event will be dispatched
+               * with the tabbar as its event target (and explicit/originalTarget),
+               * which treats that as a mouse gesture for opening a new tab.
+               * In this context, we're manually blocking the dblclick event
+               * (see onTabBarDblClick).
+               */
+              let clickedOnce = false;
+              function enableDblClick(event)
+              {
+                if (event.detail == 1 && !clickedOnce)
+                {
+                  clickedOnce = true;
+                  return;
+                }
+                setTimeout(function()
+                {
+                  tabmail._blockDblClick = false;
+                }, 0);
+                tabmail.removeEventListener("click", enableDblClick, false);
               }
-              setTimeout(function() {
-                tabbedBrowser._blockDblClick = false;
-              }, 0);
-              tabbedBrowser.removeEventListener("click", enableDblClick, false);
+              tabmail.addEventListener("click", enableDblClick, false);
             }
-            tabbedBrowser.addEventListener("click", enableDblClick, false);
+            else
+            {
+              // "tabs"
+              tabmail.removeCurrentTab();
+            }
           }
-          else // "tabs"
-            tabbedBrowser.removeCurrentTab();
-        }
-      ]]></handler>
+        ]]>
+      </handler>
       <handler event="dblclick" button="0" phase="capturing">
-        // for the one-close-button case
-        event.stopPropagation();
+        <![CDATA[
+          // for the one-close-button case
+          event.stopPropagation();
+        ]]>
       </handler>
     </handlers>
   </binding>
 
 </bindings>
--- a/suite/mailnews/threadPane.js
+++ b/suite/mailnews/threadPane.js
@@ -1,10 +1,10 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -16,16 +16,17 @@
  * March 31, 1998.
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998-1999
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Karsten Düsterloh <mnyromyr@tprac.de>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -36,63 +37,74 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 var gLastMessageUriToLoad = null;
 var gThreadPaneCommandUpdater = null;
 
 function ThreadPaneOnClick(event)
 {
-    // we only care about button 0 (left click) events
-    if (event.button != 0) return;
+  // we may want to open the message in a new tab on middle click
+  if (event.button == kMouseButtonMiddle)
+  {
+    if (AllowOpenTabOnMiddleClick())
+    {
+      // we don't allow new tabs in the search dialog
+      if ("RestoreSelectionWithoutContentLoad" in window)
+      {
+        MsgOpenNewTabForMessage();
+        RestoreSelectionWithoutContentLoad(GetThreadTree());
+      }
+      return;
+    }
+  }
 
-    // we are already handling marking as read and flagging
-    // in nsMsgDBView.cpp
-    // so all we need to worry about here is double clicks
-    // and column header.
-    //
-    // we get in here for clicks on the "treecol" (headers)
-    // and the "scrollbarbutton" (scrollbar buttons)
-    // we don't want those events to cause a "double click"
-
-    var t = event.originalTarget;
-
-    if (t.localName == "treecol") {
-       HandleColumnClick(t.id);
-    }
-    else if (t.localName == "treechildren") {
-      var row = new Object;
-      var col = new Object;
-      var childElt = new Object;
+  // otherwise, we only care about left click events
+  if (event.button != kMouseButtonLeft)
+    return;
 
-      var tree = GetThreadTree();
-      // figure out what cell the click was in
-      tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, childElt);
-      if (row.value == -1)
-       return;
+  // We are already handling marking as read and flagging in nsMsgDBView.cpp,
+  // so all we need to worry about here is double clicks and column header.
+  // We also get in here for clicks on the "treecol" (headers) and the
+  // "scrollbarbutton" (scrollbar buttons), but we don't want those events to
+  // cause a "double click".
+  var t = event.originalTarget;
+  if (t.localName == "treecol")
+  {
+    HandleColumnClick(t.id);
+  }
+  else if (t.localName == "treechildren")
+  {
+    let row = {}, col = {}, childElt = {};
+    let tree = GetThreadTree();
+    // figure out what cell the click was in
+    tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, childElt);
+    if (row.value == -1)
+      return;
 
-      // if the cell is in a "cycler" column
-      // or if the user double clicked on the twisty,
-      // don't open the message in a new window
-      if (event.detail == 2 && !col.value.cycler && (childElt.value != "twisty")) {
-        ThreadPaneDoubleClick();
-        // double clicking should not toggle the open / close state of the 
-        // thread.  this will happen if we don't prevent the event from
-        // bubbling to the default handler in tree.xml
-        event.stopPropagation();
-      }
-      else if (col.value.id == "junkStatusCol") {
-        MsgJunkMailInfo(true);
-      }
-      else if (col.value.id == "threadCol" && !event.shiftKey &&
-          (event.ctrlKey || event.metaKey)) {
-        gDBView.ExpandAndSelectThreadByIndex(row.value, true);
-        event.stopPropagation();
-      }
+    // If the cell is in a "cycler" column or if the user double clicked on the
+    // twisty, don't open the message in a new window.
+    if (event.detail == 2 && !col.value.cycler && (childElt.value != "twisty"))
+    {
+      ThreadPaneDoubleClick();
+      // Double clicking should not toggle the open/close state of the thread.
+      // This will happen if we don't prevent the event from bubbling to the
+      // default handler in tree.xml.
+      event.stopPropagation();
     }
+    else if (col.value.id == "junkStatusCol")
+    {
+      MsgJunkMailInfo(true);
+    }
+    else if (col.value.id == "threadCol" && !event.shiftKey && (event.ctrlKey || event.metaKey))
+    {
+      gDBView.ExpandAndSelectThreadByIndex(row.value, true);
+      event.stopPropagation();
+    }
+  }
 }
 
 function nsMsgDBViewCommandUpdater()
 {}
 
 nsMsgDBViewCommandUpdater.prototype = 
 {
   updateCommandStatus : function()
@@ -197,28 +209,36 @@ function HandleColumnClick(columnID)
       MsgSortThreadPane(sortType);
     }
   }
 }
 
 function ThreadPaneDoubleClick()
 {
   const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
-  if (IsSpecialFolderSelected(nsMsgFolderFlags.Drafts, true)) {
+  if (IsSpecialFolderSelected(nsMsgFolderFlags.Drafts, true))
+  {
     MsgComposeDraftMessage();
   }
-  else if(IsSpecialFolderSelected(nsMsgFolderFlags.Templates, true)) {
+  else if(IsSpecialFolderSelected(nsMsgFolderFlags.Templates, true))
+  {
     var loadedFolder = GetLoadedMsgFolder();
     var messageArray = GetSelectedMessages();
-
     ComposeMessage(Components.interfaces.nsIMsgCompType.Template,
                    Components.interfaces.nsIMsgCompFormat.Default,
                    loadedFolder, messageArray);
   }
-  else {
+  else if (AllowOpenTabOnDoubleClick())
+  {
+    // open the message in a new tab on double click
+    MsgOpenNewTabForMessage();
+    RestoreSelectionWithoutContentLoad(GetThreadTree());
+  }
+  else
+  {
     MsgOpenSelectedMessages();
   }
 }
 
 function ThreadPaneKeyPress(event)
 {
   if (event.keyCode == KeyEvent.DOM_VK_RETURN)
     ThreadPaneDoubleClick();
@@ -412,17 +432,17 @@ function UpdateSortIndicators(sortType, 
     else {
       sortedColumn.setAttribute("sortDirection","descending");
     }
   }
 }
 
 function IsSpecialFolderSelected(flags, checkAncestors)
 {
-  let folder = GetThreadPaneFolder();
+  var folder = GetThreadPaneFolder();
   return folder && folder.isSpecialFolder(flags, checkAncestors);
 }
 
 function GetThreadTree()
 {
   return document.getElementById("threadTree")
 }
 
@@ -467,17 +487,17 @@ function ThreadPaneOnLoad()
   tree.addEventListener("click",ThreadPaneOnClick,true);
 
   // The mousedown event listener below should only be added in the thread
   // pane of the mailnews 3pane window, not in the advanced search window.
   if(tree.parentNode.id == "searchResultListBox")
     return;
 
   tree.addEventListener("mousedown",TreeOnMouseDown,true);
-  let delay = pref.getIntPref("mailnews.threadpane_select_delay");
+  var delay = pref.getIntPref("mailnews.threadpane_select_delay");
   document.getElementById("threadTree")._selectDelay = delay;
 }
 
 function ThreadPaneSelectionChanged()
 {
   UpdateStatusMessageCounts(gMsgFolderSelected);
   if (!gRightMouseButtonDown)
     GetThreadTree().view.selectionChanged();
--- a/suite/mailnews/widgetglue.js
+++ b/suite/mailnews/widgetglue.js
@@ -1,10 +1,10 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** 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,
@@ -255,32 +255,16 @@ function RebuildSummaryFile(msgFolder)
 }
 
 function FolderProperties(name, oldName, uri)
 {
   if (name != oldName)
     RenameFolder(name, uri);
 }
 
-function MsgToggleMessagePane()
-{
-  MsgToggleSplitter("threadpane-splitter");
-  OnClickThreadAndMessagePaneSplitter();
-}
-
-function MsgToggleSplitter(id)
-{
-    var splitter = document.getElementById(id);
-    var state = splitter.getAttribute("state");
-    if (state == "collapsed")
-        splitter.setAttribute("state", null);
-    else
-        splitter.setAttribute("state", "collapsed")
-}
-
 function MsgSetFolderCharset() 
 {
   MsgFolderProperties() 
 }
 
 // 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
--- a/suite/themes/classic/mac/messenger/mailWindow1.css
+++ b/suite/themes/classic/mac/messenger/mailWindow1.css
@@ -80,26 +80,26 @@
   background-color: #CDCDCD;
   background-image: url("chrome://communicator/skin/toolbar-gradient34.png");
   background-repeat: repeat-x;
   border-bottom: 1px solid #9F9F9F;
 }
 
 /* Leopard-styled "zero-width" vertical splitter */
 
-#gray_vertical_splitter {
+#folderpane_splitter {
   border: solid #B3B3B3;
   border-width: 0 1px 0 0;
   min-width: 1px;
   background-image: none;
 }
 
 /* Make sure users find the splitter when it's collapsed... */
 
-#gray_vertical_splitter[state="collapsed"] {
+#folderpane_splitter[state="collapsed"] {
   min-width: 5px;
   background-color: #D6DDE5;
   cursor: e-resize;
 }
 
 /* Thick horisontal splitter */
 
 #threadpane-splitter {
@@ -168,8 +168,24 @@
 #messagepane {
   border: 1px solid transparent;
   border-right: none;
 }
 
 #messagepanebox[focusring="true"] > #messagepane {
   border-color: -moz-mac-focusring;
 }
+
+.tab-close-button {
+  margin: 0;
+}
+
+.tabmail-strip {
+  border-bottom: 1px solid #A1A1A1;
+  padding-bottom: 3px;
+  background-color: #E8E8E8;
+}
+
+/* ..... tabmail ..... */
+
+.tab-close-button {
+  margin: 0;
+}
--- a/suite/themes/classic/mac/navigator/tabbrowser.css
+++ b/suite/themes/classic/mac/navigator/tabbrowser.css
@@ -59,17 +59,22 @@ tabpanels {
 .tabbrowser-tab:not(:first-child) {
   -moz-margin-start: -3px;
 }
 
 .tab-middle {
   padding: 4px 7px;
 }
 
-.tabbrowser-tab:hover > .tab-middle {
+.tab-middle[selected="true"] {
+  padding: 1px 7px 4px;
+  background-color: #E8E8E8;
+}
+
+.tabbrowser-tab:hover > .tab-middle:not([selected="true"]) {
   background-color: rgba(0, 0, 0, 0.1);
 }
 
 .tabbrowser-tab[busy] {
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
 
 .tabbrowser-tab[selected="true"] {
@@ -83,21 +88,16 @@ tabpanels {
 }
 
 .tabbrowser-tab:focus {
   outline: 2px solid -moz-mac-focusring;
   outline-offset: -2px;
   -moz-outline-radius: 5px;
 }
 
-.tab-middle[selected="true"] {
-  padding: 1px 7px 4px;
-  -moz-appearance: dialog;
-}
-
 .tabbrowser-tab[beforeselected="true"]:not([chromedir="rtl"]) {
   -moz-border-right-colors: transparent transparent;
 }
 
 .tabbrowser-tab[beforeselected="true"][chromedir="rtl"] {
   -moz-border-left-colors: transparent transparent;
 }
 
--- a/suite/themes/classic/messenger/folderPane.css
+++ b/suite/themes/classic/messenger/folderPane.css
@@ -44,144 +44,174 @@
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* ::::: mail folder ::::: */
 
 treechildren::-moz-tree-image(folderNameCol) {
   -moz-margin-end: 2px;
+}
+
+.tabmail-tab[type="folder"],
+treechildren::-moz-tree-image(folderNameCol) {
   list-style-image: url("chrome://messenger/skin/icons/folder-closed.png");
 }
 
+.tabmail-tab[type="folder"][NewMessages="true"],
 treechildren::-moz-tree-image(folderNameCol, newMessages-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-new.png");
 }
 
 /* ..... News and Feeds ..... */
 
+.tabmail-tab[type="folder"][ServerType="nntp"],
+.tabmail-tab[type="folder"][ServerType="rss"],
 treechildren::-moz-tree-image(folderNameCol, serverType-nntp),
 treechildren::-moz-tree-image(folderNameCol, serverType-rss) {
   list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup.png");
 }
 
+.tabmail-tab[type="folder"][ServerType="nntp"][NewMessages="true"],
+.tabmail-tab[type="folder"][ServerType="rss"][NewMessages="true"],
 treechildren::-moz-tree-image(folderNameCol, serverType-nntp, newMessages-true),
 treechildren::-moz-tree-image(folderNameCol, serverType-rss, newMessages-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup-new.png");
 }
 
 /* ..... Inbox ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Inbox"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Inbox) {
   list-style-image: url("chrome://messenger/skin/icons/folder-inbox.png");
 }
 
+.tabmail-tab[type="folder"][SpecialFolder="Inbox"][NewMessages="true"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Inbox, newMessages-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-inbox-new.png");
 }
 
 /* ..... Sent ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Sent"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Sent) {
   list-style-image: url("chrome://messenger/skin/icons/folder-sent.png");
 }
 
 /* ..... Unsent ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Unsent Messages"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Unsent Messages) {
   list-style-image: url("chrome://messenger/skin/icons/folder-outbox.png");
 }
 
+.tabmail-tab[type="folder"][SpecialFolder="Outbox"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Outbox) {
   list-style-image: url("chrome://messenger/skin/icons/folder-outbox.png");
 }
 
 /* ..... Drafts ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Drafts"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Drafts) {
   list-style-image: url("chrome://messenger/skin/icons/folder-draft.png");
 }
 
 /* ..... Templates ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Templates"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Templates) {
   list-style-image: url("chrome://messenger/skin/icons/folder-template.png");
 }
 
 /* ..... Junk ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Junk"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Junk) {
   list-style-image: url("chrome://messenger/skin/icons/folder-junk.png");
 }
 
 /* ..... Trash ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Trash"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Trash) {
   list-style-image: url("chrome://messenger/skin/icons/folder-trash.png");
 }
 
 /* ..... Saved Searches ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Virtual"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Virtual) {
   list-style-image: url("chrome://messenger/skin/icons/folder-search.png");
 }
 
-treechildren::-moz-tree-cell-text(folderNameCol, newMessages-true),
-treechildren::-moz-tree-cell-text(folderNameCol, specialFolder-Inbox, newMessages-true) {
+.tabmail-tab[type="folder"][NewMessages="true"],
+treechildren::-moz-tree-cell-text(folderNameCol, newMessages-true) {
   font-weight: bold;
 }
 
+.tabmail-tab[type="folder"][ImapShared="true"],
 treechildren::-moz-tree-image(folderNameCol, imapShared-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-share.png");
 }
 
 /* ..... Server Folders ..... */
 
+.tabmail-tab[type="folder"][IsServer="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-mail.png");
 }
 
+.tabmail-tab[type="folder"][BiffState="NewMail"][IsServer="true"],
 treechildren::-moz-tree-image(folderNameCol, biffState-NewMail, isServer-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-mail-new.png");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="pop3"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-pop3, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-remote-lock.png");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="imap"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-imap, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-remote-lock.png");
 }
 
+.tabmail-tab[type="folder"][BiffState="NewMail"][IsServer="true"][ServerType="imap"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, biffState-NewMail, isServer-true, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-remote-lock-new.png");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="none"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-none) {
   list-style-image: url("chrome://messenger/skin/icons/server-local.png");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="nntp"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-nntp) {
   list-style-image: url("chrome://messenger/skin/icons/server-news.png");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="nntp"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-nntp, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-news-lock.png");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="rss"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-rss) {
   list-style-image: url("chrome://communicator/skin/icons/feedIcon16.png");
 }
 
 /* ::::: All Servers ::::: */
 
 treechildren::-moz-tree-cell-text(closed, subfoldersHaveUnreadMessages-true) {
   font-weight: bold;
 }
 
+.tabmail-tab[type="folder"][IsServer="true"],
 treechildren::-moz-tree-cell-text(folderNameCol, isServer-true),
 treechildren::-moz-tree-cell-text(hasUnreadMessages-true) {
   font-weight: bold;
 }
 
 treechildren::-moz-tree-cell-text(folderNameCol, noSelect-true) {
   color: gray;
   font-style: italic;
--- a/suite/themes/classic/messenger/mailWindow1.css
+++ b/suite/themes/classic/messenger/mailWindow1.css
@@ -54,17 +54,17 @@
   min-height: 10px;
   height: 0px;
 }
 
 /* ::::: border adjustments for focus ring and joined splitters ::::: */
 
 /* ..... splitter adjustments ..... */
 
-#gray_vertical_splitter {
+#folderpane-splitter {
   border-right: none;
   border-bottom: none;
   border-left: none;
   min-width: 5px;
 }
 
 #threadpane-splitter {
   border: none;
@@ -131,9 +131,18 @@
   border: 1px solid -moz-Field;
   border-right: none;
 }
 
 #messagepanebox[focusring="true"] > #messagepane {
   border-color: #000000;
 }
 
+/* ..... tabmail ..... */
 
+tabpanels {
+  /* don't draw tabpanel borders; see also tabbrowser.css */
+  -moz-appearance: none;
+}
+
+.tab-close-button {
+  margin: 0;
+}
--- a/suite/themes/classic/messenger/threadPane.css
+++ b/suite/themes/classic/messenger/threadPane.css
@@ -185,60 +185,87 @@ treechildren::-moz-tree-image(junkStatus
 }
 
 treechildren::-moz-tree-image(junkStatusCol, notjunk) {
   list-style-image: url("chrome://messenger/skin/icons/dot.png");
   -moz-padding-start: 0px;
   -moz-padding-end: 4px;
 }
 
-/* ..... subject column ..... */
+/* ..... subject column and tab icons ..... */
+
+.tabmail-tab[type="message"] .tab-icon {
+  margin-top: -4px;
+}
+
+.tabmail-tab[type="message"],
+treechildren::-moz-tree-image(subjectCol) {
+  list-style-image: url("chrome://messenger/skin/icons/message-mail.png");
+}
 
 treechildren::-moz-tree-image(subjectCol) {
   -moz-margin-end: 2px;
-  list-style-image: url("chrome://messenger/skin/icons/message-mail.png");
 }
 
 treechildren::-moz-tree-image(subjectCol, new) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-new.png");
 }
 
+.tabmail-tab[type="message"][Attachment="true"],
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, attach) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-attach.png");
 }
 
+.tabmail-tab[type="message"][IMAPDeleted="true"],
 treechildren::-moz-tree-image(subjectCol, imapdeleted) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-imapdelete.png");
 }
 
+.tabmail-tab[type="message"][Offline="true"],
 treechildren::-moz-tree-image(subjectCol, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-offl.png");
 }
 
 treechildren::-moz-tree-image(subjectCol, new, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-new-offl.png");
 }
 
+.tabmail-tab[type="message"][Attachment="true"][Offline="true"],
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, attach, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-attach-offl.png");
 }
 
+.tabmail-tab[type="message"][IMAPDeleted="true"][Offline="true"],
 treechildren::-moz-tree-image(subjectCol, imapdeleted, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-delete-offl.png");
 }
 
+/* the news icons are only 14px high, unfortunately */
+.tabmail-tab[type="message"][MessageType="rss"] .tab-icon,
+.tabmail-tab[type="message"][MessageType="nntp"] .tab-icon {
+  height: 14px;
+}
+
+.tabmail-tab[type="message"][MessageType="rss"],
+.tabmail-tab[type="message"][MessageType="nntp"],
 treechildren::-moz-tree-image(subjectCol, rss),
 treechildren::-moz-tree-image(subjectCol, news) {
   list-style-image: url("chrome://messenger/skin/icons/message-news.png");
 }
 
+.tabmail-tab[type="message"][MessageType="rss"][Attachment="true"],
+.tabmail-tab[type="message"][MessageType="nntp"][Attachment="true"],
+tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, rss, attach),
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, news, attach) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-attach.png");
 }
 
+.tabmail-tab[type="message"][MessageType="rss"][Attachment="true"][Offline="true"],
+.tabmail-tab[type="message"][MessageType="nntp"][Attachment="true"][Offline="true"],
+tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, rss, attach, offline),
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, news, attach, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-attach-offl.png");
 }
 
 treechildren::-moz-tree-image(subjectCol, rss, new),
 treechildren::-moz-tree-image(subjectCol, news, new) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-new.png");
 }
@@ -250,16 +277,19 @@ tree[noattachcol="true"] > treechildren:
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, news, new, attach, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-new-attach-off.png");
 }
 
 treechildren::-moz-tree-image(subjectCol, news, new, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-new-offl.png");
 }
 
+.tabmail-tab[type="message"][MessageType="rss"][Offline="true"],
+.tabmail-tab[type="message"][MessageType="nntp"][Offline="true"],
+treechildren::-moz-tree-image(subjectCol, rss, offline),
 treechildren::-moz-tree-image(subjectCol, news, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-offl.png");
 }
 
 /* ..... new thread icons for watch and ignore ..... */
 
 treechildren::-moz-tree-image(news, threadCol, watch) {
   list-style-image: url("chrome://messenger/skin/icons/thread-closed-eye.png");
--- a/suite/themes/modern/messenger/folderPane.css
+++ b/suite/themes/modern/messenger/folderPane.css
@@ -42,141 +42,171 @@
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* ::::: mail folder ::::: */
 
 treechildren::-moz-tree-image(folderNameCol) {
   -moz-margin-end: 2px;
+}
+
+.tabmail-tab[type="folder"],
+treechildren::-moz-tree-image(folderNameCol) {
   list-style-image: url("chrome://messenger/skin/icons/folder-closed.gif");
 }
 
 treechildren::-moz-tree-image(folderNameCol, newMessages-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-new.gif");
 }
 
 /* ..... News and Feeds ..... */
 
+.tabmail-tab[type="folder"][ServerType="nntp"],
+.tabmail-tab[type="folder"][ServerType="rss"],
 treechildren::-moz-tree-image(folderNameCol, serverType-nntp),
 treechildren::-moz-tree-image(folderNameCol, serverType-rss) {
   list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup.gif");
 }
 
+.tabmail-tab[type="folder"][ServerType="nntp"][NewMessages="true"],
+.tabmail-tab[type="folder"][ServerType="rss"][NewMessages="true"],
 treechildren::-moz-tree-image(folderNameCol, serverType-nntp, newMessages-true),
 treechildren::-moz-tree-image(folderNameCol, serverType-rss, newMessages-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup-new.gif");
 }
 
 /* ..... Inbox ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Inbox"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Inbox) {
   list-style-image: url("chrome://messenger/skin/icons/folder-inbox.gif");
 }
 
+.tabmail-tab[type="folder"][SpecialFolder="Inbox"][NewMessages="true"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Inbox, newMessages-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-inbox-new.gif");
 }
 
 /* ..... Sent ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Sent"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Sent) {
   list-style-image: url("chrome://messenger/skin/icons/folder-sent.gif");
 }
 
 /* ..... Drafts ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Drafts"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Drafts) {
   list-style-image: url("chrome://messenger/skin/icons/folder-draft.gif");
 }
 
 /* ..... Templates ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Templates"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Templates) {
   list-style-image: url("chrome://messenger/skin/icons/folder-template.gif");
 }
 
 /* ..... Unsent Messages ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Unsent Messages"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Unsent Messages) {
   list-style-image: url("chrome://messenger/skin/icons/folder-outbox.gif");
 }
 
+.tabmail-tab[type="folder"][SpecialFolder="Outbox"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Outbox) {
   list-style-image: url("chrome://messenger/skin/icons/folder-outbox.gif");
 }
 
 /* ..... Junk ..... */
+
+.tabmail-tab[type="folder"][SpecialFolder="Junk"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Junk) {
   list-style-image: url("chrome://messenger/skin/icons/folder-junk.gif");
 }
 
 /* ..... Trash ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Trash"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Trash) {
   list-style-image: url("chrome://messenger/skin/icons/folder-trash.gif");
 }
 
 /* ..... Saved Searches ..... */
 
+.tabmail-tab[type="folder"][SpecialFolder="Virtual"],
 treechildren::-moz-tree-image(folderNameCol, specialFolder-Virtual) {
   list-style-image: url("chrome://messenger/skin/icons/folder-search.gif");
 }
 
 /* ...... Shared ..... */
 
+.tabmail-tab[type="folder"][ImapShared="true"],
 treechildren::-moz-tree-image(folderNameCol, imapShared-true) {
   list-style-image: url("chrome://messenger/skin/icons/folder-share.gif");
 }
 
 /* ..... Server Folders ..... */
 
+.tabmail-tab[type="folder"][IsServer="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-mail.gif");
 }
 
+.tabmail-tab[type="folder"][BiffState="NewMail"][IsServer="true"],
 treechildren::-moz-tree-image(folderNameCol, biffState-NewMail, isServer-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-mail-new.gif");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="pop3"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-pop3, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-remote-lock.gif");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="imap"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-imap, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-remote-lock.gif");
 }
 
+.tabmail-tab[type="folder"][BiffState="NewMail"][IsServer="true"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, biffState-NewMail, isServer-true, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-remote-lock-new.gif");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="none"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-none) {
   list-style-image: url("chrome://messenger/skin/icons/server-local.gif");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="nntp"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-nntp) {
   list-style-image: url("chrome://messenger/skin/icons/server-news.gif");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="nntp"][IsSecure="true"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-nntp, isSecure-true) {
   list-style-image: url("chrome://messenger/skin/icons/server-news-lock.gif");
 }
 
+.tabmail-tab[type="folder"][IsServer="true"][ServerType="rss"],
 treechildren::-moz-tree-image(folderNameCol, isServer-true, serverType-rss) {
   list-style-image: url("chrome://communicator/skin/icons/feedIcon16.png");
 }
 
 /* ::::: All Servers ::::: */
 
 treechildren::-moz-tree-cell-text(closed, subfoldersHaveUnreadMessages-true)
 {
   font-weight: bold;
 }
 
+.tabmail-tab[type="folder"][IsServer="true"],
 treechildren::-moz-tree-cell-text(folderNameCol, isServer-true),
 treechildren::-moz-tree-cell-text(hasUnreadMessages-true) {
   font-weight: bold;
 }
 
 treechildren::-moz-tree-cell-text(folderNameCol, noSelect-true) {
   color: gray;
   font-style: italic;
--- a/suite/themes/modern/messenger/mailWindow1.css
+++ b/suite/themes/modern/messenger/mailWindow1.css
@@ -52,17 +52,17 @@
 
 #messagepanebox {
   min-height: 10px;
   height: 0px;
 }
 
 /* ::::: border adjustments for focus ring and joined splitters ::::: */
 
-#gray_vertical_splitter {
+#folderpane-splitter {
   border-right: none;
   border-bottom: none;
   border-left: none;
   min-width: 5px;
 }
 
 #threadpane-splitter {
   border: none;
@@ -147,8 +147,14 @@
 #messagepane {
   border: 1px solid #FFFFFF;
   border-right: none;
 }
 
 #messagepanebox[focusring="true"] > #messagepane {
   border-color: #000000;
 }
+
+/* ..... tabmail ..... */
+
+.tab-close-button {
+  list-style-image: url("chrome://global/skin/icons/close.gif");
+}
--- a/suite/themes/modern/messenger/threadPane.css
+++ b/suite/themes/modern/messenger/threadPane.css
@@ -219,58 +219,81 @@ treechildren::-moz-tree-image(junkStatus
 treechildren::-moz-tree-image(junkStatusCol, notjunk) {
   list-style-image: url("chrome://messenger/skin/icons/readcol-read.gif");
   -moz-padding-start: 0px;
   -moz-padding-end: 4px;
 }
 
 /* ..... subject column ..... */
 
+.tabmail-tab[type="message"],
+treechildren::-moz-tree-image(subjectCol) {
+  list-style-image: url("chrome://messenger/skin/icons/message-mail.gif");
+}
+
 treechildren::-moz-tree-image(subjectCol) {
   -moz-margin-end: 2px;
-  list-style-image: url("chrome://messenger/skin/icons/message-mail.gif");
 }
 
 treechildren::-moz-tree-image(subjectCol, new) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-new.gif");
 }
 
+.tabmail-tab[type="message"][Attachment="true"],
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, attach) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-attach.gif");
 }
 
+.tabmail-tab[type="message"][IMAPDeleted="true"],
 treechildren::-moz-tree-image(subjectCol, imapdeleted) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-imapdelete.gif");
 }
 
+.tabmail-tab[type="message"][Offline="true"],
 treechildren::-moz-tree-image(subjectCol, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-offl.gif");
 }
 
 treechildren::-moz-tree-image(subjectCol, new, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-new-offl.gif");
 }
 
+.tabmail-tab[type="message"][Attachment="true"][Offline="true"],
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, attach, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-attach-offl.gif");
 }
 
+.tabmail-tab[type="message"][IMAPDeleted="true"][Offline="true"],
 treechildren::-moz-tree-image(subjectCol, imapdeleted, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-mail-delete-offl.gif");
 }
 
+/* the news icons are only 14px high, unfortunately */
+.tabmail-tab[type="message"][MessageType="rss"] .tab-icon,
+.tabmail-tab[type="message"][MessageType="nntp"] .tab-icon {
+  height: 14px;
+}
+
+.tabmail-tab[type="message"][MessageType="rss"],
+.tabmail-tab[type="message"][MessageType="nntp"],
 treechildren::-moz-tree-image(subjectCol, rss),
 treechildren::-moz-tree-image(subjectCol, news) {
   list-style-image: url("chrome://messenger/skin/icons/message-news.gif");
 }
 
+.tabmail-tab[type="message"][MessageType="rss"][Attachment="true"],
+.tabmail-tab[type="message"][MessageType="nntp"][Attachment="true"],
+tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, rss, attach),
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, news, attach) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-attach.gif");
 }
 
+.tabmail-tab[type="message"][MessageType="rss"][Attachment="true"][Offline="true"],
+.tabmail-tab[type="message"][MessageType="nntp"][Attachment="true"][Offline="true"],
+tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, rss, attach, offline),
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, news, attach, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-attach-offl.gif");
 }
 
 treechildren::-moz-tree-image(subjectCol, rss, new),
 treechildren::-moz-tree-image(subjectCol, news, new) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-new.gif");
 }
@@ -282,16 +305,19 @@ tree[noattachcol="true"] > treechildren:
 tree[noattachcol="true"] > treechildren::-moz-tree-image(subjectCol, news, new, attach, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-new-attach-off.gif");
 }
 
 treechildren::-moz-tree-image(subjectCol, news, new, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-new-offl.gif");
 }
 
+.tabmail-tab[type="message"][MessageType="rss"][Offline="true"],
+.tabmail-tab[type="message"][MessageType="nntp"][Offline="true"],
+treechildren::-moz-tree-image(subjectCol, rss, offline),
 treechildren::-moz-tree-image(subjectCol, news, offline) {
   list-style-image: url("chrome://messenger/skin/icons/message-news-offl.gif");
 }
 
 #sizeCol,
 #unreadCol,
 #totalCol {
   text-align: right;