new js folder pane, w/o rdf, original work by jminta, with additional work by me, r=standard8, with some review by neil, 414038
authorDavid Bienvenu <bienvenu@nventure.com>
Wed, 12 Nov 2008 19:36:29 -0800 (2008-11-13)
changeset 1089 1de58e1d7549c7ff38aabf3cc1068451077d3cd1
parent 1088 ff252b091f9ed810b993fa53fb9bddd73bd7e4aa
child 1090 626f5b2d5b1dc46ae168212c1e6698fb80881f86
push id821
push userbienvenu@nventure.com
push dateThu, 13 Nov 2008 03:36:46 +0000 (2008-11-13)
treeherdercomm-central@1de58e1d7549 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstandard8, with
bugs414038
new js folder pane, w/o rdf, original work by jminta, with additional work by me, r=standard8, with some review by neil, 414038
mail/base/content/commandglue.js
mail/base/content/folderPane.js
mail/base/content/mail3PaneWindowCommands.js
mail/base/content/mailCommands.js
mail/base/content/mailContextMenus.js
mail/base/content/mailWindow.js
mail/base/content/mailWindowOverlay.js
mail/base/content/mailWindowOverlay.xul
mail/base/content/messenger.xul
mail/base/content/msgMail3PaneWindow.js
mail/base/content/widgetglue.js
mail/base/jar.mn
mail/locales/en-US/chrome/messenger/messenger.properties
mailnews/base/resources/content/mailWidgets.xml
mailnews/base/resources/content/msgAccountCentral.js
mailnews/base/util/Makefile.in
mailnews/base/util/folderUtils.jsm
mailnews/base/util/iteratorUtils.jsm
--- a/mail/base/content/commandglue.js
+++ b/mail/base/content/commandglue.js
@@ -63,41 +63,16 @@ var MSG_FOLDER_FLAG_TRASH = 0x0100;
 var MSG_FOLDER_FLAG_SENTMAIL = 0x0200;
 var MSG_FOLDER_FLAG_DRAFTS = 0x0400;
 var MSG_FOLDER_FLAG_QUEUE = 0x0800;
 var MSG_FOLDER_FLAG_INBOX = 0x1000;
 var MSG_FOLDER_FLAG_TEMPLATES = 0x400000;
 var MSG_FOLDER_FLAG_JUNK = 0x40000000;
 var MSG_FOLDER_FLAG_FAVORITE = 0x80000000;
 
-function GetMsgFolderFromResource(folderResource)
-{
-  if (!folderResource)
-     return null;
-
-  var msgFolder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
-  if (msgFolder && (msgFolder.parent || msgFolder.isServer))
-    return msgFolder;
-  else
-    return null;
-}
-
-function GetServer(uri)
-{
-    if (!uri) return null;
-    try {
-        var folder = GetMsgFolderFromUri(uri, true);
-        return folder.server;
-    }
-    catch (ex) {
-        dump("GetServer("+uri+") failed, ex="+ex+"\n");
-    }
-    return null;
-}
-
 function setTitleFromFolder(msgfolder, subject)
 {
     var wintype = document.documentElement.getAttribute('windowtype');
     var title; 
 
     // If we are showing the mail:3pane. Never include the subject of the selected
     // message in the title. ("Inbox - My Mail - Mozilla Thunderbird")
     // If we are a stand alone message window, we should show the Subject
@@ -341,17 +316,16 @@ function RerootFolder(uri, newFolder, vi
     {
       if (oldFolder.URI != newFolder.URI)
         oldFolder.setMsgDatabase(null);
     }
   }
   // that should have initialized gDBView, now re-root the thread pane
   RerootThreadPane();
   SetUpToolbarButtons(uri);
-  UpdateFolderLocationPicker(gMsgFolderSelected);
   UpdateStatusMessageCounts(gMsgFolderSelected);
   
   // 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...
   {
@@ -814,18 +788,17 @@ function OnMouseUpThreadAndMessagePaneSp
 {
   // the collapsed state is the state after we released the mouse 
   // so we take it as it is
   ChangeMessagePaneVisibility(IsMessagePaneCollapsed());
 }
 
 function FolderPaneSelectionChange()
 {
-    var folderTree = GetFolderTree();
-    var folderSelection = folderTree.view.selection;
+    var folderSelection = gFolderTreeView.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)
     // of a row will be different from the value of currentIndex, thus if
     // the currentIndex is not selected, it means the user right-clicked
     // and we don't want to load the contents of the folder.
     if (!folderSelection.isSelected(folderSelection.currentIndex))
@@ -843,17 +816,16 @@ function FolderPaneSelectionChange()
         if (msgFolder == gMsgFolderSelected)
            return;
         // If msgFolder turns out to be a single folder saved search, a virtual folder,
         // realFolder will get set to the underlying folder the
         // saved search is based on.
         var realFolder = msgFolder;
         gPrevSelectedFolder = gMsgFolderSelected;
         gMsgFolderSelected = msgFolder;
-        UpdateFolderLocationPicker(gMsgFolderSelected);
         var folderFlags = msgFolder.flags;
         // If this is same folder, and we're not showing a virtual folder
         // then do nothing.
         if (msgFolder == msgWindow.openFolder && 
           !(folderFlags & MSG_FOLDER_FLAG_VIRTUAL) && ! (gPrevFolderFlags & MSG_FOLDER_FLAG_VIRTUAL))
         {
             return;
         }
@@ -1075,45 +1047,39 @@ function  CreateVirtualFolder(newName, p
     }
   }
   else 
   {
     dump("no name or nothing selected\n");
   }   
 }
 
-var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
-var gSearchView;
 var gSearchSession;
 
-var nsIMsgFolder = Components.interfaces.nsIMsgFolder;
-var nsIMsgWindow = Components.interfaces.nsIMsgWindow;
 var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
 
-var gFolderDatasource;
-var gFolderPicker;
-var gStatusBar = null;
 var gMessengerBundle = null;
 
 // Datasource search listener -- made global as it has to be registered
 // and unregistered in different functions.
-var gDataSourceSearchListener;
 var gViewSearchListener;
 
 function GetScopeForFolder(folder) 
 {
   return folder.server.searchScope;
 }
 
 function setupXFVirtualFolderSearch(folderUrisToSearch, searchTerms, searchOnline)
 {
-    var count = new Object;
+  const Ci = Components.interfaces;
+  var count = new Object;
   var i;
 
-    gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
+  gSearchSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
+                             .createInstance(Ci.nsIMsgSearchSession);
 
   for (i in folderUrisToSearch)
     {
       var realFolder = GetMsgFolderFromUri(folderUrisToSearch[i]);
       if (!realFolder.isServer)
         gSearchSession.addScopeTerm(!searchOnline ? nsMsgSearchScope.offlineMail : GetScopeForFolder(realFolder), realFolder);
     }
 
@@ -1121,19 +1087,22 @@ function setupXFVirtualFolderSearch(fold
     const nsIMsgSearchTerm = Components.interfaces.nsIMsgSearchTerm;
     for each (var term in fixIterator(termsArray, nsIMsgSearchTerm)) {
       gSearchSession.appendTerm(term);
     }
 }
 
 function CreateGroupedSearchTerms(searchTermsArray)
 {
-
-  var searchSession = gSearchSession || 
-    Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
+  const Ci = Components.interfaces;
+  var searchSession = gSearchSession;
+  if (!searchSession) {
+    searchSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
+                              .createInstance(Ci.nsIMsgSearchSession);
+  }
 
   // create a temporary isupports array to store our search terms
   // since we will be modifying the terms so they work with quick search
   var searchTermsArrayForQS = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
   
   var numEntries = searchTermsArray.Count();
   for (var i = 0; i < numEntries; i++) {
     var searchTerm = searchTermsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm); 
new file mode 100644
--- /dev/null
+++ b/mail/base/content/folderPane.js
@@ -0,0 +1,1369 @@
+/* ***** 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 mail folder tree code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * 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 ***** */
+
+Components.utils.import("resource://gre/modules/iteratorUtils.jsm");
+Components.utils.import("resource://gre/modules/folderUtils.jsm");
+
+/**
+ * This file contains the controls and functions for the folder pane.
+ * The following definitions will be useful to know:
+ *
+ * gFolderTreeView - the controller for the folder tree.
+ * ftvItem  - folder tree view item, representing a row in the tree
+ * mode - folder view type, e.g., all folders, favorite folders, MRU...
+ */
+
+/**
+ * This is our controller for the folder-tree. It includes our nsITreeView
+ * implementation, as well as other control functions.
+ */
+let gFolderTreeView = {
+  /**
+   * Called when the window is initially loaded.  This function initializes the
+   * folder-pane to the view last shown before the application was closed.
+   */
+  load: function ftv_load(aTree, aJSONFile) {
+    const Cc = Components.classes;
+    const Ci = Components.interfaces;
+    this._treeElement = aTree;
+
+    // the folder pane can be used for other trees which may not have these elements. 
+    if (document.getElementById("folderpane_splitter"))
+      document.getElementById("folderpane_splitter").collapsed = false;
+    if (document.getElementById("folderPaneBox"))
+      document.getElementById("folderPaneBox").collapsed = false;
+
+    try {
+      // Normally our tree takes care of keeping the last selected by itself.
+      // However older versions of TB stored this in a preference, which we need
+      // to migrate
+      let prefB = Cc["@mozilla.org/preferences-service;1"]
+                     .getService(Ci.nsIPrefBranch);
+      let modeIndex = prefB.getIntPref("mail.ui.folderpane.view");
+      this._mode = this.modeNames[modeIndex];
+      prefB.deleteBranch("mail.ui.folderpane");
+    } catch(ex) {
+      // This is ok.  If we've already migrated we'll end up here
+    }
+
+    if (document.getElementById('folderpane-title')) {
+      let key = "folderPaneHeader_" + this.mode;
+      let string = document.getElementById("bundle_messenger").getString(key);
+      document.getElementById('folderpane-title').value = string;
+    }
+
+    if (aJSONFile) {
+      // Parse our persistent-open-state json file
+      let file = Cc["@mozilla.org/file/directory_service;1"]
+                    .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
+      file.append(aJSONFile);
+
+      if (file.exists()) {
+        let data = "";
+        let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+                         .createInstance(Ci.nsIFileInputStream);
+        let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
+                         .createInstance(Ci.nsIScriptableInputStream);
+        fstream.init(file, -1, 0, 0);
+        sstream.init(fstream);
+
+        while (sstream.available())
+          data += sstream.read(4096);
+        
+        sstream.close();
+        fstream.close();
+        let JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+        this._persistOpenMap = JSON.decode(data);
+      }
+    }
+
+    // Load our data
+    this._rebuild();
+    // And actually draw the tree
+    aTree.view = this;
+
+    // Add this listener so that we can update the tree when things change
+    let session = Cc["@mozilla.org/messenger/services/session;1"]
+                     .getService(Ci.nsIMsgMailSession);
+    session.AddFolderListener(this, Ci.nsIFolderListener.all);
+
+    // Listen for account creation/deletion
+    Cc["@mozilla.org/preferences-service;1"]
+                .getService(Ci.nsIPrefBranch2)
+                .addObserver("mail.accountmanager.", this, false);
+  },
+
+  /**
+   * Called when the window is being torn down.  Here we undo everything we did
+   * onload.  That means removing our listener and serializing our JSON.
+   */
+  unload: function ftv_unload(aJSONFile) {
+    const Cc = Components.classes;
+    const Ci = Components.interfaces;
+
+    // Remove our listener
+    let session = Cc["@mozilla.org/messenger/services/session;1"]
+                     .getService(Ci.nsIMsgMailSession);
+    session.RemoveFolderListener(this);
+
+    Cc["@mozilla.org/preferences-service;1"]
+                .getService(Ci.nsIPrefBranch2)
+                .removeObserver("mail.accountmanager.", this, false);
+
+    if (aJSONFile) {
+      // Write out our json file...
+      let JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+      let data = JSON.encode(this._persistOpenMap);
+      let file = Cc["@mozilla.org/file/directory_service;1"]
+                    .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
+      file.append("folderTree.json");
+      let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
+                        .createInstance(Ci.nsIFileOutputStream);
+
+      foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
+      foStream.write(data, data.length);
+      foStream.close();
+    }
+  },
+
+  /**
+   * This is an array of all possible modes for the folder tree.  Extensions are
+   * free to add to this pane, but you must make a corresponding addition to the
+   * _mapGenerators object.
+   */
+  modeNames: ["all", "unread", "favorite", "recent"],
+
+  /**
+   * Called to move to the next/prev folder-mode in the list
+   *
+   * @param aForward  whether or not we should move forward in the list
+   */
+  cycleMode: function ftv_cycleMode(aForward) {
+    let index = this.modeNames.indexOf(this.mode);
+    let offset = aForward ? 1 : this.modeNames.length - 1;
+    index = (index + offset) % this.modeNames.length;
+
+    this.mode = this.modeNames[index];
+  },
+
+  /**
+   * If the hidden pref is set, then double-clicking on a folder should open it
+   *
+   * @param event  the double-click event
+   */
+  onDoubleClick: function ftv_onDoubleClick(aEvent) {
+    if (pref.getBoolPref("mailnews.reuse_thread_window2") ||
+        aEvent.button != 0 || aEvent.originalTarget.localName == "twisty" ||
+        aEvent.originalTarget.localName == "slider" ||
+        aEvent.originalTarget.localName == "scrollbarbutton")
+      return;
+
+    let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aEvent.clientX,
+                                                                  aEvent.clientY);
+    let folderItem = gFolderTreeView._rowMap[row];
+    if (folderItem)                                                   
+      folderItem.command();
+
+    // Don't let the double-click toggle the open state of the folder here
+    aEvent.stopPropagation();
+  },
+
+  getFolderAtCoords: function ftv_getFolderAtCoords(aX, aY) {
+    let row = gFolderTreeView._treeElement.treeBoxObject.getRowAt(aX, aY);
+    return gFolderTreeView._rowMap[row]._folder;
+  },
+
+  /**
+   * A string representation for the current display-mode.  Each value here must
+   * correspond to an entry in _mapGenerators
+   */
+  _mode: null,
+  get mode() {
+    if (!this._mode)
+      this._mode = this._treeElement.getAttribute("mode");
+    return this._mode;
+  },
+  set mode(aMode) {
+    this._mode = aMode;
+
+    var string;
+    try {
+      let key = "folderPaneHeader_" + aMode;
+      string = document.getElementById("bundle_messenger").getString(key);
+    } catch(ex) {
+      string = aMode;
+    }
+    document.getElementById('folderpane-title').value = string;
+
+    this._treeElement.setAttribute("mode", aMode);
+    this._rebuild();
+  },
+
+  /**
+   * Selects a given nsIMsgFolder in the tree.  This function will also ensure
+   * that the folder is actually being displayed (that is, that none of its
+   * ancestors are collapsed.
+   *
+   * @param aFolderUri  the nsIMsgFolder to select
+   */
+  selectFolder: function ftv_selectFolder(aFolder) {
+    // "this" inside the nested function refers to the function...
+    // Also note that openIfNot is recursive.
+    let tree = this; 
+    function openIfNot(aFolderToOpen) {
+      let index = tree.getIndexOfFolder(aFolderToOpen);
+      if (index) {
+        if (!tree._rowMap[index].open)
+          tree._toggleRow(index, false);
+        return;
+      }
+
+      // not found, so open the parent
+      openIfNot(aFolderToOpen.parent);
+
+      // now our parent is open, so we can open ourselves
+      tree._toggleRow(tree.getIndexOfFolder(aFolderToOpen), false);
+    }
+    if (aFolder.parent)
+      openIfNot(aFolder.parent);
+    this.selection.select(tree.getIndexOfFolder(aFolder));
+  },
+
+  /**
+   * Returns the index of a folder in the current display.
+   *
+   * @param aFolder  the folder whose index should be returned.
+   * @note If the folder is not in the display (perhaps because one of its
+   *       anscetors is collapsed), this function returns null.
+   */
+  getIndexOfFolder: function ftv_getIndexOfFolder(aFolder) {
+    for (let i in this._rowMap) {
+      if (this._rowMap[i].id == aFolder.URI)
+        return i;
+    }
+    return null;
+  },
+
+  /**
+   * Returns an array of nsIMsgFolders corresponding to the current selection
+   * in the tree
+   */
+  getSelectedFolders: function ftv_getSelectedFolders() {
+    let folderArray = [];
+    let selection = this._treeElement.view.selection;
+    let rangeCount = selection.getRangeCount();
+    for (let i = 0; i < rangeCount; i++) {
+      let startIndex = {};
+      let endIndex = {};
+      selection.getRangeAt(i, startIndex, endIndex);
+      for (let j = startIndex.value; j <= endIndex.value; j++) {
+        folderArray.push(this._rowMap[j]._folder);
+      }
+    }
+    return folderArray;
+  },
+
+  // ****************** Start of nsITreeView implementation **************** //
+
+  get rowCount() {
+    return this._rowMap.length;
+  },
+
+  /**
+   * drag drop interfaces
+   */
+  canDrop: function ftv_canDrop(aRow, aOrientation) {
+    let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
+    if (!targetFolder || !targetFolder.canFileMessages)
+      return false;
+    if (aOrientation != Components.interfaces.nsITreeView.DROP_ON)
+      return false;
+    return true;
+  },
+  drop: function ftv_drop(aRow, aOrientation) {
+    const Cc = Components.classes;
+    const Ci = Components.interfaces;
+    let targetFolder = gFolderTreeView._rowMap[aRow]._folder;
+
+    let dt = this._currentTransfer;
+    let count = dt.mozItemCount;
+    let cs = Cc["@mozilla.org/messenger/messagecopyservice;1"]
+                .getService(Ci.nsIMsgCopyService);
+
+    // we only support drag of a single flavor at a time.
+    let types = dt.mozTypesAt(0);
+    if (Array.indexOf(types, "text/x-moz-folder") != -1) {
+      for (let i = 0; i < count; i++) {
+        let folders = new Array;
+        folders.push(dt.mozGetDataAt("text/x-moz-folder", i));
+        let array = toXPCOMArray(folders, Ci.nsIMutableArray);
+        cs.CopyFolders(array, targetFolder,
+                      (folders[0].server == targetFolder.server), null,
+                       msgWindow);
+      }
+    } 
+    else if (Array.indexOf(types, "text/x-moz-message") != -1) {
+      let array = Cc["@mozilla.org/array;1"]
+                    .createInstance(Components.interfaces.nsIMutableArray);
+      let sourceFolder;
+      let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+      for (let i = 0; i < count; i++) {
+        let msgHdr = messenger.msgHdrFromURI(dt.mozGetDataAt("text/x-moz-message", i));
+        if (!i)
+          sourceFolder = msgHdr.folder;
+        array.appendElement(msgHdr, false);
+      }
+      let prefBranch = Cc["@mozilla.org/preferences-service;1"]
+                          .getService(Ci.nsIPrefService).getBranch("mail.");
+      let isMove = Cc["@mozilla.org/widget/dragservice;1"]
+                      .getService(Ci.nsIDragService).getCurrentSession()
+                      .dragAction == Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
+
+      pref.setCharPref("last_msg_movecopy_target_uri", targetFolder.URI);
+      pref.setBoolPref("last_msg_movecopy_was_move", isMove);
+      // ### ugh, so this won't work with cross-folder views. We would
+      // really need to partition the messages by folder.
+      cs.CopyMessages(sourceFolder, array, targetFolder, isMove, null,
+                        msgWindow, true);
+    }
+    else if (Array.indexOf(types, "text/x-moz-url") != -1) {
+      // This is a potential rss feed to subscribe to
+      // and there's only one, so just get the 0th element.
+      let url = dt.mozGetDataAt("text/x-moz-url", 0);
+      let uri = Cc["@mozilla.org/network/io-service;1"]
+                   .getService(Ci.nsIIOService).newUri(url, null, null);
+      if (!(uri.schemeIs("http") || uri.schemeIs("https")) ||
+             targetFolder.server.type != 'rss')
+        return;
+
+      Cc["@mozilla.org/newsblog-feed-downloader;1"]
+         .getService(Ci.nsINewsBlogFeedDownloader)
+         .subscribeToFeed(url, targetFolder, msgWindow);
+    }
+  },
+
+  _onDragStart: function ftv_dragStart(aEvent) {
+    // Ugh, this is ugly but necessary
+    let view = gFolderTreeView;
+
+    if (aEvent.originalTarget.localName != "treechildren")
+      return;
+
+    let folders = view.getSelectedFolders();
+    folders = folders.filter(function(f) { return !f.isServer; });
+    for (let i in folders)
+      aEvent.dataTransfer.mozSetDataAt("text/x-moz-folder", folders[i], i);
+    aEvent.dataTransfer.effectAllowed = "copyMove";
+    aEvent.dataTransfer.addElement(aEvent.originalTarget);
+    return;
+  },
+
+  _onDragOver: function ftv_onDragOver(aEvent) {
+    this._currentTransfer = aEvent.dataTransfer;
+  },
+
+  /**
+   * CSS files will cue off of these.  Note that we reach into the rowMap's
+   * items so that custom data-displays can define their own properties
+   */
+  getCellProperties: function ftv_getCellProperties(aRow, aCol, aProps) {
+    this._rowMap[aRow].getProperties(aProps, aCol);
+  },
+
+  /**
+   * The actual text to display in the tree
+   */
+  getCellText: function ftv_getCellText(aRow, aCol) {
+    if (aCol.id == "folderNameCol")
+      return this._rowMap[aRow].text;
+  },
+
+  /**
+   * The ftvItems take care of assigning this when building children lists
+   */
+  getLevel: function ftv_getLevel(aIndex) {
+    return this._rowMap[aIndex].level;
+  },
+
+  /**
+   * This is easy since the ftv items assigned the _parent property when making
+   * the child lists
+   */
+  getParentIndex: function ftv_getParentIndex(aIndex) {
+    for (let i = 0; i < this._rowMap.length; i++) {
+      if (this._rowMap[i] == this._rowMap[aIndex]._parent)
+        return i;
+    }
+    return -1;
+  },
+
+  /**
+   * This is duplicative for our normal ftv views, but custom data-displays may
+   * want to do something special here
+   */
+  getRowProperties: function ftv_getRowProperties(aIndex, aProps) {
+    this._rowMap[aIndex].getProperties(aProps);
+  },
+
+  /**
+   * If the next item in our list has the same level as us, it's a sibling
+   */
+  hasNextSibling: function ftv_hasNextSibling(aIndex, aNextIndex) {
+    return this._rowMap[aIndex].level == this._rowMap[aNextIndex].level;
+  },
+
+  /**
+   * All folders are containers, so we can drag drop messages to them.
+   */
+  isContainer: function ftv_isContainer(aIndex) {
+    return true;
+  },
+
+  isContainerEmpty: function ftv_isContainerEmpty(aIndex) {
+    // If the folder has no children, the container is empty.
+    return !this._rowMap[aIndex].children.length;
+  },
+
+  /**
+   * Just look at the ftvItem here
+   */
+  isContainerOpen: function ftv_isContainerOpen(aIndex) {
+    return this._rowMap[aIndex].open;
+  },
+  isEditable: function ftv_isEditable(aRow, aCol) {
+    // We don't support editing rows in the tree yet.  We may want to later as
+    // an easier way to rename folders.
+    return false;
+  },
+  isSeparator: function ftv_isSeparator(aIndex) {
+    // There are no separators in our trees
+    return false;
+  },
+  isSorted: function ftv_isSorted() {
+    // We do our own customized sorting
+    return false;
+  },
+  setTree: function ftv_setTree(aTree) {
+    this._tree = aTree;
+  },
+
+  /**
+   * Opens or closes a folder with children.  The logic here is a bit hairy, so
+   * be very careful about changing anything.
+   */
+  toggleOpenState: function ftv_toggleOpenState(aIndex) {
+    this._toggleRow(aIndex, true);
+  },
+
+  _toggleRow: function toggleRow(aIndex, aExpandServer)
+  {
+    // Ok, this is a bit tricky.
+    this._rowMap[aIndex].open = !this._rowMap[aIndex].open;
+    if (!this._rowMap[aIndex].open) {
+      // We're closing the current container.  Remove the children
+
+      // Note that we can't simply splice out children.length, because some of
+      // them might have children too.  Find out how many items we're actually
+      // going to splice
+      let count = 0;
+      let i = aIndex + 1;
+      let row = this._rowMap[i];
+      while (row && row.level > this._rowMap[aIndex].level) {
+        count++;
+        row = this._rowMap[++i];
+      }
+      this._rowMap.splice(aIndex + 1, count);
+
+      // Remove us from the persist map
+      let index = this._persistOpenMap[this.mode]
+                      .indexOf(this._rowMap[aIndex].id);
+      if (index != -1)
+        this._persistOpenMap[this.mode].splice(index, 1);
+
+      // Notify the tree of changes
+      if (this._tree) {
+        this._tree.rowCountChanged(aIndex + 1, (-1) * count);
+        this._tree.invalidateRow(aIndex);
+      }
+    } else {
+      // We're opening the container.  Add the children to our map
+
+      // Note that these children may have been open when we were last closed,
+      // and if they are, we also have to add those grandchildren to the map
+      let tree = this;
+      let oldCount = this._rowMap.length;
+      function recursivelyAddToMap(aChild, aNewIndex) {
+        // When we add sub-children, we're going to need to increase our index
+        // for the next add item at our own level
+        let count = 0;
+        if (aChild.children.length && aChild.open) {
+          for (let [i, child] in Iterator(tree._rowMap[aNewIndex].children)) {
+            count++;
+            var index = Number(aNewIndex) + Number(i) + 1;
+            tree._rowMap.splice(index, 0, child);
+            // Somehow the aNewIndex turns into a string without this
+            aNewIndex = Number(aNewIndex) + recursivelyAddToMap(child, index);
+          }
+        }
+        return count;
+      }
+      recursivelyAddToMap(this._rowMap[aIndex], aIndex);
+
+      // Add this folder to the persist map
+      if (!this._persistOpenMap[this.mode])
+        this._persistOpenMap[this.mode] = [];
+      let id = this._rowMap[aIndex].id;
+      if (this._persistOpenMap[this.mode].indexOf(id) == -1)
+        this._persistOpenMap[this.mode].push(id);
+
+      // Notify the tree of changes
+      if (this._tree)
+        this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
+      // if this was a server that was expanded, let it update its counts
+      let folder = this._rowMap[aIndex]._folder;
+      if (aExpandServer && folder.isServer)
+        folder.server.performExpand(msgWindow);
+    }
+  },
+
+  // We don't implement any of these at the moment
+  performAction: function ftv_performAction(aAction) {},
+  performActionOnCell: function ftv_performActionOnCell(aAction, aRow, aCol) {},
+  performActionOnRow: function ftv_performActionOnRow(aAction, aRow) {},
+  selectionChanged: function ftv_selectionChanged() {},
+  setCellText: function ftv_setCellText(aRow, aCol, aValue) {},
+  setCellValue: function ftv_setCellValue(aRow, aCol, aValue) {},
+  getCellValue: function ftv_getCellValue(aRow, aCol) {},
+  getColumnProperties: function ftv_getColumnProperties(aCol, aProps) {},
+  getImageSrc: function ftv_getImageSrc(aRow, aCol) {},
+  getProgressMode: function ftv_getProgressMode(aRow, aCol) {},
+  cycleCell: function ftv_cycleCell(aRow, aCol) {},
+  cycleHeader: function ftv_cycleHeader(aCol) {},
+
+  // ****************** End of nsITreeView implementation **************** //
+
+  //
+  // WARNING: Everything below this point is considered private.  Touch at your
+  //          own risk (Other than plugging into mapGenerators).
+
+  /**
+   * This is a javaascript map of which folders we had open, so that we can
+   * persist their state over-time.  It is designed to be used as a JSON object.
+   */
+  _persistOpenMap: {},
+
+  _restoreOpenStates: function ftv__persistOpenStates() {
+    if (!(this.mode in this._persistOpenMap))
+      return;
+
+    let curLevel = 0;
+    let tree = this;
+    function openLevel() {
+      let goOn = false;
+      // We can't use a js iterator because we're changing the array as we go.
+      // So fallback on old trick of going backwards from the end, which
+      // doesn't care when you add things at the end.
+      for (let i = tree._rowMap.length - 1; i >= 0; i--) {
+        let row = tree._rowMap[i];
+        if (row.level != curLevel)
+          continue;
+
+        let map = tree._persistOpenMap[tree.mode];
+        if (map && map.indexOf(row.id) != -1) {
+          tree._toggleRow(i, false);
+          goOn = true;
+        }
+      }
+
+      // If we opened up any new kids, we need to check their level as well.
+      curLevel++;
+      if (goOn)
+        openLevel();
+    }
+    openLevel();
+  },
+
+  _tree: null,
+
+  /**
+   * An array of ftvItems, where each item corresponds to a row in the tree
+   */
+  _rowMap: null,
+
+  /**
+   * Completely discards the current tree and rebuilds it based on current
+   * settings
+   */
+  _rebuild: function ftv__rebuild() {
+    let oldCount = this._rowMap ? this._rowMap.length : null;
+    this._rowMap = this._mapGenerators[this.mode]();
+
+    if (oldCount !== null)
+      this._tree.rowCountChanged(0, this._rowMap.length - oldCount);
+
+    this._restoreOpenStates();
+  },
+
+  /**
+   * This object holds the functions that actually build the tree for each mode.
+   * When the tree must be rebuilt, we call the function here for the current
+   * mode.  That function should return an array of ftvItems that should be
+   * displayed.
+   *
+   * Extensions should feel free to plug in here!
+   */
+  _mapGenerators: {
+
+    /**
+     * The all mode returns all folders, arranged in a hierarchy
+     */
+    all: function ftv__mg_all() {
+      const Cc = Components.classes;
+      const Ci = Components.interfaces;
+      let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+                       .getService(Ci.nsIMsgAccountManager);
+      let accounts = [a for each
+                      (a in fixIterator(acctMgr.accounts, Ci.nsIMsgAccount))];
+      // Bug 41133 workaround
+      accounts = accounts.filter(function fix(a) { return a.incomingServer; });
+
+      // Don't show deferred pop accounts
+      accounts = accounts.filter(function isNotDeferred(a) {
+        let server = a.incomingServer;
+        return !(server instanceof Ci.nsIPop3IncomingServer &&
+                 server.deferredToAccount);
+      });
+
+      function sortAccounts(a, b) {
+        if (a.key == acctMgr.defaultAccount.key)
+          return -1;
+        if (b.key == acctMgr.defaultAccount.key)
+          return 1;
+        let aIsNews = a.incomingServer.type == "nntp";
+        let bIsNews = b.incomingServer.type == "nntp";
+        if (aIsNews && !bIsNews)
+          return 1;
+        if (bIsNews && !aIsNews)
+          return -1;
+
+        let aIsLocal = a.incomingServer.type == "none";
+        let bIsLocal = b.incomingServer.type == "none";
+        if (aIsLocal && !bIsLocal)
+          return 1;
+        if (bIsLocal && !aIsLocal)
+          return -1;
+        return 0;
+      }
+      accounts.sort(sortAccounts);
+      // force each root folder to do its local subfolder discovery.
+      for each (acct in accounts)
+        acct.incomingServer.rootFolder.subFolders;
+        
+      return [new ftvItem(acct.incomingServer.rootFolder)
+              for each (acct in accounts)];
+    },
+
+    /**
+     * The unread mode returns all folders that are not root-folders and that
+     * have unread items
+     */
+    unread: function ftv__mg_unread() {
+      let map = [];
+      for each (let folder in this._enumerateFolders) {
+        if (!folder.isServer && folder.getNumUnread(false) > 0)
+          map.push(new ftvItem(folder));
+      }
+
+      // There are no children in this view!
+      for each (let folder in map) {
+        folder.__defineGetter__("children", function() []);
+        folder.useServerName = true;
+      }
+      sortFolderItems(map);
+      return map;
+    },
+
+    /**
+     * The favorites mode returns all folders whose flags are set to include
+     * the favorite flag
+     */
+    favorite: function ftv__mg_unread() {
+      let faves = [];
+      for each (let folder in this._enumerateFolders) {
+        if (folder.flags & Components.interfaces.nsMsgFolderFlags.Favorite)
+          faves.push(new ftvItem(folder));
+      }
+
+      // There are no children in this view!
+      // And we want to display the account name to distinguish folders w/
+      // the same name.
+      for each (let folder in faves) {
+        folder.__defineGetter__("children", function() []);
+        folder.useServerName = true;
+      }
+      sortFolderItems(faves);
+      return faves;
+    },
+
+    /**
+     * The recent mode is a flat view of the 15 most recently used folders
+     */
+    recent: function ftv__mg_recent() {
+      const MAXRECENT = 15;
+
+      /**
+       * Sorts our folders by their recent-times.
+       */
+      function sorter(a, b) {
+        return Number(a.getStringProperty("MRUTime")) <
+          Number(b.getStringProperty("MRUTime"));
+      }
+
+      /**
+       * This function will add a folder to the recentFolders array if it
+       * is among the 15 most recent.  If we exceed 15 folders, it will pop
+       * the oldest folder, ensuring that we end up with the right number
+       *
+       * @param aFolder the folder to check
+       */
+      let recentFolders = [];
+      let oldestTime = 0;
+      function addIfRecent(aFolder) {
+        let time = Number(aFolder.getStringProperty("MRUTime")) || 0;
+        if (time <= oldestTime)
+          return;
+
+        if (recentFolders.length == MAXRECENT) {
+          recentFolders.sort(sorter);
+          recentFolders.pop();
+          let oldestFolder = recentFolders[recentFolders.length - 1];
+          oldestTime = Number(oldestFolder.getStringProperty("MRUTime"));
+        }
+        recentFolders.push(aFolder);
+      }
+
+      for each (let folder in this._enumerateFolders)
+        addIfRecent(folder);
+
+      recentFolders.sort(sorter);
+
+      let items = [new ftvItem(f) for each (f in recentFolders)];
+
+      // There are no children in this view! 
+      // And we want to display the account name to distinguish folders w/
+      // the same name.
+      for each (let folder in items) {
+        folder.__defineGetter__("children", function() []);
+        folder.useServerName = true;
+      }
+
+      return items;
+    },
+
+    /**
+     * This is a helper attribute that simply returns a flat list of all folders
+     */
+    get _enumerateFolders() {
+      const Cc = Components.classes;
+      const Ci = Components.interfaces;
+      let folders = [];
+
+      /**
+       * This is a recursive function to add all subfolders to the array. It
+       * assumes that the passed in folder itself has already been added.
+       *
+       * @param aFolder  the folder whose subfolders should be added
+       */
+      function addSubFolders(aFolder) {
+        for each (let f in fixIterator(aFolder.subFolders, Ci.nsIMsgFolder)) {
+          folders.push(f);
+          addSubFolders(f);
+        }
+      }
+
+      let acctMgr = Cc["@mozilla.org/messenger/account-manager;1"]
+                       .getService(Ci.nsIMsgAccountManager);
+      for each (let acct in fixIterator(acctMgr.accounts, Ci.nsIMsgAccount)) {
+        // Skip deferred accounts
+        if (acct.incomingServer instanceof Ci.nsIPop3IncomingServer &&
+            acct.incomingServer.deferredToAccount)
+          continue;
+        folders.push(acct.incomingServer.rootFolder);
+        addSubFolders(acct.incomingServer.rootFolder);
+      }
+      return folders;
+    }
+  },
+
+  /**
+   * This is our implementation of nsIMsgFolderListener to watch for changes
+   */
+  OnItemAdded: function ftl_add(aParentItem, aItem) {
+    // Only rebuild if we didn't know about the folder
+    if (!(aItem instanceof Components.interfaces.nsIMsgFolder) ||
+        this.getIndexOfFolder(aItem))
+      return;
+
+    let parentIndex = this.getIndexOfFolder(aParentItem);
+    let parent = this._rowMap[parentIndex];
+
+    // Getting these children might have triggered our parent to build its
+    // array just now, in which case the added item will already exist
+    let children = parent.children;
+    var newChild;
+    for each (let child in children) {
+      if (child._folder == aItem) {
+        newChild = child;
+        break;
+      }
+    }
+    if (!newChild) {
+      newChild = new ftvItem(aItem);
+      parent.children.push(newChild);
+      newChild._level = parent._level + 1;
+      newChild._parent = parent;
+      sortFolderItems(parent._children);
+    }
+
+    // If the parent is open, add the new child into the folder pane. Otherwise,
+    // just invalidate the parent row.
+    if (parent.open) {
+      let newChildIndex;
+      let newChildNum = parent._children.indexOf(newChild);
+      // only child - go right after our parent
+      if (newChildNum == 0)
+      {
+        newChildIndex = Number(parentIndex) + 1
+      }
+      // if we're not the last child, insert ourselves before the next child.
+      else if (newChildNum < parent._children.length - 1)
+      {
+        newChildIndex = this.getIndexOfFolder(parent._children[Number(newChildNum) + 1]._folder);
+      }
+      // otherwise, go after the last child
+      else
+      {
+        let lastChild = parent._children[newChildNum - 1];
+        let lastChildIndex = this.getIndexOfFolder(lastChild._folder);
+        newChildIndex = Number(lastChildIndex) + 1;
+        while (newChildIndex < this.rowCount &&
+               this._rowMap[newChildIndex].level > this._rowMap[lastChildIndex].level)
+          newChildIndex++;
+      }
+      this._rowMap.splice(newChildIndex, 0, newChild);
+      this._tree.rowCountChanged(newChildIndex, 1);
+    } else {
+      this._tree.invalidateRow(parentIndex);
+    }
+  },
+
+  OnItemRemoved: function ftl_remove(aRDFParentItem, aItem) {
+    if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+      return;
+
+    let persistMapIndex = this._persistOpenMap[this.mode].indexOf(aItem.URI);
+    if (persistMapIndex != -1)
+      this._persistOpenMap[this.mode].splice(persistMapIndex, 1);
+    
+    let index = this.getIndexOfFolder(aItem);
+    if (!index)
+      return;
+    // forget our parent's children; they'll get rebuilt
+    this._rowMap[index]._parent._children = null;
+    let kidCount = 1;
+    let walker = Number(index) + 1;
+    while (walker < this.rowCount &&
+           this._rowMap[walker].level > this._rowMap[index].level) {
+      walker++;
+      kidCount++;
+    }
+    this._rowMap.splice(index, kidCount);
+    this._tree.rowCountChanged(index, -1 * kidCount);
+    this._tree.invalidateRow(index);
+  },
+
+  OnItemPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+  OnItemIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+    if (aItem instanceof Components.interfaces.nsIMsgFolder)
+    {
+      let index = this.getIndexOfFolder(aItem);
+      if (index)
+        this._tree.invalidateRow(index);
+    }
+  },
+
+  OnItemBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+  OnItemUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+  OnItemPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
+  OnItemEvent: function(aFolder, aEvent) {
+    let index = this.getIndexOfFolder(aFolder);
+    if (index)
+      this._tree.invalidateRow(index);
+  },
+
+  // Believe it or not, this is the simplest way to watch for account creation
+  observe: function ftv_observe(aSubject, aTopic, aPrefName) {
+    if (aPrefName != "mail.accountmanager.accounts")
+      return;
+    let view = this;
+    function wrapper() {
+      view._rebuild();
+    }
+    setTimeout(wrapper, 0);
+  }
+};
+
+/**
+ * The ftvItem object represents a single row in the tree view. Because I'm lazy
+ * I'm just going to define the expected interface here.  You are free to return
+ * an alternative object in a _mapGenerator, provided that it matches this
+ * interface:
+ *
+ * id (attribute) - a unique string for this object. Must persist over sessions
+ * text (attribute) - the text to display in the tree
+ * level (attribute) - the level in the tree to display the item at
+ * open (rw, attribute) - whether or not this container is open
+ * children (attribute) - an array of child items also conforming to this spec
+ * getProperties (function) - a call from getRowProperties or getCellProperties
+ *                            for this item will be passed into this function
+ * command (function) - this function will be called when the item is double-
+ *                      clicked
+ */
+function ftvItem(aFolder) {
+  this._folder = aFolder;
+  this._level = 0;
+}
+
+ftvItem.prototype = {
+  open: false,
+  useServerName: false,
+
+  get id() {
+    return this._folder.URI;
+  },
+  get text() {
+    let text = this._folder.abbreviatedName;
+    if (this.useServerName)
+      text += " - " + this._folder.server.prettyName;
+    // Yeah, we hard-code this, but so did the old code...
+    let unread = this._folder.getNumUnread(false);
+    if (unread > 0)
+      text += " (" + unread + ")";
+    return text;
+  },
+
+  get level() {
+    return this._level;
+  },
+
+  getProperties: function ftvItem_getProperties(aProps) {
+    // From folderUtils.jsm
+    setPropertyAtoms(this._folder, aProps);
+  },
+
+  command: function fti_command() {
+    MsgOpenNewWindowForFolder(this._folder.URI, -1 /* key */);
+  },
+
+  _children: null,
+  get children() {
+    const Ci = Components.interfaces;
+    // We're caching our child list to save perf.
+    if (!this._children) {
+      let iter = fixIterator(this._folder.subFolders, Ci.nsIMsgFolder);
+      this._children = [new ftvItem(f) for each (f in iter)];
+
+      sortFolderItems(this._children);
+      // Each child is a level one below us
+      for each (let child in this._children) {
+        child._level = this._level + 1;
+        child._parent = this;
+      }
+    }
+    return this._children;
+  }
+};
+
+/**
+ * This handles the invocation of most commmands dealing with folders, based off
+ * of the current selection, or a passed in folder.
+ */
+let gFolderTreeController = {
+  /**
+   * Opens the dialog to create a new sub-folder, and creates it if the user
+   * accepts
+   *
+   * @param aParent (optional)  the parent for the new subfolder
+   */
+  newFolder: function ftc_newFolder(aParent) {
+    let folder = aParent || gFolderTreeView.getSelectedFolders()[0];
+
+    // Make sure we actually can create subfolders
+    if (!folder.canCreateSubfolders) {
+      // Check if we can create them at the root
+      if (folder.rootMsgFolder.canCreateSubfolders)
+        folder = folder.rootMsgFolder;
+      else // just use the default account
+        folder = GetDefaultAccountRootFolder();
+    }
+
+    let dualUseFolders = true;
+    if (folder.server instanceof Components.interfaces.nsIImapIncomingServer)
+      dualUseFolders = folder.server.dualUseFolders;
+
+    //xxx useless param
+    function newFolderCallback(aName, aFolder) {
+      if (aName)
+        folder.createSubfolder(aName, msgWindow);
+    }
+
+    window.openDialog("chrome://messenger/content/newFolderDialog.xul",
+                      "", "chrome,titlebar,modal",
+                      {folder: folder, dualUseFolders: dualUseFolders,
+                       okCallback: newFolderCallback});
+  },
+
+  /**
+   * Opens the dialog to edit the properties for a folder
+   *
+   * @param aTabID  (optional) the tab to show in the dialog
+   * @param aFolder (optional) the folder to edit, if not the selected one
+   */
+  editFolder: function ftc_editFolder(aTabID, aFolder) {
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    // If this is actually a server, send it off to that controller
+    if (folder.isServer) {
+      MsgAccountManager(null);
+      return;
+    }
+
+    if (folder.flags & Components.interfaces.nsMsgFolderFlags.Virtual) {
+      this.editVirtualFolder(folder);
+      return;
+    }
+
+    let title = document.getElementById("bundle_messenger")
+                        .getString("folderProperties");
+
+    //xxx useless param
+    function editFolderCallback(aNewName, aOldName, aUri) {
+      if (aNewName != aOldName)
+        folder.rename(aNewName, msgWindow);
+    }
+
+    //xxx useless param
+    function rebuildSummary(aFolder) {
+      let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+      if (folder.locked) {
+        folder.throwAlertMsg("operationFailedFolderBusy", msgWindow);
+        return;
+      }
+      folder.getMsgDatabase(msgWindow).summaryValid = false;
+
+      var msgDB = folder.getMsgDatabase(msgWindow);
+      msgDB.summaryValid = false;
+      try {
+        folder.closeAndBackupFolderDB("");
+      }
+      catch(e) {
+        // In a failure, proceed anyway since we're dealing with problems
+        folder.ForceDBClosed();
+      }
+      // these lines will cause the thread pane to get reloaded when the
+      // download/reparse is finished. Only do this if the selected folder is
+      // loaded (i.e., not thru the context menu on a non-loaded folder).
+      if (folder == GetLoadedMsgFolder()) {
+        gRerootOnFolderLoad = true;
+        gCurrentFolderToReroot = folder.URI;
+      }
+      folder.updateFolder(msgWindow);
+    }
+
+    window.openDialog("chrome://messenger/content/folderProps.xul", "",
+                      "chrome,centerscreen,titlebar,modal",
+                      {folder: folder, serverType: folder.server.type,
+                       msgWindow: msgWindow, title: title,
+                       okCallback: editFolderCallback,
+                       tabID: aTabID, name: folder.prettyName,
+                       rebuildSummaryCallback: rebuildSummary});
+  },
+
+  /**
+   * Opens the dialog to rename a particular folder, and does the renaming if
+   * the user clicks OK in that dialog
+   *
+   * @param aFolder (optional)  the folder to rename, if different than the
+   *                            currently selected one
+   */
+  renameFolder: function ftc_rename(aFolder) {
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    //xxx no need for uri now
+    let controller = this;
+    function renameCallback(aName, aUri) {
+      if (aUri != folder.URI)
+        Components.utils.reportError("got back a different folder to rename!");
+
+      controller._resetThreadPane();
+      controller._tree.view.selection.clearSelection();
+
+      // Actually do the rename
+      folder.rename(aName, msgWindow);
+    }
+    window.openDialog("chrome://messenger/content/renameFolderDialog.xul",
+                      "newFolder", "chrome,titlebar,modal",
+                      {preselectedURI: folder.URI,
+                       okCallback: renameCallback, name: folder.prettyName});
+  },
+
+  /**
+   * Deletes a folder from its parent
+   *
+   * @param aFolder (optional) the folder to delete, if not the selected one
+   */
+  deleteFolder: function ftc_delete(aFolder) {
+    const Ci = Components.interfaces;
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    const FLAGS = Ci.nsMsgFolderFlags;
+    if (folder.flags & FLAGS.Inbox || folder.flags & FLAGS.Trash)
+      return;
+
+    let prefix = "@mozilla.org/messenger/protocol/info;1?type=";
+    let info = Components.classes[prefix + folder.server.type]
+                          .getService(Ci.nsIMsgProtocolInfo);
+
+    // do not allow deletion of special folders on imap accounts
+    let bundle = document.getElementById("bundle_messenger");
+    if ((folder.flags & FLAGS.Sent || folder.flags & FLAGS.Draft ||
+         folder.flags & FLAGS.Template ||
+         ((folder.flags & FLAGS.Junk) && CanRenameDeleteJunkMail(folder))) &&
+        !info.specialFoldersDeletionAllowed) {
+      let specialFolderString = getSpecialFolderString(folder);
+      let errorMessage = bundle.getFormattedString("specialFolderDeletionErr",
+                                                    [specialFolderString]);
+      let errorTitle = bundle.getString("specialFolderDeletionErrTitle");
+      Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                         .getService(Ci.nsIPromptService)
+                         .alert(window, errorTitle, errorMessage);
+      return;
+    }
+
+    // handle news folder specially
+    if (isNewsURI(folder.URI) && confirmUnsubscribe(folder)) {
+      Unsubscribe(folder);
+      return;
+    }
+
+    let array = toXPCOMArray([folder], Ci.nsIMutableArray);
+    if (folder.flags & FLAGS.Virtual) {
+      if (gCurrentVirtualFolderUri == folder.URI)
+        gCurrentVirtualFolderUri = null;
+    }
+
+    folder.parent.deleteSubFolders(array, msgWindow);
+  },
+
+  /**
+   * Prompts the user to confirm and empties the trash for the selected folder
+   *
+   * @param aFolder (optional)  the trash folder to empty
+   * @note Calling this function on a non-trash folder will result in strange
+   *       behavior!
+   */
+  emptyTrash: function ftc_emptyTrash(aFolder) {
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    if (this._checkConfirmationPrompt("emptyTrash"))
+      folder.emptyTrash(msgWindow, null);
+  },
+
+  /**
+   * Deletes everything (folders and messages) in this folder
+   *
+   * @param aFolder (optional)  the folder to empty
+   */
+  emptyJunk: function ftc_emptyJunk(aFolder) {
+    const Ci = Components.interfaces;
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    if (!this._checkConfirmationPrompt("emptyJunk"))
+      return;
+
+    // Delete any subfolders this folder might have
+    let iter = folder.subFolders;
+    while (iter.hasMoreElements())
+      folder.propagateDelete(iter.getNext(), true, msgWindow);
+
+    // Now delete the messages
+    let iter = fixIterator(folder.getMessages(msgWindow));
+    let messages = [m for each (m in iter)];
+    let children = toXPCOMArray(messages, Ci.nsIMutableArray);
+    folder.deleteMessages(children, msgWindow, true, false, null, false);
+  },
+
+  /**
+   * Compacts either a particular folder, or all folders
+   *
+   * @param aCompactAll - whether we should compact all folders
+   * @param aFolder (optional) the folder to compact, if different than the
+   *                           currently selected one
+   */
+  compactFolder: function ftc_compactFolder(aCompactAll, aFolder) {
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    // Can't compact folders that have just been compacted
+    if (!folder.expungedBytes && !aCompactAll)
+      return;
+
+    // reset thread pane for non-imap folders.
+    if (!folder.server.type == "imap" && (gDBView.msgFolder == folder || aCompactAll))
+      this._resetThreadPane();
+
+    if (aCompactAll)
+      folder.compactAll(null, msgWindow, null, true, null);
+    else
+      folder.compact(null, msgWindow);
+  },
+
+  /**
+   * Opens the dialog to create a new virtual folder
+   *
+   * @param aName - the default name for the new folder
+   * @param aSearchTerms - the search terms associated with the folder
+   * @param aParent - the folder to run the search terms on
+   */
+  newVirtualFolder: function ftc_newVFolder(aName, aSearchTerms, aParent) {
+    let folder = aParent || gFolderTreeView.getSelectedFolders()[0];
+    let name = folder.prettyName;
+    if (aName)
+      name += "-" + aName;
+
+    window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
+                      "", "chrome,titlebar,modal,centerscreen",
+                      {folder: folder, searchTerms: aSearchTerms,
+                       newFolderName: name});
+  },
+
+  editVirtualFolder: function ftc_editVirtualFolder(aFolder) {
+    let folder = aFolder || gFolderTreeView.getSelectedFolders()[0];
+
+    //xxx should pass the folder object
+    function editVirtualCallback(aURI) {
+      // we need to reload the folder if it is the currently loaded folder...
+      if (gMsgFolderSelected && aURI == gMsgFolderSelected.URI) {
+        gMsgFolderSelected = null;
+        FolderPaneSelectionChange();
+      }
+    }
+    window.openDialog("chrome://messenger/content/virtualFolderProperties.xul",
+                      "", "chrome,titlebar,modal,centerscreen",
+                      {folder: folder, editExistingFolder: true,
+                       onOKCallback: editVirtualCallback,
+                       msgWindow: msgWindow});
+  },
+
+  /**
+   * For certain folder commands, the thread pane needs to be invalidated, this
+   * takes care of doing so.
+   */
+  _resetThreadPane: function ftc_resetThreadPane() {
+    if (gDBView)
+      gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
+
+    ClearThreadPaneSelection();
+    ClearThreadPane();
+    ClearMessagePane();
+  },
+
+  /**
+   * Prompts for confirmation, if the user hasn't already chosen the "don't ask
+   * again" option.
+   *
+   * @param aCommand - the command to prompt for
+   */
+  _checkConfirmationPrompt: function ftc_confirm(aCommand) {
+    const Cc = Components.classes;
+    const Ci = Components.interfaces;
+    let showPrompt = true;
+    try {
+      let pref = Cc["@mozilla.org/preferences-service;1"]
+                    .getService(Ci.nsIPrefBranch);
+      showPrompt = !pref.getBoolPref("mail." + aCommand + ".dontAskAgain");
+    } catch (ex) {}
+
+    if (showPrompt) {
+      let checkbox = {value:false};
+      let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
+                             .getService(Ci.nsIPromptService);
+      let bundle = document.getElementById("bundle_messenger");
+      let ok = promptService.confirmEx(window,
+                                       bundle.getString(aCommand + "Title"),
+                                       bundle.getString(aCommand + "Message"),
+                                       promptService.STD_YES_NO_BUTTONS,
+                                       null, null, null,
+                                       bundle.getString(aCommand + "DontAsk"),
+                                       checkbox) == 0;
+      if (checkbox.value)
+        pref.setBoolPref("mail." + aCommand + ".dontAskAgain", true);
+      if (!ok)
+        return false;
+    }
+    return true;
+  },
+
+  get _tree() {
+    let tree = document.getElementById("folderTree");
+    delete this._tree;
+    return this._tree = tree;
+  }
+};
+
+/**
+ * Sorts the passed in array of folder items using the folder sort key
+ *
+ * @param aFolders - the array of ftvItems to sort.
+ */
+function sortFolderItems (aFtvItems) {
+  function sorter(a, b) {
+    let sortKey = a._folder.compareSortKeys(b._folder);
+    if (sortKey)
+      return sortKey;
+    return a.text.toLowerCase() > b.text.toLowerCase();
+  }
+  aFtvItems.sort(sorter);
+}
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -39,17 +39,17 @@
 #
 # ***** END LICENSE BLOCK *****
 
 var gMessengerBundle = document.getElementById("bundle_messenger");
 
 // Controller object for folder pane
 var FolderPaneController =
 {
-   supportsCommand: function(command)
+  supportsCommand: function(command)
   {
     switch ( command )
     {
       case "cmd_delete":
       case "cmd_shiftDelete":
       case "button_delete":
         // Even if the folder pane has focus, don't do a folder delete if
         // we have a selected message, but do a message delete instead.
@@ -114,20 +114,28 @@ var FolderPaneController =
     // really disabled. kick out if the command should be disabled.
     if (!this.isCommandEnabled(command)) return;
 
     switch ( command )
     {
       case "cmd_delete":
       case "cmd_shiftDelete":
       case "button_delete":
-        MsgDeleteFolder();
+        // Even if the folder pane has focus, don't do a folder delete if
+        // we have a selected message, but delete the message instead.
+        if (GetNumSelectedMessages() == 0)
+          gFolderTreeController.deleteFolder();
+        else
+          DefaultController.doCommand(command);
+        break;
+      case "cmd_deleteFolder":
+        gFolderTreeController.deleteFolder();
         break;
       case "button_compact":
-        MsgCompactFolder(false);
+        gFolderTreeController.compactFolder(false);
         break;
     }
   },
 
   onEvent: function(event)
   {
   }
 };
@@ -585,17 +593,17 @@ var DefaultController =
         break;
       case "cmd_expandAllThreads":
         gDBView.doCommand(nsMsgViewCommandType.expandAll);
         break;
       case "cmd_collapseAllThreads":
         gDBView.doCommand(nsMsgViewCommandType.collapseAll);
         break;
       case "cmd_renameFolder":
-        MsgRenameFolder();
+        gFolderTreeController.renameFolder();
         return;
       case "cmd_sendUnsentMsgs":
         // if offline, prompt for sendUnsentMessages
         if (MailOfflineMgr.isOnline())
           SendUnsentMessages();
         else
           MailOfflineMgr.goOnlineToSendMessages(msgWindow);
         return;
@@ -616,17 +624,17 @@ var DefaultController =
         return;
       case "cmd_saveAsTemplate":
         MsgSaveAsTemplate();
         return;
       case "cmd_viewPageSource":
         ViewPageSource(GetSelectedMessages());
         return;
       case "cmd_setFolderCharset":
-        MsgFolderProperties();
+        gFolderTreeController.editFolder();
         return;
       case "cmd_reload":
         ReloadMessage();
         return;
       case "cmd_find":
         // Make sure the message pane has focus before we start a find since we
         // only support searching within the message body.
         SetFocusMessagePane();
@@ -643,17 +651,17 @@ var DefaultController =
         // only support searching within the message body.
         SetFocusMessagePane();
         document.getElementById("FindToolbar").onFindAgainCommand(true);
         return;
       case "cmd_markReadByDate":
         MsgMarkReadByDate();
         return;
       case "cmd_properties":
-        MsgFolderProperties();
+        gFolderTreeController.editFolder();
         return;
       case "cmd_search":
         MsgSearchMessages();
         return;
       case "button_mark":
       case "cmd_markAsRead":
         MsgMarkMsgAsRead();
         return;
@@ -690,23 +698,23 @@ var DefaultController =
         return;
       case "cmd_runJunkControls":
         filterFolderForJunk();
         return;
       case "cmd_deleteJunk":
         deleteJunkInFolder();
         return;
       case "cmd_emptyTrash":
-        MsgEmptyTrash();
+        gFolderTreeController.emptyTrash();
         return;
       case "cmd_compactFolder":
-        MsgCompactFolder(true);
+        gFolderTreeController.compactFolder(true);
         return;
       case "button_compact":
-        MsgCompactFolder(false);
+        gFolderTreeController.compactFolder(false);
         return;
       case "cmd_downloadFlagged":
           gDBView.doCommand(nsMsgViewCommandType.downloadFlaggedForOffline);
           break;
       case "cmd_downloadSelected":
           gDBView.doCommand(nsMsgViewCommandType.downloadSelectedForOffline);
           break;
       case "cmd_synchronizeOffline":
@@ -749,22 +757,17 @@ var DefaultController =
       goSetMenuValue('cmd_undo', 'valueDefault');
       goSetMenuValue('cmd_redo', 'valueDefault');
     }
   }
 };
 
 function GetNumSelectedMessages()
 {
-    try {
-        return gDBView.numSelected;
-    }
-    catch (ex) {
-        return 0;
-    }
+  return gDBView ? gDBView.numSelected : 0;
 }
 
 var gLastFocusedElement=null;
 
 function FocusRingUpdate_Mail()
 {
   // WhichPaneHasFocus() uses on top.document.commandDispatcher.focusedElement
   // to determine which pane has focus
@@ -789,39 +792,35 @@ function FocusRingUpdate_Mail()
     // can we optimize
     // and just update cmd_delete and button_delete?
     UpdateMailToolbar("focus");
   }
 }
 
 function WhichPaneHasFocus()
 {
-  var threadTree = GetThreadTree();
-  var folderTree = GetFolderTree();
-  var messagePane = GetMessagePane();
-
   if (top.document.commandDispatcher.focusedWindow == GetMessagePaneFrame())
-    return messagePane;
+    return GetMessagePane();
 
   var currentNode = top.document.commandDispatcher.focusedElement;
   while (currentNode) {
-    if (currentNode === threadTree ||
-        currentNode === folderTree ||
-        currentNode === messagePane)
+    if (currentNode === document.getElementById('threadTree') ||
+        currentNode === document.getElementById("folderTree") ||
+        currentNode === document.getElementById("messagepanebox"))
       return currentNode;
 
     currentNode = currentNode.parentNode;
   }
   return null;
 }
 
 function SetupCommandUpdateHandlers()
 {
   // folder pane
-  var widget = GetFolderTree();
+  var widget = document.getElementById("folderTree");
   if ( widget )
     widget.controllers.appendController(FolderPaneController);
 
   top.controllers.insertControllerAt(0, DefaultController);
 }
 
 function UnloadCommandUpdateHandlers()
 {
@@ -875,21 +874,20 @@ function IsRenameFolderEnabled()
 {
   var folders = GetSelectedMsgFolders();
   return folders.length == 1 && folders[0].canRename &&
          isCommandEnabled("cmd_renameFolder");
 }
 
 function IsCanSearchMessagesEnabled()
 {
-  var folderURI = GetSelectedFolderURI();
-  if (!folderURI)
+  var folder = GetSelectedMsgFolders()[0];
+  if (!folder)
     return false;
-  var server = GetServer(folderURI);
-  return server.canSearchMessages;
+  return folder.server.canSearchMessages;
 }
 function IsFolderCharsetEnabled()
 {
   return IsFolderSelected();
 }
 
 function IsPropertiesEnabled(command)
 {
@@ -918,108 +916,54 @@ function IsFolderSelected()
   return folders.length == 1 && !folders[0].isServer;
 }
 
 function IsMessageDisplayedInMessagePane()
 {
   return (!IsMessagePaneCollapsed() && (GetNumSelectedMessages() > 0));
 }
 
-function MsgDeleteFolder()
-{
-    var folderTree = GetFolderTree();
-    var selectedFolders = GetSelectedMsgFolders();
-    for (var i = 0; i < selectedFolders.length; i++)
-    {
-        var selectedFolder = selectedFolders[i];
-        var specialFolder = getSpecialFolderString(selectedFolder);
-        if (specialFolder != "Inbox" && specialFolder != "Trash")
-        {
-            var folder = selectedFolder.QueryInterface(Components.interfaces.nsIMsgFolder);
-            if (folder.flags & MSG_FOLDER_FLAG_VIRTUAL)
-            {
-                if (gCurrentVirtualFolderUri == selectedFolder.URI)
-                  gCurrentVirtualFolderUri = null;
-                var array = Components.classes["@mozilla.org/array;1"]
-                                      .createInstance(Components.interfaces.nsIMutableArray);
-                array.appendElement(folder, false);
-                folder.parent.deleteSubFolders(array, msgWindow);
-                continue;
-            }
-            var protocolInfo = Components.classes["@mozilla.org/messenger/protocol/info;1?type=" + selectedFolder.server.type].getService(Components.interfaces.nsIMsgProtocolInfo);
-
-            // do not allow deletion of special folders on imap accounts
-            if ((specialFolder == "Sent" ||
-                specialFolder == "Drafts" ||
-                specialFolder == "Templates" ||
-                (specialFolder == "Junk" && !CanRenameDeleteJunkMail(GetSelectedFolderURI()))) &&
-                !protocolInfo.specialFoldersDeletionAllowed)
-            {
-                var errorMessage = gMessengerBundle.getFormattedString("specialFolderDeletionErr",
-                                                    [specialFolder]);
-                var specialFolderDeletionErrTitle = gMessengerBundle.getString("specialFolderDeletionErrTitle");
-                var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                                              .getService(Components.interfaces.nsIPromptService);
-
-                promptService.alert(window, specialFolderDeletionErrTitle, errorMessage);
-                continue;
-            }
-            else if (isNewsURI(selectedFolder.URI))
-            {
-                var unsubscribe = ConfirmUnsubscribe(selectedFolder);
-                if (unsubscribe)
-                    UnSubscribe(selectedFolder);
-            }
-            else
-            {
-                var array = Components.classes["@mozilla.org/array;1"]
-                                      .createInstance(Components.interfaces.nsIMutableArray);
-                array.appendElement(selectedFolder, false);
-                selectedFolder.parent.deleteSubFolders(array, msgWindow);
-            }
-        }
-    }
-}
-
 function SetFocusThreadPaneIfNotOnMessagePane()
 {
   var focusedElement = WhichPaneHasFocus();
 
   if((focusedElement != GetThreadTree()) &&
      (focusedElement != GetMessagePane()))
      SetFocusThreadPane();
 }
 
 // 3pane related commands.  Need to go in own file.  Putting here for the moment.
 function SwitchPaneFocus(event)
 {
-  var folderTree = GetFolderTree();
+  var folderTree = document.getElementById("folderTree");
   var threadTree = GetThreadTree();
   var messagePane = GetMessagePane();
 
+  var folderPaneCollapsed = document.getElementById("folderPaneBox").collapsed;
+
   var focusedElement = WhichPaneHasFocus();
   if (focusedElement == null)       // focus not on one of the main three panes (probably toolbar)
     focusedElement = threadTree;    // treat as if on thread tree
 
   if (event && event.shiftKey)
   {
     // Reverse traversal: Message -> Thread -> Folder -> Message
-    if (focusedElement == threadTree && !IsFolderPaneCollapsed())
+    if (focusedElement == threadTree && !folderPaneCollapsed)
       folderTree.focus();
     else if (focusedElement != messagePane && !IsMessagePaneCollapsed())
       SetFocusMessagePane();
     else
       threadTree.focus();
   }
   else
   {
     // Forward traversal: Folder -> Thread -> Message -> Folder
     if (focusedElement == threadTree && !IsMessagePaneCollapsed())
       SetFocusMessagePane();
-    else if (focusedElement != folderTree && !IsFolderPaneCollapsed())
+    else if (focusedElement != folderTree && !folderPaneCollapsed)
       folderTree.focus();
     else
       threadTree.focus();
   }
 }
 
 function SetFocusThreadPane()
 {
--- a/mail/base/content/mailCommands.js
+++ b/mail/base/content/mailCommands.js
@@ -69,18 +69,17 @@ function getBestIdentity(identities, opt
       // normalize case on the optional hint to improve our chances of finding a match
       optionalHint = optionalHint.toLowerCase();
 
       var id;
       // iterate over all of the identities
       var tempID;
 
       var lengthOfLongestMatchingEmail = 0;
-      for (id = 0; id < identitiesCount; ++id) {
-        tempID = identities.GetElementAt(id).QueryInterface(Components.interfaces.nsIMsgIdentity);
+      for each (var tempID in fixIterator(identities, nsIMsgIdentity)) {
         if (optionalHint.indexOf(tempID.email.toLowerCase()) >= 0) {
           // Be careful, the user can have several adresses with the same
           // postfix e.g. aaa.bbb@ccc.ddd and bbb@ccc.ddd. Make sure we get the
           // longest match.
           if (tempID.email.length > lengthOfLongestMatchingEmail) {
             identity = tempID;
             lengthOfLongestMatchingEmail = tempID.email.length;
           }
@@ -497,16 +496,14 @@ function deleteAllInFolder(commandName)
   var iter = folder.subFolders;
   while (iter.hasMoreElements())
     folder.propagateDelete(iter.getNext(), true, msgWindow);
 
   var children = Components.classes["@mozilla.org/array;1"]
                   .createInstance(Components.interfaces.nsIMutableArray);
   
   // Delete messages.
-  iter = folder.getMessages(msgWindow);
-  while (iter.hasMoreElements()) {
-    children.appendElement(iter.getNext(), false);
-  }
+  var messages = [m for each (m in fixIterator(folder.getMessages(msgWindow)))];
+  var children = toXPCOMArray(messages, Components.interfaces.nsISupportsArray);
   folder.deleteMessages(children, msgWindow, true, false, null, false); 
   children.clear();                                       
 }
 
--- a/mail/base/content/mailContextMenus.js
+++ b/mail/base/content/mailContextMenus.js
@@ -397,17 +397,17 @@ function OpenMessageByHeader(messageHead
 
     window.openDialog("chrome://messenger/content/messageWindow.xul",
                       "_blank", "all,chrome,dialog=no,status,toolbar",
                       messageURI, folderURI, null);
   }
   else
   {
     if (msgWindow.openFolder != folderURI)
-      SelectFolder(folderURI);
+      gFolderTreeView.selectFolder(folder);
 
     var tree = null;
     var wintype = document.documentElement.getAttribute('windowtype');
     if (wintype != "mail:messageWindow")
     {
       tree = GetThreadTree();
       tree.view.selection.clearSelection();
     }
@@ -486,17 +486,17 @@ function CheckForMessageIdInFolder(folde
     folder.setMsgDatabase(null);
   }
 
   return messageHeader;
 }
 
 function folderPaneOnPopupHiding()
 {
-  RestoreSelectionWithoutContentLoad(GetFolderTree());
+  RestoreSelectionWithoutContentLoad(document.getElementById("folderTree"));
 }
 
 function fillFolderPaneContextMenu()
 {
   var folders = GetSelectedMsgFolders();
   if (!folders.length)
     return false;
 
--- a/mail/base/content/mailWindow.js
+++ b/mail/base/content/mailWindow.js
@@ -47,22 +47,16 @@ var msgWindow;
 var msgComposeService;
 var accountManager;
 
 var gMessengerBundle;
 var gBrandBundle;
 
 var gContextMenu;
 
-var accountManagerDataSource;
-var folderDataSource;
-var unreadFolderDataSource;
-var favoriteFoldersDataSource;
-var recentFoldersDataSource;
-
 var gAccountCentralLoaded = true;
 
 var gAutoSyncManager;
 const nsIAutoSyncMgrListener = Components.interfaces.nsIAutoSyncMgrListener;
 
 var gAutoSyncMonitor = {
   logEnabled : false,
   msgWindow : null,
@@ -264,30 +258,16 @@ function CreateMailWindowGlobals()
 
   msgComposeService = Components.classes['@mozilla.org/messengercompose;1']
                                 .getService(Components.interfaces.nsIMsgComposeService);
 
   accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager);
 
   gMessengerBundle = document.getElementById("bundle_messenger");
   gBrandBundle = document.getElementById("bundle_brand");
-
-  //Create datasources
-  var prefix = "@mozilla.org/rdf/datasource;1?name=";
-  accountManagerDataSource = Components.classes[prefix + "msgaccountmanager"]
-                                       .getService();
-  folderDataSource = Components.classes[prefix + "mailnewsfolders"]
-                               .getService();
-  unreadFolderDataSource = Components.classes[prefix + "mailnewsunreadfolders"]
-                                     .getService();
-  favoriteFoldersDataSource = Components.classes[prefix + "mailnewsfavefolders"]
-                                        .getService();
-  recentFoldersDataSource = Components.classes[prefix + "mailnewsrecentfolders"]
-                                      .getService();
-                                      
   gAutoSyncManager = Components.classes["@mozilla.org/imap/autosyncmgr;1"]
                                        .getService(Components.interfaces.nsIAutoSyncManager);
   gAutoSyncMonitor.msgWindow = msgWindow;
   gAutoSyncManager.addListener(gAutoSyncMonitor);
 }
 
 function InitMsgWindow()
 {
@@ -301,22 +281,16 @@ function InitMsgWindow()
             .AddMsgWindow(msgWindow);
   getBrowser().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 AddDataSources()
-{
-  accountManagerDataSource = accountManagerDataSource.QueryInterface(Components.interfaces.nsIRDFDataSource);
-  folderDataSource = folderDataSource.QueryInterface(Components.interfaces.nsIRDFDataSource);
-}
-
 // We're going to implement our status feedback for the mail window in JS now.
 // the following contains the implementation of our status feedback object
 
 function nsMsgStatusFeedback()
 {
 }
 
 nsMsgStatusFeedback.prototype =
@@ -492,17 +466,17 @@ nsMsgWindowCommands.prototype =
     if (iid.equals(Components.interfaces.nsIMsgWindowCommands) ||
         iid.equals(Components.interfaces.nsISupports))
       return this;
     throw Components.results.NS_NOINTERFACE;
   },
 
   selectFolder: function(folderUri)
   {
-    SelectFolder(folderUri);
+    gFolderTreeView.selectFolder(GetMsgFolderFromUri(folderUri));
   },
 
   selectMessage: function(messageUri)
   {
     SelectMessage(messageUri);
   },
 
   clearMsgPane: function()
@@ -623,44 +597,37 @@ function ObserveDisplayDeckChange(event)
   if (nowSelected != gCurrentDisplayDeckId)
   {
     if (nowSelected == "threadPaneBox")
       ShowingThreadPane();
     else
       HidingThreadPane();
 
     if (nowSelected == "accountCentralBox") {
-      if (!IsFolderPaneCollapsed())
-        GetFolderTree().focus();
+      if (!document.getElementById("folderPaneBox").collapsed)
+        document.getElementById("folderTree").focus();
 
       gAccountCentralLoaded = true;
     } else
       gAccountCentralLoaded = false;
     gCurrentDisplayDeckId = nowSelected;
   }
 }
 
 // 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);
-    SelectFolder(inboxFolder.URI);
+  ShowThreadPane();
+  gFolderTreeView.selectFolder(GetInboxFolder(server));
 
-    if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail())  {
-      if (server.type != "imap")
-        GetMessagesForInboxOnServer(server);
-    }
-  }
-  catch (ex) {
-      dump("Error opening inbox for server -> " + ex + "\n");
-      return;
+  if (MailOfflineMgr.isOnline() || MailOfflineMgr.getNewMail()) {
+    if (server.type != "imap")
+      GetMessagesForInboxOnServer(server);
   }
 }
 
 function GetSearchSession()
 {
   if (("gSearchSession" in top) && gSearchSession)
     return gSearchSession;
   else
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -738,18 +738,19 @@ function UpdateJunkToolbarButton()
 function UpdateDeleteToolbarButton()
 {
   var deleteButtonDeck = document.getElementById("delete-deck");
   if (!deleteButtonDeck)
     return;
 
   // Never show "Undelete" in the 3-pane for folders, when delete would
   // apply to the selected folder.
-  if (this.WhichPaneHasFocus && WhichPaneHasFocus() == GetFolderTree()
-      && GetNumSelectedMessages() == 0)
+  if (this.WhichPaneHasFocus &&
+      WhichPaneHasFocus() == document.getElementById("folderTree") &&
+      GetNumSelectedMessages() == 0)
     deleteButtonDeck.selectedIndex = 0;
   else
     deleteButtonDeck.selectedIndex = SelectedMessagesAreDeleted() ? 1 : 0;
 }
 function UpdateDeleteCommand()
 {
   var value = "value";
   var uri = GetFirstSelectedMessage();
@@ -1185,22 +1186,22 @@ function MsgSaveAsTemplate()
 }
 
 function MsgOpenNewWindowForFolder(uri, key)
 {
   var uriToOpen = uri;
   var keyToSelect = key;
 
   if (!uriToOpen)
-    // use GetSelectedFolderURI() to find out which message to open instead of
+    // 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. GetSelectedFolderURI() will return the message that is
+    // highlighted. GetSelectedMsgFolders() will return the folder that is
     // highlighted.
-    uriToOpen = GetSelectedFolderURI();
+    uriToOpen = GetSelectedMsgFolders()[0].URI;
 
   if (uriToOpen)
     window.openDialog("chrome://messenger/content/", "_blank", "chrome,all,dialog=no", uriToOpen, keyToSelect);
 }
 
 function CreateToolbarTooltip(document, event)
 {
   event.stopPropagation();
@@ -1237,19 +1238,16 @@ function DisplayFolderAndThreadPane(show
 
   document.getElementById("threadpane-splitter").collapsed = collapse;
   document.getElementById("folderpane_splitter").collapsed = collapse;
   document.getElementById("folderPaneBox").collapsed = collapse;
   try {
     document.getElementById("search-container").collapsed = collapse;
   } catch (ex) {}
   try {
-    document.getElementById("folder-location-container").collapsed = collapse;
-  } catch (ex) {}
-  try {
     document.getElementById("mailviews-container").collapsed = collapse;
   } catch (ex) {}
 }
 
 /**
  * mailTabType provides both "folder" and "message" tab modes. Under the
  * previous TabOwner framework, their logic was separated into two 'classes'
  * which called common helper methods and had similar boilerplate logic.
@@ -1321,25 +1319,26 @@ let mailTabType = {
     aTab.messenger = messenger;
 
     aTab.msgSelectedFolder = gMsgFolderSelected;
 
     // 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.
-    GetFolderTree().view.selection.clearSelection();
-    GetFolderTree().view.selection.currentIndex = -1;
+    var folderTree = document.getElementById("folderTree");
+    folderTree.view.selection.clearSelection();
+    folderTree.view.selection.currentIndex = -1;
     gMsgFolderSelected = null;
     msgWindow.openFolder = null;
 
     // Clear thread pane selection - otherwise, the tree tries to impose the
-    // current selection on the new view.
-    gDBView = null; // Clear gDBView so we won't try to close it.
-    SelectFolder(aTab.uriToOpen);
+    // the current selection on the new view.
+    gDBView = null; // clear gDBView so we won't try to close it.
+    gFolderTreeView.selectFolder(GetMsgFolderFromUri(aTab.uriToOpen));
     aTab.dbView = gDBView;
   },
 
   closeTab: function(aTab) {
     if (aTab.dbView)
       aTab.dbView.close();
     if (aTab.messenger)
       aTab.messenger.setWindow(null, null);
@@ -1386,29 +1385,28 @@ let mailTabType = {
     // restore globals
     messenger = aTab.messenger;
     gDBView = aTab.dbView;
     gSearchSession = aTab.searchSession;
 
     // restore view state if we had one
     if (gDBView)
     {
-      var folderTree = GetFolderTree();
-      var row = EnsureFolderIndex(folderTree.builderView, gDBView.msgFolder);
+      var row = gFolderTreeView.getIndexOfFolder(gDBView.msgFolder);
 
-      var folderTreeBoxObj = folderTree.treeBoxObject;
-      var folderTreeSelection = folderTreeBoxObj.view.selection;
+      var treeBoxObj = document.getElementById("folderTree").treeBoxObject;
+      var 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 = gDBView.msgFolder;
         folderTreeSelection.selectEventsSuppressed = true;
         folderTreeSelection.select(row);
-        folderTreeBoxObj.ensureRowIsVisible(row);
+        treeBoxObj.ensureRowIsVisible(row);
         folderTreeSelection.selectEventsSuppressed = false;
       }
       // This sets the thread pane tree's view to the gDBView view.
       UpdateSortIndicators(gDBView.sortType, gDBView.sortOrder);
       RerootThreadPane();
       // 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
@@ -1457,24 +1455,24 @@ window.addEventListener("load", function
 );
 
 function MsgOpenNewTabForFolder(uri, key)
 {
   var uriToOpen = uri;
   var keyToSelect = key;
 
   if (!uriToOpen)
-    // Use GetSelectedFolderURI() to find out which message to open instead of
+    // 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. GetSelectedFolderURI() will return the message that is
+    // highlighted. GetSelectedMsgFolders() will return the message that is
     // highlighted.
-    uriToOpen = GetSelectedFolderURI();
-
-  // Set up the first tab, which was previously invisible.
+    uriToOpen = GetSelectedMsgFolders()[0].URI;
+  
+  // set up the first tab, which was previously invisible.
   // This assumes the first tab is always a 3-pane ui, which
   // may not be right, especially if we have the ability
   // to persist your tab setup.
   document.getElementById('tabmail').openTab("folder", uriToOpen);
 }
 
 function MsgOpenNewTabForMessage(messageKey, folderUri)
 {
@@ -1610,24 +1608,24 @@ function MsgOpenNewWindowForMessage(mess
     // Use GetFirstSelectedMessage() to find out which message to open
     // instead of gDBView.getURIForViewIndex(currentIndex). This is
     // required because on a right-click, the currentIndex value will be
     // different from the actual row that is highlighted.
     // GetFirstSelectedMessage() will return the message that is
     // highlighted.
     messageUri = GetFirstSelectedMessage();
 
-  if (!folderUri)
-    // Use GetSelectedFolderURI() to find out which message to open
-    // instead of gDBView.getURIForViewIndex(currentIndex). This is
-    // required because on a right-click, the currentIndex value will be
-    // different from the actual row that is highlighted.
-    // GetSelectedFolderURI() will return the message that is
-    // highlighted.
-    folderUri = GetSelectedFolderURI();
+    if (!folderUri)
+      // Use GetSelectedMsgFolders() to find out which message to open
+      // instead of gDBView.getURIForViewIndex(currentIndex).  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.
+      folderUri = GetSelectedMsgFolders()[0].URI;
 
   // be sure to pass in the current view....
   if (messageUri && folderUri) {
     window.openDialog("chrome://messenger/content/messageWindow.xul", "_blank",
                       "all,chrome,dialog=no,status,toolbar",
                       messageUri, folderUri, gDBView);
   }
 }
@@ -1664,17 +1662,17 @@ function MsgMarkReadByDate()
 {
   window.openDialog("chrome://messenger/content/markByDate.xul","",
                     "chrome,modal,titlebar,centerscreen",
                     GetLoadedMsgFolder());
 }
 
 function MsgMarkAllRead()
 {
-  var folder = GetMsgFolderFromUri(GetSelectedFolderURI(), true);
+  var folder = GetSelectedMsgFolders()[0];
 
   if (folder)
     folder.markAllMessagesRead();
 }
 
 function MsgFilters(emailAddress, folder)
 {
   if (!folder)
@@ -1907,17 +1905,20 @@ function IsGetNextNMessagesEnabled()
   }
 
   menuItem.setAttribute("hidden","true");
   return false;
 }
 
 function IsCompactFolderEnabled()
 {
-  var server = GetServer(GetSelectedFolderURI());
+  var folder = GetSelectedMsgFolders()[0];
+  if (!folder)
+    return;
+  let server = folder.server;
   return (server &&
       (server.type != 'nntp') && // compact news folder is not supported
       ((server.type != 'imap') || server.canCompactFoldersOnServer) &&
       isCommandEnabled("cmd_compactFolder")); // checks e.g. if IMAP is offline
 }
 
 function SetUpToolbarButtons(uri)
 {
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -636,65 +636,65 @@
     <menuitem id="folderPaneContext-newsUnsubscribe"
               label="&folderContextUnsubscribe.label;"
               accesskey="&folderContextUnsubscribe.accesskey;"
               oncommand="MsgUnsubscribe();"/>
     <menuseparator id="folderPaneContext-sep1"/>
     <menuitem id="folderPaneContext-new"
               label="&folderContextNew.label;"
               accesskey="&folderContextNew.accesskey;"
-              oncommand="MsgNewFolder(NewFolder);"/>
+              oncommand="gFolderTreeController.newFolder();"/>
     <menuitem id="folderPaneContext-remove"
               label="&folderContextRemove.label;"
               accesskey="&folderContextRemove.accesskey;"
-              oncommand="MsgDeleteFolder();"/>
+              oncommand="gFolderTreeController.deleteFolder();"/>
     <menuitem id="folderPaneContext-rename"
               label="&folderContextRename.label;"
               accesskey="&folderContextRename.accesskey;"
-              oncommand="MsgRenameFolder();"/>
+              oncommand="gFolderTreeController.renameFolder();"/>
     <menuseparator id="folderPaneContext-sep2"/>
     <menuitem id="folderPaneContext-compact"
               label="&folderContextCompact.label;"
               accesskey="&folderContextCompact.accesskey;"
-              oncommand="MsgCompactFolder(false);"/>
+              oncommand="gFolderTreeController.compactFolder(false);"/>
     <menuitem id="folderPaneContext-markMailFolderAllRead"
               label="&folderContextMarkMailFolderRead.label;"
               accesskey="&folderContextMarkMailFolderRead.accesskey;"
               oncommand="MsgMarkAllRead();"/>
     <menuitem id="folderPaneContext-markNewsgroupAllRead"
               label="&folderContextMarkNewsgroupRead.label;"
               accesskey="&folderContextMarkNewsgroupRead.accesskey;"
               oncommand="MsgMarkAllRead();"/>
     <menuitem id="folderPaneContext-emptyTrash"
               label="&folderContextEmptyTrash.label;"
               accesskey="&folderContextEmptyTrash.accesskey;"
-              oncommand="MsgEmptyTrash();"/>
+              oncommand="gFolderTreeController.emptyTrash();"/>
     <menuitem id="folderPaneContext-emptyJunk"
               label="&folderContextEmptyJunk.label;"
               accesskey="&folderContextEmptyJunk.accesskey;"
-              oncommand="deleteAllInFolder('emptyJunk');"/>
+              oncommand="gFolderTreeController.emptyJunk();"/>
     <menuitem id="folderPaneContext-sendUnsentMessages"
               label="&folderContextSendUnsentMessages.label;"
               accesskey="&folderContextSendUnsentMessages.accesskey;"
               oncommand="goDoCommand('cmd_sendUnsentMsgs')"/>
     <menuseparator id="folderPaneContext-sep3"/>
     <menuitem id="folderPaneContext-favoriteFolder"
               type="checkbox"
               label="&folderContextFavoriteFolder.label;"
               accesskey="&folderContextFavoriteFolder.accesskey;"
               check="false"
               oncommand="ToggleFavoriteFolderFlag();"/>
     <menuitem id="folderPaneContext-properties"
               label="&folderContextProperties.label;"
               accesskey="&folderContextProperties.accesskey;"
-              oncommand="MsgFolderProperties();"/>
+              oncommand="gFolderTreeController.editFolder();"/>
     <menuitem id="folderPaneContext-settings"
               label="&folderContextSettings.label;"
               accesskey="&folderContextSettings.accesskey;"
-              oncommand="MsgFolderProperties();"/>
+              oncommand="gFolderTreeController.editFolder();"/>
   </popup>
 
   <popup id="messagePaneContext"
           onpopupshowing="if (event.target != this) return true; gContextMenu = new nsContextMenu(this); return fillMessagePaneContextMenu();"
           onpopuphiding="if (event.target == this) gContextMenu = null;">
     <menuseparator id="messagePaneContext-sep-link"/>
     <menuitem id="messagePaneContext-selectall"
               label="&selectAllCmd.label;"
@@ -897,20 +897,20 @@
           <menupopup id="menu_FilePopup" onpopupshowing="file_init();">
           <menu id="menu_New">
             <menupopup id="menu_NewPopup" onpopupshowing="menu_new_init();">
               <menuitem id="newNewMsgCmd" label="&newNewMsgCmd.label;"
                         accesskey="&newNewMsgCmd.accesskey;"
                         key="key_newMessage2"
                         oncommand="MsgNewMessage(null);"/>
               <menuitem id="menu_newFolder" label="&newFolderCmd.label;"
-                         oncommand="MsgNewFolder(NewFolder);"
+                         oncommand="gFolderTreeController.newFolder();"
                          accesskey="&newFolderCmd.accesskey;"/>
               <menuitem id="menu_newVirtualFolder" label="&newVirtualFolderCmd.label;"
-                         oncommand="MsgVirtualFolderProperties(false);"
+                         oncommand="gFolderTreeController.newVirtualFolder();"
                          accesskey="&newVirtualFolderCmd.accesskey;"/>
               <menuitem id="newAccountMenuItem" label="&newAccountCmd.label;"
                          accesskey="&newAccountCmd.accesskey;"
                          oncommand="MsgAccountWizard();"/>
               <menuseparator id="newPopupMenuSeparator"/>
               <menuitem id="menu_newCard"/>
             </menupopup>
           </menu>
@@ -1092,23 +1092,27 @@
             <menuseparator id="viewMenuAfterPaneVerticalSeparator"/>
             <menuitem id="menu_showMessage" type="checkbox" label="&showMessageCmd.label;" key="key_toggleMessagePane"
                       accesskey="&showMessageCmd.accesskey;" oncommand="MsgToggleMessagePane();"/>
           </menupopup>
         </menu>
         <menu id="menu_FolderViews" label="&folderView.label;" accesskey="&folderView.accesskey;">
           <menupopup id="menu_FolderViewsPopup" onpopupshowing="InitViewFolderViewsMenu(event)">
             <menuitem id="menu_allFolders"      label="&allFolders.label;" accesskey="&allFolders.accesskey;"
-                      type="radio" name="viewmessages" oncommand="loadFolderView(0);"/>
+                      type="radio" name="viewmessages"
+                      oncommand="gFolderTreeView.mode = 'all';"/>
             <menuitem id="menu_unreadFolders"   label="&unreadFolders.label;" accesskey="&unreadFolders.accesskey;"
-                      type="radio" name="viewmessages" oncommand="loadFolderView(1);"/>
+                      type="radio" name="viewmessages"
+                      oncommand="gFolderTreeView.mode = 'unread';"/>
             <menuitem id="menu_favoriteFolders" label="&favoriteFolders.label;" accesskey="&favoriteFolders.accesskey;"
-                      type="radio" name="viewmessages" oncommand="loadFolderView(2);"/>
+                      type="radio" name="viewmessages"
+                      oncommand="gFolderTreeView.mode = 'favorite';"/>
             <menuitem id="menu_recentFolders"   label="&recentFolders.label;" accesskey="&recentFolders.accesskey;"
-                      type="radio" name="viewmessages" oncommand="loadFolderView(3);"/>
+                      type="radio" name="viewmessages"
+                      oncommand="gFolderTreeView.mode = 'recent';"/>
           </menupopup>
         </menu>
         <menuseparator id="viewSortMenuSeparator"/>
         <menu id="viewSortMenu" accesskey="&sortMenu.accesskey;" label="&sortMenu.label;">
           <menupopup id="menu_viewSortPopup" onpopupshowing="InitViewSortByMenu()">
             <menuitem id="sortByDateMenuitem" type="radio" name="sortby" label="&sortByDateCmd.label;" accesskey="&sortByDateCmd.accesskey;" oncommand="MsgSortThreadPane('byDate')"/>
             <menuitem id="sortByReceivedMenuitem" type="radio" name="sortby" label="&sortByReceivedCmd.label;" accesskey="&sortByReceivedCmd.accesskey;" oncommand="MsgSortThreadPane('byReceived')"/>
             <menuitem id="sortByFlagMenuitem" type="radio" name="sortby" label="&sortByStarCmd.label;" accesskey="&sortByStarCmd.accesskey;" oncommand="MsgSortThreadPane('byFlagged')"/>
@@ -1885,15 +1889,15 @@
     <statusbarpanel class="statusbarpanel-progress"
                     id="quotaPanel" hidden="true">
       <stack>
         <progressmeter class="progressmeter-statusbar"
                        id="quotaMeter"
                        mode="normal"
                        value="0" />
         <label id="quotaLabel"
-               onclick="MsgFolderProperties('QuotaTab');" />
+               onclick="gFolderTreeController.editFolder('QuotaTab');" />
       </stack>
     </statusbarpanel>
   </hbox>
 </statusbar>
 
 </overlay>
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -79,19 +79,19 @@
 <script type="application/x-javascript" src="chrome://messenger/content/shareglue.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/msgViewNavigation.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/mailWindow.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/msgMail3PaneWindow.js"/>
 <script type="application/x-javascript" src="chrome://messenger/content/mail3PaneWindowCommands.js"/>
 <script type="application/x-javascript" src="chrome://global/content/contentAreaUtils.js"/>
 <script type="application/x-javascript" src="chrome://communicator/content/nsContextMenu.js"/>
 <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/searchBar.js"/>
+<script type="application/x-javascript" src="chrome://messenger/content/folderPane.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/editContactOverlay.js"/>
 
 
 <!-- move needed functions into a single js file -->
 <script type="application/x-javascript" src="chrome://messenger/content/threadPane.js"/>
@@ -179,143 +179,42 @@
     <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">
       <label id="folderColumnLabel" hidden="true" value="&folderColumn.label;"/>
       <sidebarheader id="folderPaneHeader" align="center">
         <label id="folderpane-title"/>
         <spacer flex="1"/>
         <toolbarbutton id="folderview-cycler-left"  class="folderview-cycler"
-                       onclick="CycleFolderView(false);"/>
+                       onclick="gFolderTreeView.cycleMode(false);"/>
         <toolbarbutton id="folderview-cycler-right" class="folderview-cycler"
-                       onclick="CycleFolderView(true);"/>
+                       onclick="gFolderTreeView.cycleMode(true);"/>
       </sidebarheader>
 
-      <tree id="folderTree"
-            class="plain focusring"
+      <tree id="folderTree" class="plain focusring" flex="1"
             treelines="true"
-            flex="1"
+            hidecolumnpicker="true" persist="mode" mode="all"
             context="folderPaneContext"
             disableKeyNavigation="true"
-            datasources="rdf:null"
-            statedatasource="rdf:mailnewsfolders"
-            flags="dont-build-content"
-            ondraggesture="BeginDragFolderTree(event);"
+            ondraggesture="gFolderTreeView._onDragStart(event);"
+            ondragover="gFolderTreeView._onDragOver(event);"
+            ondblclick="gFolderTreeView.onDoubleClick(event);"
             onselect="FolderPaneSelectionChange();">
-        <treechildren tooltip="folderpopup"/>
-        <template>
-          <rule>
-            <conditions>
-              <content uri="?container"/>
-              <member container="?container" child="?member" />
-              <triple subject="?member" predicate="http://home.netscape.com/NC-rdf#IsDeferred" object="false"/>
-            </conditions>
-
-            <bindings>
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#FolderTreeName"
-                       object="?folderTreeName" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#FolderTreeName?sort=true"
-                       object="?folderTreeNameSort" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#FolderTreeSimpleName"
-                       object="?folderTreeSimpleName" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#SpecialFolder"
-                       object="?specialFolder" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#BiffState"
-                       object="?biffState" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#IsServer"
-                       object="?isServer" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#NewMessages"
-                       object="?newMessages" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#HasUnreadMessages"
-                       object="?hasUnreadMessages" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#SubfoldersHaveUnreadMessages"
-                       object="?subfoldersHaveUnreadMessages" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#IsSecure"
-                       object="?isSecure" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#ServerType"
-                       object="?serverType" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#NoSelect"
-                       object="?noSelect" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#ImapShared"
-                       object="?imapShared" />
-              <binding subject="?member"
-                 predicate="http://home.netscape.com/NC-rdf#TotalUnreadMessages"
-                 object="?unreadCount" />
-              <binding subject="?member"
-                       predicate="http://home.netscape.com/NC-rdf#TotalMessages"
-                       object="?totalCount" />
-              <binding subject="?member"
-                     predicate="http://home.netscape.com/NC-rdf#FolderSize"
-                     object="?folderSize" />
-     </bindings>
-
-            <action>
-              <treechildren>
-                <treeitem uri="?member">
-                  <treerow>
-                    <treecell id="folderNameCell"
-                              label="?folderTreeName"
-                              properties="specialFolder-?specialFolder biffState-?biffState isServer-?isServer newMessages-?newMessages hasUnreadMessages-?hasUnreadMessages subfoldersHaveUnreadMessages-?subfoldersHaveUnreadMessages isSecure-?isSecure serverType-?serverType noSelect-?noSelect imapShared-?imapShared"/>
-                    <treecell label="?unreadCount"
-                                  properties="hasUnreadMessages-?hasUnreadMessages subfoldersHaveUnreadMessages-?subfoldersHaveUnreadMessages"/>
-                    <treecell label="?totalCount"
-                                  properties="hasUnreadMessages-?hasUnreadMessages subfoldersHaveUnreadMessages-?subfoldersHaveUnreadMessages"/>
-                    <treecell label="?folderSize"/>
-                  </treerow>
-                </treeitem>
-              </treechildren>
-            </action>
-          </rule>
-        </template>
         <treecols>
           <treecol id="folderNameCol"
                    flex="5"
                    crop="center"
                    persist="width"
                    hideheader="true"
                    ignoreincolumnpicker="true"
                    primary="true"
-                   sort="?folderTreeNameSort"
                    sortActive="true"
                    sortDirection="ascending"/>
-          <splitter class="tree-splitter"/>
-          <treecol id="folderUnreadCol"
-                       persist="hidden width"
-                       hidden="true"
-                       flex="1"
-                       label="&unreadColumn.label;"
-                       selectable="false"/>
-          <splitter class="tree-splitter"/>
-          <treecol id="folderTotalCol"
-                       persist="hidden width"
-                       hidden="true"
-                       flex="1"
-                       label="&totalColumn.label;"
-                       selectable="false"/>
-          <splitter class="tree-splitter"/>
-          <treecol id="folderSizeCol"
-                       persist="hidden width"
-                       hidden="true"
-                       flex="1"
-                       label="&folderSizeColumn.label;"
-                       selectable="false"/>
         </treecols>
+        <treechildren tooltip="folderpopup"/>
       </tree>
     </vbox>
 
     <splitter id="folderpane_splitter" collapse="before" persist="state"/>
 
     <vbox flex="1">
       <box orient="vertical" id="messagesBox" flex="1">
           <deck id="displayDeck" flex="1" selectedIndex="0"
@@ -399,17 +298,17 @@
                         label="&totalColumn.label;" tooltiptext="&totalColumn.tooltip;"/>
                <splitter class="tree-splitter"/>
                <treecol id="locationCol" persist="width" flex="1" hidden="true" ignoreincolumnpicker="true"
                         label="&locationColumn.label;" tooltiptext="&locationColumn.tooltip;"/>
                <splitter class="tree-splitter"/>
                <treecol id="idCol" persist="hidden ordinal width" flex="1" hidden="true"
                         label="&idColumn.label;" tooltiptext="&idColumn.tooltip;"/>
               </treecols>
-            <treechildren ondraggesture="BeginDragThreadPane(event);"/>
+            <treechildren ondraggesture="threadPaneOnDragStart(event);"/>
           </tree>
         </hbox>
         <!-- extensions may overlay in additional panels; don't assume that there are only 2! -->
         </deck> <!-- displayDeck -->
 
       <!-- if you change this id, please change GetThreadAndMessagePaneSplitter() and MsgToggleMessagePane() -->
         <splitter id="threadpane-splitter" collapse="after" persist="state" collapsed="true"
                   onmouseup="OnMouseUpThreadAndMessagePaneSplitter()"/>
--- a/mail/base/content/msgMail3PaneWindow.js
+++ b/mail/base/content/msgMail3PaneWindow.js
@@ -38,33 +38,34 @@
 # 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 *****
 
+Components.utils.import("resource://gre/modules/folderUtils.jsm");
+
 /* 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";
 
 const kStandardPaneConfig = 0;
 const kWidePaneConfig = 1;
 const kVerticalPaneConfig = 2;
 
 const kNumFolderViews = 4; // total number of folder views
 
 // from nsMsgFolderFlags.h
 const MSG_FOLDER_FLAG_ELIDED = 0x10;
 
-var gFolderTree;
 var gMessagePane;
 var gThreadTree;
 var gSearchInput;
 
 var gThreadAndMessagePaneSplitter = null;
 var gUnreadCount = null;
 var gTotalCount = null;
 var gCurrentFolderView;
@@ -166,18 +167,16 @@ var folderListener = {
     OnItemRemoved: function(parentItem, item) { },
 
     OnItemPropertyChanged: function(item, property, oldValue, newValue) { },
 
     OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {
       if (item == gMsgFolderSelected) {
         if(property.toString() == "TotalMessages" || property.toString() == "TotalUnreadMessages") {
           UpdateStatusMessageCounts(gMsgFolderSelected);
-          item = item.QueryInterface(Components.interfaces.nsIRDFResource);
-          UpdateFolderLocationPicker(item);
         }
       }
     },
 
     OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) { },
 
     OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) { },
     OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) { },
@@ -240,17 +239,18 @@ var folderListener = {
               }
             }
           }
           if (uri == gCurrentLoadingFolderURI) {
             viewDebug("uri == current loading folder uri\n");
             gCurrentLoadingFolderURI = "";
             // Scroll to message for virtual folders is done in
             // gSearchNotificationListener.OnSearchDone (see searchBar.js).
-            if (!scrolled && !(gMsgFolderSelected.flags & MSG_FOLDER_FLAG_VIRTUAL))
+            if (!scrolled && gMsgFolderSelected &&
+                !(gMsgFolderSelected.flags & MSG_FOLDER_FLAG_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");
           if (rerootingFolder)
@@ -326,85 +326,24 @@ var folderListener = {
       else if (eventType == "AboutToCompact") {
         if (gDBView)
           gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
       }
       else if (eventType == "CompactCompleted") {
         HandleCompactCompleted(folder);
       }
       else if (eventType == "RenameCompleted") {
-        SelectFolder(folder.URI);
+        gFolderTreeView.selectFolder(folder);
       }
       else if (eventType == "JunkStatusChanged") {
         HandleJunkStatusChanged(folder);
       }
     }
 }
 
-var folderObserver = {
-    canDrop: function(index, orientation)
-    {
-        return CanDropOnFolderTree(index, orientation);
-    },
-
-    onDrop: function(row, orientation)
-    {
-        DropOnFolderTree(row, orientation);
-    },
-
-    onToggleOpenState: function(index)
-    {
-      var folderTree = GetFolderTree();
-
-      // Nothing to do when collapsing an item.
-      if (folderTree.view.isContainerOpen(index))
-        return;
-
-      var folderResource = GetFolderResource(folderTree, index);
-
-      if (folderTree.view.getLevel(index) == 0)
-      {
-        // (Imap/Nntp/Pop) Account item.
-
-        folderResource.QueryInterface(Components.interfaces.nsIMsgFolder)
-                      .server.performExpand(msgWindow);
-      }
-      else if (folderResource instanceof Components.interfaces.nsIMsgImapMailFolder)
-      {
-        // Imap message folder item.
-
-        folderResource.performExpand(msgWindow);
-      }
-    },
-
-    onCycleHeader: function(colID, elt)
-    {
-    },
-
-    onCycleCell: function(row, colID)
-    {
-    },
-
-    onSelectionChanged: function()
-    {
-    },
-
-    onPerformAction: function(action)
-    {
-    },
-
-    onPerformActionOnRow: function(action, row)
-    {
-    },
-
-    onPerformActionOnCell: function(action, row, col)
-    {
-    }
-}
-
 function HandleDeleteOrMoveMsgFailed(folder)
 {
   gDBView.onDeleteCompleted(false);
   if(IsCurrentLoadedFolder(folder)) {
     if(gNextMessageAfterDelete) {
       gNextMessageAfterDelete = null;
       gNextMessageViewIndexAfterDelete = -2;
     }
@@ -600,17 +539,17 @@ function ServerContainsFolder(server, fo
   if (!folder || !server)
     return false;
 
   return server.equals(folder.server);
 }
 
 function SelectServer(server)
 {
-  SelectFolder(server.rootFolder.URI);
+  gFolderTreeView.selectFolder(server.rootFolder);
 }
 
 // we have this incoming server listener in case we need to
 // alter the folder pane selection when a server is removed
 // or changed (currently, when the real username or real hostname change)
 var gThreePaneIncomingServerListener = {
     onServerLoaded: function(server) {},
     onServerUnloaded: function(server) {
@@ -682,18 +621,16 @@ function UpdateMailPaneConfig(aMsgWindow
 
 const MailPrefObserver = {
   observe: function(subject, topic, prefName) {
     // verify that we're changing the mail pane config pref
     if (topic == "nsPref:changed")
     {
       if (prefName == "mail.pane_config.dynamic")
         UpdateMailPaneConfig(true);
-      else if (prefName == "mail.showFolderPaneColumns")
-        UpdateFolderColumnVisibility();
     }
   }
 };
 
 /**
  * Called on startup to initialize various parts of the main window
  */
 function OnLoadMessenger()
@@ -720,34 +657,32 @@ function OnLoadMessenger()
     }
 
     document.documentElement.setAttribute("width", defaultWidth);
     document.documentElement.setAttribute("height", defaultHeight);
   }
 
   gPrefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
   gPrefBranch.addObserver("mail.pane_config.dynamic", MailPrefObserver, false);
-  gPrefBranch.addObserver("mail.showFolderPaneColumns", MailPrefObserver, false);
 
   MailOfflineMgr.init();
   CreateMailWindowGlobals();
   GetMessagePane().collapsed = true;
   // verifyAccounts returns true if the callback won't be called
   if (verifyAccounts(LoadPostAccountWizard))
     LoadPostAccountWizard();
 
   window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 }
 
 function LoadPostAccountWizard()
 {
   InitMsgWindow();
   messenger.setWindow(window, msgWindow);
 
-  InitializeDataSources();
   InitPanes();
   MigrateAttachmentDownloadStore();
   MigrateJunkMailSettings();
   MigrateFolderViews();
 
   accountManager.setSpecialFolders();
   accountManager.loadVirtualFolders();
   accountManager.addIncomingServerListener(gThreePaneIncomingServerListener);
@@ -851,32 +786,30 @@ function HandleAppCommandEvent(evt)
 }
 
 function OnUnloadMessenger()
 {
   OnLeavingFolder(gMsgFolderSelected);  // mark all read in current folder
   accountManager.removeIncomingServerListener(gThreePaneIncomingServerListener);
   gPrefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
   gPrefBranch.removeObserver("mail.pane_config.dynamic", MailPrefObserver);
-  gPrefBranch.removeObserver("mail.showFolderPaneColumns", MailPrefObserver);
   document.getElementById('tabmail').closeTabs();
 
   gPhishingDetector.shutdown();
 
   // FIX ME - later we will be able to use onload from the overlay
   OnUnloadMsgHeaderPane();
 
   UnloadPanes();
 
   OnMailWindowUnload();
 }
 
 function loadStartFolder(initialUri)
 {
-    var folderTree = GetFolderTree();
     var defaultServer = null;
     var startFolder;
     var isLoginAtStartUpEnabled = false;
 
     //First get default account
     try
     {
         if(initialUri)
@@ -921,17 +854,17 @@ function loadStartFolder(initialUri)
         // Perform biff on the server to check for new mail, except for imap
         // or a pop3 account that is deferred or deferred to,
         // or the case where initialUri is non-null (non-startup)
         if (!initialUri && isLoginAtStartUpEnabled && gLoadStartFolder
             && !defaultServer.isDeferredTo &&
             defaultServer.rootFolder == defaultServer.rootMsgFolder)
           defaultServer.performBiff(msgWindow);
 
-        SelectFolder(startFolder.URI);
+        gFolderTreeView.selectFolder(startFolder);
     }
     catch(ex)
     {
       // this is the case where we're trying to auto-subscribe to a folder.
       if (initialUri && !startFolder.parent)
       {
         // hack to force display of thread pane.
         ShowingThreadPane();
@@ -963,175 +896,33 @@ function AddToSession()
                               .getService(Components.interfaces.nsIMsgMailSession);
   var nsIFolderListener = Components.interfaces.nsIFolderListener;
   var notifyFlags = nsIFolderListener.intPropertyChanged | nsIFolderListener.event;
   mailSession.AddFolderListener(folderListener, notifyFlags);
 }
 
 function InitPanes()
 {
-  OnLoadFolderPane();
+  gFolderTreeView.load(document.getElementById("folderTree"),
+                       "folderTree.json");
+  var folderTree = document.getElementById("folderTree");
+  folderTree.addEventListener("click",FolderPaneOnClick,true);
+  folderTree.addEventListener("mousedown",TreeOnMouseDown,true);
+                       
   OnLoadThreadPane();
   SetupCommandUpdateHandlers();
 }
 
 function UnloadPanes()
 {
-  OnUnloadFolderPane();
-  UnloadCommandUpdateHandlers();
-}
-
-function InitializeDataSources()
-{
-  //Setup common mailwindow stuff.
-  AddDataSources();
-}
-
-function OnFolderUnreadColAttrModified(event)
-{
-  if (event.attrName == "hidden")
-  {
-    var folderNameCell = document.getElementById("folderNameCell");
-    var label = {"true": "?folderTreeName", "false": "?folderTreeSimpleName"};
-    folderNameCell.setAttribute("label", label[event.newValue]);
-  }
-}
-
-function UpdateFolderColumnVisibility()
-{
-  var folderNameCol = document.getElementById("folderNameCol");
-  var showColumns = gPrefBranch.getBoolPref("mail.showFolderPaneColumns");
-  var folderUnreadCol = document.getElementById("folderUnreadCol");
-  var folderColumnLabel = document.getElementById("folderColumnLabel");
-  if (!showColumns)
-  {
-    var folderTotalCol = document.getElementById("folderTotalCol");
-    var folderSizeCol = document.getElementById("folderSizeCol");
-    folderUnreadCol.setAttribute("hidden", "true");
-    folderTotalCol.setAttribute("hidden", "true");
-    folderSizeCol.setAttribute("hidden", "true");
-    folderNameCol.removeAttribute("label");
-  }
-  else
-  {
-    folderNameCol.setAttribute("label", folderColumnLabel.value);
-  }
-
-  folderNameCol.setAttribute("hideheader", showColumns ? "false" : "true");
   var folderTree = document.getElementById("folderTree");
-  folderTree.setAttribute("hidecolumnpicker", showColumns ? "false" : "true");
-  var hidden = folderUnreadCol.getAttribute("hidden");
-  if (hidden != "true")
-  {
-    var folderNameCell = document.getElementById("folderNameCell");
-    folderNameCell.setAttribute("label", "?folderTreeSimpleName");
-  }
-}
-
-// loadFolderViewForTree -- a helper routine split away from
-// loadFolderView.
-// returns a localized string corresponding to the name of the new view
-function loadFolderViewForTree(aNewFolderView, aFolderTree)
-{
-  var folderPaneHeader = document.getElementById('folderpane-title');
-  var database = aFolderTree.database;
-  var nsIRDFDataSource = Components.interfaces.nsIRDFDataSource;
-
-  // Each folder pane view has the following properties:
-  // ref-> the ref attribute for the folderTree
-  // label -> the UI label associated with the folder view
-  // datasources -> array of the data sources associated with the view
-
-  var folderViews = [
-                      {ref:"msgaccounts:/",           label:"folderPaneHeader",           dataSources: [accountManagerDataSource.QueryInterface(nsIRDFDataSource),
-                                                                                                        folderDataSource.QueryInterface(nsIRDFDataSource)] },
-                      {ref:"mailnewsunreadfolders:/", label:"folderPaneHeader_unread",    dataSources: [unreadFolderDataSource.QueryInterface(nsIRDFDataSource)]},
-                      {ref:"mailnewsfavefolders:/",   label:"folderPaneHeader_favorites", dataSources: [favoriteFoldersDataSource.QueryInterface(nsIRDFDataSource)]},
-                      {ref:"mailnewsrecentfolders:/", label:"folderPaneHeader_recent",    dataSources: [recentFoldersDataSource.QueryInterface(nsIRDFDataSource)]},
-                    ];
-
-  // unload the current data sources
-  if (gCurrentFolderView != undefined)
-  {
-    var dataSourcesToUnload = folderViews[gCurrentFolderView].dataSources;
-    for (index in dataSourcesToUnload)
-      database.RemoveDataSource(dataSourcesToUnload[index]);
-  }
-
-  // add the new data sources
-  var dataSourcesToAdd = folderViews[aNewFolderView].dataSources;
-  for (index in dataSourcesToAdd)
-  {
-    database.AddDataSource(dataSourcesToAdd[index]);
-  }
-
-  aFolderTree.setAttribute('ref', folderViews[aNewFolderView].ref);
-  return gMessengerBundle.getString(folderViews[aNewFolderView].label);
-}
-
-function loadFolderView(aNewFolderView)
-{
-  if (gCurrentFolderView && (gCurrentFolderView == aNewFolderView))
-    return;
-
-  var folderTree = GetFolderTree();
-
-  var folderPaneHeader = document.getElementById('folderpane-title');
-  var folderTree = GetFolderTree();
-  var database = GetFolderDatasource();
-
-  // load the folder view into the folder pane
-  folderPaneHeader.value = loadFolderViewForTree(aNewFolderView, GetFolderTree());
-
-  // if the folder location picker is visible, load the folder view into the location
-  // picker as well.
-  var folderLocationPicker = document.getElementById('folder-location-container');
-  if (folderLocationPicker)
-    loadFolderViewForTree(aNewFolderView, document.getElementById('folderLocationPopup').tree);
-
-  // now reflect the new value back into prefs
-  gPrefBranch.setIntPref('mail.ui.folderpane.view', gCurrentFolderView = aNewFolderView);
-}
-
-// we can cycle the folder view forward or backwards
-function CycleFolderView(aCycleForward)
-{
-  // pass the call onto loadFolderView...
-  var offset = aCycleForward ? 1 : kNumFolderViews - 1;
-  loadFolderView((gCurrentFolderView + offset) % kNumFolderViews);
-}
-
-function OnLoadFolderPane()
-{
-  // want to start up showing the folder pane, in case we shut down
-  // with the 3-pane showing a single message.
-  document.getElementById("folderpane_splitter").collapsed = false;
-  document.getElementById("folderPaneBox").collapsed = false;
-  UpdateFolderColumnVisibility();
-  var folderUnreadCol = document.getElementById("folderUnreadCol");
-  folderUnreadCol.addEventListener("DOMAttrModified", OnFolderUnreadColAttrModified, false);
-
-  loadFolderView(gPrefBranch.getIntPref('mail.ui.folderpane.view'));
-
-  var folderTree = GetFolderTree();
-  var folderTreeBuilder = folderTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
-  folderTreeBuilder.addObserver(folderObserver);
-  folderTree.addEventListener("click",FolderPaneOnClick,true);
-  folderTree.addEventListener("mousedown",TreeOnMouseDown,true);
-}
-
-function OnUnloadFolderPane()
-{
-  var folderTree = GetFolderTree();
-  var folderTreeBuilder = folderTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
-  folderTreeBuilder.removeObserver(folderObserver);
-  var folderUnreadCol = document.getElementById("folderUnreadCol");
-  folderUnreadCol.removeEventListener("DOMAttrModified", OnFolderUnreadColAttrModified, false);
   folderTree.removeEventListener("click",FolderPaneOnClick,true);
   folderTree.removeEventListener("mousedown",TreeOnMouseDown,true);
+  gFolderTreeView.unload("folderTree.json");
+  UnloadCommandUpdateHandlers();
 }
 
 // builds prior to 12-08-2001 did not have the labels column
 // in the thread pane.  so if a user ran an old build, and then
 // upgraded, they get the new column, and this causes problems.
 // We're trying to avoid a similar problem to bug #96979.
 // to work around this, we hide the column once, using the
 // "mailnews.ui.threadpane.version" pref.
@@ -1193,81 +984,17 @@ function UpgradeThreadPaneUI()
   }
 }
 
 function OnLoadThreadPane()
 {
   UpgradeThreadPaneUI();
 }
 
-// folderLocationPickerOnLoad can be called multiple times
-// and it can be called when the location picker isn't in the toolbar
-function folderLocationPickerOnLoad()
-{
-  var folderLocationPicker = document.getElementById('folder-location-container');
-  if (!folderLocationPicker)
-    return;
-
-  var locationTree = document.getElementById('folderLocationPopup').tree;
-  locationTree.database.AddDataSource(accountManagerDataSource);
-  locationTree.database.AddDataSource(folderDataSource);
-  locationTree.setAttribute("ref", "msgaccounts:/");
-}
-
-function OnLocationTreeSelect(menulist)
-{
-  SelectFolder(menulist.getAttribute('uri'));
-}
-
-function UpdateFolderLocationPicker(resource)
-{
-  var folderLocationPicker = document.getElementById('folder-location-container');
-  if (!folderLocationPicker)
-    return;
-
-  var tree = GetFolderTree();
-  var folders = document.getElementById('locationFolders');
-  var properties = ['BiffState', 'NewMessages', 'HasUnreadMessages',
-                    'SpecialFolder', 'IsServer', 'IsSecure', 'ServerType', 'NoSelect'];
-  var folder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
-  var label = folder.prettyName;
-  folders.setAttribute("label", label);
-
-  var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"]
-                             .getService(Components.interfaces.nsIRDFService);
-  function GetFolderAttribute(tree, source, attribute) {
-    var property = rdfService.GetResource("http://home.netscape.com/NC-rdf#" + attribute);
-    var target = tree.database.GetTarget(source, property, true);
-    if (target)
-      target = target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
-    return target;
-  }
-  for (var i in properties)
-  {
-    var property = properties[i];
-    var value = GetFolderAttribute(tree, resource, property);
-    folders.setAttribute(property, value);
-  }
-  folders.setAttribute('uri', resource.Value);
-}
-
-function GetFolderDatasource()
-{
-  var folderTree = GetFolderTree();
-  return folderTree.database;
-}
-
 /* Functions for accessing particular parts of the window*/
-function GetFolderTree()
-{
-  if (!gFolderTree)
-    gFolderTree = document.getElementById("folderTree");
-  return gFolderTree;
-}
-
 function GetSearchInput()
 {
   if (!gSearchInput)
     gSearchInput = document.getElementById("searchInput");
   return gSearchInput;
 }
 
 function GetMessagePane()
@@ -1324,21 +1051,16 @@ function GetTotalCountElement()
   return gTotalCount;
 }
 
 function IsMessagePaneCollapsed()
 {
   return GetMessagePane().collapsed;
 }
 
-function IsFolderPaneCollapsed()
-{
-  return GetFolderTree().parentNode.collapsed;
-}
-
 function ClearThreadPaneSelection()
 {
   try {
     if (gDBView) {
       var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
       var treeSelection = treeView.selection;
       if (treeSelection)
         treeSelection.clearSelection();
@@ -1406,89 +1128,37 @@ function TreeOnMouseDown(event)
 }
 
 function FolderPaneOnClick(event)
 {
     // we only care about button 0 (left click) events
     if (event.button != 0)
         return;
 
-    var folderTree = GetFolderTree();
+    var folderTree = document.getElementById("folderTree");
     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")) {
       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 (!gPrefBranch.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 */);
-
-      // 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);
-    }
-}
-
-//xxx this function should go away
-function GetSelectedFolders()
-{
-    return GetSelectedMsgFolders();
 }
 
 function GetSelectedMsgFolders()
 {
-    var folderArray = [];
-    var folderTree = GetFolderTree();
-    var rangeCount = folderTree.view.selection.getRangeCount();
-
-    for (var i = 0; i < rangeCount; i++)
-    {
-        var startIndex = {};
-        var endIndex = {};
-        folderTree.view.selection.getRangeAt(i, startIndex, endIndex);
-        for (var j = startIndex.value; j <= endIndex.value; j++)
-        {
-          var folderResource = GetFolderResource(folderTree, j);
-          var msgFolder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
-          folderArray.push(msgFolder);
-        }
-    }
-
-    return folderArray;
+  return gFolderTreeView.getSelectedFolders();
 }
 
 function GetFirstSelectedMessage()
 {
     try {
         return gDBView.URIForFirstSelectedMessage;
     }
     catch (ex) {
@@ -1581,40 +1251,16 @@ function SetNextMessageAfterDelete()
     // was moved or deleted from the folder.
     gThreadPaneDeleteOrMoveOccurred = true;
     gNextMessageViewIndexAfterDelete = treeSelection.currentIndex - NumberOfSelectedMessagesAboveCurrentIndex(treeSelection.currentIndex);
   }
   else
     gNextMessageViewIndexAfterDelete = treeSelection.currentIndex;
 }
 
-function EnsureFolderIndex(builder, msgFolder)
-{
-  // try to get the index of the folder in the tree
-  var index = builder.getIndexOfResource(msgFolder);
-  if (index == -1) {
-    // if we couldn't find the folder, open the parent
-    builder.toggleOpenState(EnsureFolderIndex(builder, msgFolder.parent));
-    index = builder.getIndexOfResource(msgFolder);
-  }
-  return index;
-}
-
-function SelectFolder(folderUri)
-{
-  var folderTree = GetFolderTree();
-  var msgFolder = GetMsgFolderFromUri(folderUri);
-
-  // Before we can select a folder, we need to make sure it is "visible"
-  // in the tree. To do that, we need to ensure that all its
-  // ancestors are expanded.
-  var folderIndex = EnsureFolderIndex(folderTree.builderView, msgFolder);
-  ChangeSelection(folderTree, folderIndex);
-}
-
 function SelectMessage(messageUri)
 {
   var msgHdr = messenger.messageServiceFromURI(messageUri).messageURIToMsgHdr(messageUri);
   if (msgHdr)
     gDBView.selectMsgByKey(msgHdr.messageKey);
 }
 
 function ReloadMessage()
@@ -1622,21 +1268,16 @@ function ReloadMessage()
   gDBView.reloadMessage();
 }
 
 function GetDBView()
 {
   return gDBView;
 }
 
-function GetFolderResource(tree, index)
-{
-  return tree.builderView.getResourceAtIndex(index);
-}
-
 function LoadNavigatedToMessage(msgHdr, folder, folderUri)
 {
   if (IsCurrentLoadedFolder(folder))
   {
     gDBView.selectMsgByKey(msgHdr.messageKey);
   }
   else
   {
@@ -1716,34 +1357,22 @@ function MigrateAttachmentDownloadStore(
     if (downloadsFile && downloadsFile.exists())
       downloadsFile.remove(false);
 
     // bump the version so we don't bother doing this again.
     gPrefBranch.setIntPref("mail.attachment.store.version", 1);
   }
 }
 
-/**
- * Returns a string representation of a folder's specialFolder attribute.
- *
- * @param aFolder  the folder whose specialFolder attribute to return
- */
-function getSpecialFolderString(aFolder) {
-  if (aFolder.flags & 0x1000) // MSG_FOLDER_FLAG_INBOX
-    return "Inbox";
-  else if (aFolder.flags & 0x0100) // MSG_FOLDER_FLAG_TRASH
-    return "Trash";
-  else if (aFolder.flags & 0x0800) // MSG_FOLDER_FLAG_QUEUE
-    return "Unsent Messages";
-  else if (aFolder.flags & 0x0200) // MSG_FOLDER_FLAG_SENTMAIL
-    return "Sent";
-  else if (aFolder.flags & 0x0400) // MSG_FOLDER_FLAG_DRAFTS
-    return "Drafts";
-  else if (aFolder.flags & 0x400000) // MSG_FOLDER_FLAG_TEMPLATES
-    return "Templates";
-  else if (aFolder.flags & 0x40000000) // MSG_FOLDER_FLAG_JUNK
-    return "Junk";
-  else if (aFolder.flags & 0x0020) // MSG_FOLDER_FLAG_VIRTUAL
-    return "Virtual";
-  else
-    return "none";
+function threadPaneOnDragStart(aEvent) {
+  if (aEvent.originalTarget.localName != "treechildren")
+    return;
+
+  var messages = GetSelectedMessages();
+  if (!messages)
+    return;
+
+  SetNextMessageAfterDelete()
+  for (let i in messages)
+    aEvent.dataTransfer.mozSetDataAt("text/x-moz-message", messages[i], i);
+  aEvent.dataTransfer.effectAllowed = "copyMove";
+  aEvent.dataTransfer.addElement(aEvent.originalTarget);
 }
-
--- a/mail/base/content/widgetglue.js
+++ b/mail/base/content/widgetglue.js
@@ -50,152 +50,16 @@
 //NOTE: gMessengerBundle must be defined and set or this Overlay won't work
 
 function GetSelectedFolderURI()
 {
   var folders = GetSelectedMsgFolders();
   return folders.length == 1 ? folders[0].URI : null;
 }
 
-function MsgRenameFolder()
-{
-  var folder = GetSelectedMsgFolders()[0];
-
-  var dialog = window.openDialog(
-               "chrome://messenger/content/renameFolderDialog.xul",
-               "newFolder",
-               "chrome,titlebar,modal",
-               {preselectedURI: folder.URI,
-                okCallback: RenameFolder, name: folder.prettyName});
-}
-
-function RenameFolder(name,uri)
-{
-  var folderTree = GetFolderTree();
-  if (folderTree)
-  {
-    if (uri && (uri != "") && name && (name != ""))
-    {
-      var selectedFolder = GetMsgFolderFromUri(uri);
-      if (gDBView)
-        gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
-
-      ClearThreadPane();
-      ClearMessagePane();
-      folderTree.view.selection.clearSelection();
-
-      try
-      {
-        selectedFolder.rename(name, msgWindow);
-      }
-      catch(e)
-      {
-        SelectFolder(selectedFolder.URI);  //restore selection
-        throw(e); // so that the dialog does not automatically close
-        dump ("Exception : RenameFolder \n");
-      }
-    }
-    else
-    {
-      dump("no name or nothing selected\n");
-    }
-  }
-  else
-  {
-    dump("no folder tree\n");
-  }
-}
-
-function MsgEmptyTrash()
-{
-  var folders = GetSelectedMsgFolders();
-  if (folders.length == 1)
-  {
-    if (!confirmToProceed('emptyTrash'))
-      return;
-
-    folders[0].emptyTrash(msgWindow, null);
-  }
-}
-
-function MsgCompactFolder(isAll)
-{
-  // Get the selected folders.
-  var selectedFolders = GetSelectedMsgFolders();
-
-  if (selectedFolders.length == 1)
-  {
-    var msgfolder = selectedFolders[0];
-
-    if (msgfolder.server.type != "imap") //can be local only
-    {
-      var expungedBytes = msgfolder.expungedBytes;
-
-      if (expungedBytes > 0)
-      {
-        if (gDBView)
-        {
-          gCurrentlyDisplayedMessage = gDBView.currentlyDisplayedMessage;
-          if (gDBView.msgFolder == msgfolder || isAll)
-          {
-            ClearThreadPaneSelection();
-            ClearThreadPane();
-            ClearMessagePane();
-          }
-        }
-      }
-      else
-      {
-        if (!isAll)  //you have one local folder with no room to compact
-          return;
-      }
-    }
-    if (isAll)
-      msgfolder.compactAll(null, msgWindow, null, true, null);
-    else
-      msgfolder.compact(null, msgWindow);
-  }
-}
-
-function openNewVirtualFolderDialogWithArgs(defaultViewName, aSearchTerms)
-{
-  var folder = GetFirstSelectedMsgFolder();
-  var folderTree = GetFolderTree();
-  var name = folder.prettyName
-  name += "-" + defaultViewName;
-
-  var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
-                                 "chrome,titlebar,modal,centerscreen",
-                                 {folder:folder,
-                                  searchTerms:aSearchTerms,
-                                  newFolderName:name});
-}
-
-function MsgVirtualFolderProperties(aEditExistingVFolder)
-{
-  var preselectedFolder = GetFirstSelectedMsgFolder();
-
-  var dialog = window.openDialog("chrome://messenger/content/virtualFolderProperties.xul", "",
-                                 "chrome,titlebar,modal,centerscreen",
-                                 {folder:preselectedFolder,
-                                  editExistingFolder: aEditExistingVFolder,
-                                  onOKCallback:onEditVirtualFolderPropertiesCallback,
-                                  msgWindow:msgWindow});
-}
-
-function onEditVirtualFolderPropertiesCallback(aVirtualFolderURI)
-{
-  // we need to reload the folder if it is the currently loaded folder...
-  if (gMsgFolderSelected && aVirtualFolderURI == gMsgFolderSelected.URI)
-  {
-    gMsgFolderSelected = null; // force the folder pane to reload the virtual folder
-    FolderPaneSelectionChange();
-  }
-}
-
 /**
  @param tabID  initial tab
  */
 function MsgFolderProperties(tabID)
 {
   var preselectedURI = GetSelectedFolderURI();
   var msgFolder = GetMsgFolderFromUri(preselectedURI, true);
 
--- a/mail/base/jar.mn
+++ b/mail/base/jar.mn
@@ -30,16 +30,17 @@ messenger.jar:
 *   content/messenger/FilterListDialog.xul          (content/FilterListDialog.xul)
 *   content/messenger/FilterListDialog.js           (content/FilterListDialog.js)
 *   content/messenger/subscribe.xul                 (content/subscribe.xul)
     content/messenger/subscribe.js                  (content/subscribe.js)
 *   content/messenger/aboutDialog.xul               (content/aboutDialog.xul)
 *   content/messenger/aboutDialog.js                (content/aboutDialog.js)
 *   content/messenger/defaultClientDialog.xul       (content/defaultClientDialog.xul)
 *   content/messenger/defaultClientDialog.js        (content/defaultClientDialog.js)
+    content/messenger/folderPane.js                 (content/folderPane.js)
 *   content/messenger/msgSelectOffline.xul          (content/msgSelectOffline.xul)
 *   content/messenger/msgPrintEngine.xul            (content/msgPrintEngine.xul)
 *   content/messenger/searchBar.js                  (content/searchBar.js)
 *   content/messenger/phishingDetector.js           (content/phishingDetector.js)
 *   content/messenger/mail-offline.js               (content/mail-offline.js)
     content/messenger/about-footer.png              (content/about-footer.png)
     content/messenger/aboutDialog.css               (content/aboutDialog.css)
 *   content/messenger/credits.xhtml                 (content/credits.xhtml)
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -410,19 +410,19 @@ updatesItem_defaultFallback=Check for Updates…
 updatesItem_downloading=Downloading %S…
 updatesItem_downloadingFallback=Downloading Update…
 updatesItem_resume=Resume Downloading %S…
 updatesItem_resumeFallback=Resume Downloading Update…
 updatesItem_pending=Apply Downloaded Update Now…
 updatesItem_pendingFallback=Apply Downloaded Update Now…
 
 # Folder Pane Header Title Strings
-folderPaneHeader=All Folders
+folderPaneHeader_all=All Folders
 folderPaneHeader_unread=Unread Folders
-folderPaneHeader_favorites=Favorite Folders
+folderPaneHeader_favorite=Favorite Folders
 folderPaneHeader_recent=Recent Folders
 
 # Copy / Move to Folder Again
 #LOCALIZATION NOTE %1$S is the name of the folder we will move to. moveToFolderAgainAccessKey
 # should have the same value as copyToFolderAgainAccessKey as they are the same menu item in the UI
 # moveToFolderAgainAccessKey should also be a letter that occurs before %1$S
 moveToFolderAgain=Move to "%1$S" Again
 moveToFolderAgainAccessKey=t
--- a/mailnews/base/resources/content/mailWidgets.xml
+++ b/mailnews/base/resources/content/mailWidgets.xml
@@ -1853,22 +1853,31 @@
     <content>
       <children>
         <xul:folderSummary/>
       </children>
     </content>
     <handlers>
       <handler event="popupshowing">
         <![CDATA[
-          var folderTree = GetFolderTree();
-          var row = folderTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
-          if (row == -1)
-            return false;
-
-          var msgFolder = GetFolderResource(folderTree, row).QueryInterface(Components.interfaces.nsIMsgFolder);
+          var msgFolder;
+          // Allow for differences between Thunderbird and SeaMonkey folder pane
+          if ("gFolderTreeView" in window) {
+            msgFolder = gFolderTreeView.getFolderAtCoords(event.clientX,
+                                                          event.clientY);
+            if (!msgFolder)
+              return false;
+          } else {
+            var folderTree = GetFolderTree();
+            var row = folderTree.treeBoxObject.getRowAt(event.clientX,
+                                                        event.clientY);
+            if (row == -1)
+              return false;
+            msgFolder = GetFolderResource(folderTree, row).QueryInterface(Components.interfaces.nsIMsgFolder);
+          }
           if (!msgFolder || msgFolder.isServer)
             return false;
           var asyncResults = {};
           return document.getAnonymousNodes(this)[0].parseFolder(msgFolder, null, asyncResults);
         ]]>
       </handler>
 
       <handler event="popuphiding">
--- a/mailnews/base/resources/content/msgAccountCentral.js
+++ b/mailnews/base/resources/content/msgAccountCentral.js
@@ -216,19 +216,17 @@ function CollapseSectionSeparators(separ
         var separator = document.getElementById(separatorId);   
         separator.setAttribute("collapsed", true);
     }
 }
 
 // From the current folder tree, return the selected server
 function GetSelectedServer()
 {
-    var folderURI = window.parent.GetSelectedFolderURI();
-    var server = GetServer(folderURI);
-    return server;
+  return GetSelectedMsgFolder().server;
 }
 
 // From the current folder tree, return the selected folder
 function GetSelectedMsgFolder()
 {
     var folderURI = window.parent.GetSelectedFolderURI();
     var msgFolder = window.parent.GetMsgFolderFromUri(folderURI, true);
     return msgFolder;
--- a/mailnews/base/util/Makefile.in
+++ b/mailnews/base/util/Makefile.in
@@ -110,16 +110,20 @@ EXPORTS		= \
 		nsMsgUtils.h \
 		nsMsgProtocol.h \
 		nsMsgMailNewsUrl.h \
 		nsMsgTxn.h \
 		nsMsgI18N.h \
 		nsImapMoveCoalescer.h \
 		$(NULL)
 
+EXTRA_JS_MODULES = \
+		folderUtils.jsm \
+		iteratorUtils.jsm
+					
 ifndef MOZ_STATIC_MAIL_BUILD
 
 EXTRA_DSO_LDOPTS = \
 	$(LIBS_DIR) \
 	$(MOZDEPTH)/rdf/util/src/internal/$(LIB_PREFIX)rdfutil_s.$(LIB_SUFFIX) \
 	$(MOZ_UNICHARUTIL_LIBS) \
 	$(MOZ_COMPONENT_LIBS) \
 	$(NULL)
@@ -129,12 +133,10 @@ else
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 endif
 
 
 DEFINES		+= -D_IMPL_NS_MSG_BASE
 
-EXTRA_JS_MODULES = iteratorUtils.jsm
-
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/mailnews/base/util/folderUtils.jsm
@@ -0,0 +1,149 @@
+/* ***** 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 mail folder code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Joey Minta <jminta@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * This file contains helper methods for dealing with nsIMsgFolders.
+ */
+
+var EXPORTED_SYMBOLS = ["setPropertyAtoms", "getSpecialFolderString",
+                        "getFolderFromUri"];
+
+/**
+ * Returns a string representation of a folder's "special" type
+ *
+ * @param aFolder  the folder whose special type should be returned
+ */
+function getSpecialFolderString(aFolder) {
+  const Ci = Components.interfaces;
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Inbox)
+    return "Inbox";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Trash)
+    return "Trash";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Queue)
+    return "Unsent Messages";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.SentMail)
+    return "Sent";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Drafts)
+    return "Drafts";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Templates)
+    return "Templates";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Junk)
+    return "Junk";
+  if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual)
+    return "Virtual";
+  return "none";
+}
+
+/**
+ * This function is meant to be used with trees. It adds atoms for all of the
+ * common properties that css styling is based off of.
+ *
+ * @param aFolder     the folder whose properties should be added as atoms
+ * @param aProperties the nsIProperties object where the atoms should be added
+ */
+function setPropertyAtoms(aFolder, aProperties) {
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+  let atomSvc = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
+  function addAtom(aName) {
+    aProperties.AppendElement(atomSvc.getAtom(aName));
+  }
+
+  addAtom("folderNameCol");
+  if (aFolder.getNumUnread(false) > 0)
+    addAtom("hasUnreadMessages-true");
+
+  if (aFolder.isServer)
+    addAtom("isServer-true");
+
+  addAtom("serverType-" + aFolder.server.type);
+
+  // set the SpecialFolder attribute
+  addAtom("specialFolder-" + getSpecialFolderString(aFolder));
+
+  // Now set the biffState
+  switch (aFolder.biffState) {
+    case Ci.nsIMsgFolder.nsMsgBiffState_NewMail:
+      addAtom("biffState-NewMail");
+      break;
+    case Ci.nsIMsgFolder.nsMsgBiffState_NoMail:
+      addAtom("biffState-NoMail");
+      break;
+    default:
+      addAtom("biffState-UnknownMail");
+  }
+
+  // We have to work a bit for IsSecure.  This sucks
+  let server = aFolder.server;
+  if (server instanceof Ci.nsINntpIncomingServer)
+    addAtom("isSecure-" + server.isSecure);
+  else {
+    // If it's not a news-server, apparently we look at the socket type
+    let sock = server.socketType;
+    let isSecure = (sock == Ci.nsIMsgIncomingServer.alwaysUseTLS ||
+                    sock == Ci.nsIMsgIncomingServer.useSSL);
+    addAtom("isSecure-" + isSecure);
+  }
+
+  if (aFolder.hasNewMessages)
+    addAtom("newMessages-true");
+
+  // We only set this if we're not a server
+  if (!aFolder.isServer) {
+    let shallowUnread = aFolder.getNumUnread(false);
+    // Make sure that shallowUnread isn't negative
+    if (shallowUnread < 0)
+      shallowUnread = 0;
+    let deepUnread = aFolder.getNumUnread(true);
+    if (deepUnread - shallowUnread > 0)
+      addAtom("subfoldersHaveUnreadMessages-true");
+  }
+
+  addAtom("noSelect-" + aFolder.noSelect);
+  addAtom("imapShared-" + aFolder.imapShared);
+}
+
+/**
+ * Returns a folder for a particular uri
+ *
+ * @param aUri  the rdf uri of the folder to return
+ */
+function getFolderFromUri(aUri) {
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+  return Cc["@mozilla.org/mail/folder-lookup;1"].
+         getService(Ci.nsIFolderLookupService).getFolderById(aUri);
+}
--- a/mailnews/base/util/iteratorUtils.jsm
+++ b/mailnews/base/util/iteratorUtils.jsm
@@ -34,17 +34,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * This file contains helper methods for dealing with XPCOM iterators (arrays
  * and enumerators) in js-friendly ways.
  */
 
-var EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMIterator"];
+var EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMArray"];
 
 let Ci = Components.interfaces;
 
 /**
  * This function will take a variety of xpcom iterators designed for c++ and
  * turns them into a nice JavaScript style object that can be iterated using
  * for...in
  *
@@ -95,27 +95,27 @@ function fixIterator(aEnum, aIface) {
       while (aEnum.hasMoreElements())
         yield aEnum.getNext().QueryInterface(face);
     }
     return { __iterator__: iter };
   }
 }
 
 /**
- * This function takes a javascript Array object and returns an xpcom iterator
+ * This function takes a javascript Array object and returns an xpcom array
  * of the desired type. It will *not* work if you extend Array.prototype.
  *
  * @param aArray      the array to convert to an xpcom array
  * @param aInterface  the type of xpcom array to convert
  *
  * @note The returned array is *not* dynamically updated.  Changes made to the
  *       js array after a call to this function will not be reflected in the
  *       xpcom array.
  */
-function toXPCOMIterator(aArray, aInterface) {
+function toXPCOMArray(aArray, aInterface) {
   if (aInterface.equals(Ci.nsISupportsArray)) {
     let supportsArray = Components.classes["@mozilla.org/supports-array;1"]
                                   .createInstance(Ci.nsISupportsArray);
     for each (let item in aArray) {
       supportsArray.AppendElement(item);
     }
     return supportsArray;
   }