bug 382187 - use places for SeaMonkey history, enable it and switch the UI, r+sr=Neil
authorRobert Kaiser <kairo@kairo.at>
Sun, 07 Dec 2008 14:20:44 +0100
changeset 1359 28012b9430e51444d7f6db064b06fab1825f7733
parent 1358 ed0cf62bc9fb20cade8f49387cbc5a289da2db1b
child 1360 8dbdd7537b670f4d304c5a89ae9bac968252196e
push idunknown
push userunknown
push dateunknown
bugs382187
bug 382187 - use places for SeaMonkey history, enable it and switch the UI, r+sr=Neil
suite/browser/browser-prefs.js
suite/common/history/controller.js
suite/common/history/findHistory.js
suite/common/history/findHistory.xul
suite/common/history/history-panel.xul
suite/common/history/history-test.js
suite/common/history/history-test.xul
suite/common/history/history.js
suite/common/history/history.xul
suite/common/history/historyTreeOverlay.xul
suite/common/history/historyTreeSorting.js
suite/common/history/places.css
suite/common/history/places.js
suite/common/history/placesOverlay.xul
suite/common/history/sidebarUtils.js
suite/common/history/tree.xml
suite/common/history/treeView.js
suite/common/history/utils.js
suite/common/jar.mn
suite/confvars.sh
suite/installer/unix/packages
suite/installer/windows/packages
suite/locales/en-US/chrome/common/history/findHistory.dtd
suite/locales/en-US/chrome/common/history/history.dtd
suite/locales/en-US/chrome/common/history/history.properties
suite/locales/en-US/chrome/common/history/historyTreeOverlay.dtd
suite/locales/en-US/chrome/common/history/places.properties
suite/locales/jar.mn
suite/themes/classic/communicator/history/calendar.png
suite/themes/classic/communicator/history/places.css
suite/themes/classic/communicator/sidebar/sidebarListView.css
suite/themes/classic/jar.mn
suite/themes/modern/communicator/history/calendar.png
suite/themes/modern/communicator/history/places.css
suite/themes/modern/communicator/sidebar/sidebarListView.css
suite/themes/modern/jar.mn
--- a/suite/browser/browser-prefs.js
+++ b/suite/browser/browser-prefs.js
@@ -65,21 +65,16 @@ pref("general.autoScroll", true);
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last
 pref("browser.startup.page",                1);
 pref("browser.startup.homepage",	   "chrome://navigator-region/locale/region.properties");
 pref("browser.startup.homepage.count", 1);
 
 // disable this until it can be disabled on a per-docshell basis (see bug 319368)
 pref("browser.send_pings", false);
 
-pref("browser.urlbar.autoFill", false);
-pref("browser.urlbar.showPopup", true);
-pref("browser.urlbar.showSearch", true);
-pref("browser.urlbar.matchOnlyTyped", false);
-
 pref("browser.chrome.site_icons", true);
 pref("browser.chrome.favicons", false);
 pref("browser.chrome.image_icons.max_size", 1024);
 
 // 0 = never, 1 = when in cache, 2 = always
 pref("browser.chrome.load_toolbar_icons", 0);
 
 pref("browser.toolbars.showbutton.bookmarks", true);
@@ -106,34 +101,113 @@ pref("browser.search.mode", 0);
 // basic search popup constraint: minimum sherlock plugin version displayed
 // (note: must be a string representation of a float or it'll default to 0.0)
 pref("browser.search.basic.min_ver", "0.0");
 pref("browser.urlbar.autocomplete.enabled", true);
 pref("browser.urlbar.clickSelectsAll", true);
 // when clickSelectsAll=true, does it also apply when the click is past end of text?
 pref("browser.urlbar.clickAtEndSelects", true);
 
+pref("browser.urlbar.autoFill", false);
+pref("browser.urlbar.showPopup", true);
+pref("browser.urlbar.showSearch", true);
+pref("browser.urlbar.matchOnlyTyped", false);
+// 0: Match anywhere (e.g., middle of words)
+// 1: Match on word boundaries and then try matching anywhere
+// 2: Match only on word boundaries (e.g., after / or .)
+// 3: Match at the beginning of the url or title
+pref("browser.urlbar.matchBehavior", 1);
+// 0: Search nothing
+// 1: Search history (visited pages)
+// 2: Search bookmarks
+// 3: Search both history and bookmarks
+pref("browser.urlbar.search.sources", 1);
+pref("browser.urlbar.filter.javascript", true);
+
+// Size of "chunks" affects the number of places to process between each search
+// timeout (ms). Too big and the UI will be unresponsive; too small and we'll
+// be waiting on the timeout too often without many results.
+pref("browser.urlbar.search.chunkSize", 1000);
+pref("browser.urlbar.search.timeout", 100);
+
+// The special characters below can be typed into the urlbar to either restrict
+// the search to visited history, bookmarked, tagged pages; or force a match on
+// just the title text or url.
+pref("browser.urlbar.restrict.history", "^");
+pref("browser.urlbar.restrict.bookmark", "*");
+pref("browser.urlbar.restrict.tag", "+");
+pref("browser.urlbar.match.title", "#");
+pref("browser.urlbar.match.url", "@");
+
 pref("browser.search.param.Google.1.custom", "chrome://navigator/content/searchconfig.properties");
 pref("browser.search.param.Google.1.default", "chrome://navigator/content/searchconfig.properties");
 
 pref("browser.history.grouping", "day");
 pref("browser.sessionhistory.max_entries", 50);
+pref("browser.history_expire_days", 180);
+pref("browser.history_expire_days_min", 90);
+pref("browser.history_expire_sites", 40000);
+
+// the (maximum) number of the recent visits to sample
+// when calculating frecency
+pref("places.frecency.numVisits", 10);
+
+// Number of records to update frecency for when idle.
+pref("places.frecency.numCalcOnIdle", 50);
+
+// Number of records to update frecency for when migrating from
+// a pre-frecency build.
+pref("places.frecency.numCalcOnMigrate", 50);
+
+// Perform frecency recalculation after this amount of idle, repeating.
+// A value of zero disables updating of frecency on idle.
+// Default is 1 minute (60000ms).
+pref("places.frecency.updateIdleTime", 60000);
+
+// buckets (in days) for frecency calculation
+pref("places.frecency.firstBucketCutoff", 4);
+pref("places.frecency.secondBucketCutoff", 14);
+pref("places.frecency.thirdBucketCutoff", 31);
+pref("places.frecency.fourthBucketCutoff", 90);
+
+// weights for buckets for frecency calculations
+pref("places.frecency.firstBucketWeight", 100);
+pref("places.frecency.secondBucketWeight", 70);
+pref("places.frecency.thirdBucketWeight", 50);
+pref("places.frecency.fourthBucketWeight", 30);
+pref("places.frecency.defaultBucketWeight", 10);
+
+// bonus (in percent) for visit transition types for frecency calculations
+pref("places.frecency.embedVisitBonus", 0);
+pref("places.frecency.linkVisitBonus", 100);
+pref("places.frecency.typedVisitBonus", 2000);
+pref("places.frecency.bookmarkVisitBonus", 150);
+pref("places.frecency.downloadVisitBonus", 0);
+pref("places.frecency.permRedirectVisitBonus", 0);
+pref("places.frecency.tempRedirectVisitBonus", 0);
+pref("places.frecency.defaultVisitBonus", 0);
+
+// bonus (in percent) for place types for frecency calculations
+pref("places.frecency.unvisitedBookmarkBonus", 140);
+pref("places.frecency.unvisitedTypedBonus", 200);
 
 // Tabbed browser
 pref("browser.tabs.loadDivertedInBackground", false);
 pref("browser.tabs.loadInBackground", false);
 pref("browser.tabs.opentabfor.middleclick", false);
 pref("browser.tabs.opentabfor.urlbar", false);
 pref("browser.tabs.tooltippreview.enable", true);
 pref("browser.tabs.tooltippreview.width", 300);
 pref("browser.tabs.undoclose.depth", 3);
 pref("browser.tabs.autoHide", true);
 pref("browser.tabs.forceHide", false);
 pref("browser.tabs.warnOnClose", true);
 pref("browser.tabs.warnOnCloseOther", true);
+pref("browser.tabs.warnOnOpen", true);
+pref("browser.tabs.maxOpenBeforeWarn", 15);
 // 0 = append, 1 = replace
 pref("browser.tabs.loadGroup", 1);
 
 // lets new tab/window load something different than first window
 // -1 - use navigator startup preference
 //  0 - loads blank page
 //  1 - loads home page
 //  2 - loads last page visited
new file mode 100755
--- /dev/null
+++ b/suite/common/history/controller.js
@@ -0,0 +1,642 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Places Command Controller.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Myk Melez <myk@mozilla.org>
+ *   Asaf Romano <mano@mozilla.com>
+ *   Marco Bonardo <mak77@bonardo.net>
+ *
+ * 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 ***** */
+
+// XXXmano: we should move most/all of these constants to PlacesUtils
+const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
+const ORGANIZER_SUBSCRIPTIONS_QUERY = "place:annotation=livemark%2FfeedURI";
+
+// when removing a bunch of pages we split them in chunks to avoid passing
+// a too big array to RemovePages
+// 300 is the best choice with an history of about 150000 visits
+// smaller chunks could cause a Slow Script warning with a huge history
+const REMOVE_PAGES_CHUNKLEN = 300;
+// if we are removing less than this pages we will remove them one by one
+// since it will be reflected faster on the UI
+// 10 is a good compromise, since allows the user to delete a little amount of
+// urls for privacy reasons, but does not cause heavy disk access
+const REMOVE_PAGES_MAX_SINGLEREMOVES = 10;
+
+/**
+ * Places Controller
+ */
+
+function PlacesController(aView) {
+  this._view = aView;
+}
+
+PlacesController.prototype = {
+  /**
+   * The places view.
+   */
+  _view: null,
+
+  supportsCommand: function PC_supportsCommand(aCommand) {
+    //LOG("supportsCommand: " + command);
+    // Non-Places specific commands that we also support
+    switch (aCommand) {
+    case "cmd_cut":
+    case "cmd_copy":
+    case "cmd_delete":
+    case "cmd_selectAll":
+      return true;
+    }
+
+    // All other Places Commands are prefixed with "placesCmd_" ... this
+    // filters out other commands that we do _not_ support (see 329587).
+    return (/^placesCmd_/.test(aCommand));
+  },
+
+  isCommandEnabled: function PC_isCommandEnabled(aCommand) {
+    switch (aCommand) {
+    case "cmd_cut":
+    case "cmd_delete":
+      return true; // history items can always be deleted
+    case "cmd_copy":
+      return this._view.hasSelection;
+    case "cmd_selectAll":
+      if (this._view.selType != "single") {
+        var result = this._view.getResult();
+        if (result) {
+          var container = asContainer(result.root);
+          if (container.childCount > 0);
+            return true;
+        }
+      }
+      return false;
+    case "placesCmd_open":
+    case "placesCmd_open:window":
+    case "placesCmd_open:tab":
+      var selectedNode = this._view.selectedNode;
+      return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
+    default:
+      return false;
+    }
+  },
+
+  doCommand: function PC_doCommand(aCommand) {
+    switch (aCommand) {
+    case "cmd_cut":
+      this.cut();
+      break;
+    case "cmd_copy":
+      this.copy();
+      break;
+    case "cmd_delete":
+      this._removeRowsFromHistory();
+      break;
+    case "cmd_selectAll":
+      this.selectAll();
+      break;
+    case "placesCmd_open":
+      PlacesUIUtils.openNodeIn(this._view.selectedNode, "current");
+      break;
+    case "placesCmd_open:window":
+      PlacesUIUtils.openNodeIn(this._view.selectedNode, "window");
+      break;
+    case "placesCmd_open:tab":
+      PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab");
+      break;
+    }
+  },
+
+  onEvent: function PC_onEvent(eventName) { },
+
+  /**
+   * Gathers information about the selected nodes according to the following
+   * rules:
+   *    "link"              node is a URI
+   *    "folder"            node is a folder
+   *    "query"             node is a query
+   *    "dynamiccontainer"  node is a dynamic container
+   *    "host"              node is a host
+   *    "day"               node is a date query
+   *
+   * @returns an array of objects corresponding the selected nodes. Each
+   *          object has each of the properties above set if its corresponding
+   *          node matches the rule. In addition, the annotations names for each
+   *          node are set on its corresponding object as properties.
+   * Notes:
+   *   1) This can be slow, so don't call it anywhere performance critical!
+   *   2) A single-object array corresponding the root node is returned if
+   *      there's no selection.
+   */
+  _buildSelectionMetadata: function PC__buildSelectionMetadata() {
+    var metadata = [];
+    var root = this._view.getResult().root;
+    var nodes = this._view.getSelectionNodes();
+    if (nodes.length == 0)
+      nodes.push(root); // See the second note above
+
+    for (var i=0; i < nodes.length; i++) {
+      var nodeData = {};
+      var node = nodes[i];
+      var nodeType = node.type;
+      var uri = null;
+
+      // We don't use the nodeIs* methods here to avoid going through the type
+      // property way too often
+      switch(nodeType) {
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
+          nodeData["query"] = true;
+          if (node.parent) {
+            switch (asQuery(node.parent).queryOptions.resultType) {
+              case Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
+                nodeData["host"] = true;
+                break;
+              case Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
+              case Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
+                nodeData["day"] = true;
+                break;
+            }
+          }
+          break;
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_DYNAMIC_CONTAINER:
+          nodeData["dynamiccontainer"] = true;
+          break;
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT:
+          nodeData["folder"] = true;
+          break;
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_URI:
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_VISIT:
+        case Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FULL_VISIT:
+          nodeData["link"] = true;
+          uri = PlacesUtils._uri(node.uri);
+          break;
+      }
+
+      // annotations
+      if (uri) {
+        var names = PlacesUtils.annotations.getPageAnnotationNames(uri, {});
+        for (var j = 0; j < names.length; ++j)
+          nodeData[names[j]] = true;
+      }
+
+      // For items also include the item-specific annotations
+      if (node.itemId != -1) {
+        names = PlacesUtils.annotations
+                           .getItemAnnotationNames(node.itemId, {});
+        for (j = 0; j < names.length; ++j)
+          nodeData[names[j]] = true;
+      }
+      metadata.push(nodeData);
+    }
+
+    return metadata;
+  },
+
+  /**
+   * Determines if a context-menu item should be shown
+   * @param   aMenuItem
+   *          the context menu item
+   * @param   aMetaData
+   *          meta data about the selection
+   * @returns true if the conditions (see buildContextMenu) are satisfied
+   *          and the item can be displayed, false otherwise.
+   */
+  _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
+    var selectiontype = aMenuItem.getAttribute("selectiontype");
+    if (selectiontype == "multiple" && aMetaData.length == 1)
+      return false;
+    if (selectiontype == "single" && aMetaData.length != 1)
+      return false;
+
+    var forceHideRules = aMenuItem.getAttribute("forcehideselection").split("|");
+    for (var i = 0; i < aMetaData.length; ++i) {
+      for (var j=0; j < forceHideRules.length; ++j) {
+        if (forceHideRules[j] in aMetaData[i])
+          return false;
+      }
+    }
+
+    var selectionAttr = aMenuItem.getAttribute("selection");
+    if (selectionAttr) {
+      if (selectionAttr == "any")
+        return true;
+
+      var showRules = selectionAttr.split("|");
+      var anyMatched = false;
+      function metaDataNodeMatches(metaDataNode, rules) {
+        for (var i=0; i < rules.length; i++) {
+          if (rules[i] in metaDataNode)
+            return true;
+        }
+
+        return false;
+      }
+      for (var i = 0; i < aMetaData.length; ++i) {
+        if (metaDataNodeMatches(aMetaData[i], showRules))
+          anyMatched = true;
+        else
+          return false;
+      }
+      return anyMatched;
+    }
+
+    return !aMenuItem.hidden;
+  },
+
+  /**
+   * Detects information (meta-data rules) about the current selection in the
+   * view (see _buildSelectionMetadata) and sets the visibility state for each
+   * of the menu-items in the given popup with the following rules applied:
+   *  1) The "selectiontype" attribute may be set on a menu-item to "single"
+   *     if the menu-item should be visible only if there is a single node
+   *     selected, or to "multiple" if the menu-item should be visible only if
+   *     multiple nodes are selected. If the attribute is not set or if it is
+   *     set to an invalid value, the menu-item may be visible for both types of
+   *     selection.
+   *  2) The "selection" attribute may be set on a menu-item to the various
+   *     meta-data rules for which it may be visible. The rules should be
+   *     separated with the | character.
+   *  3) A menu-item may be visible only if at least one of the rules set in
+   *     its selection attribute apply to each of the selected nodes in the
+   *     view.
+   *  4) The "forcehideselection" attribute may be set on a menu-item to rules
+   *     for which it should be hidden. This attribute takes priority over the
+   *     selection attribute. A menu-item would be hidden if at least one of the
+   *     given rules apply to one of the selected nodes. The rules should be
+   *     separated with the | character.
+   *  5) The visibility state of a menu-item is unchanged if none of this
+   *     attribute is set.
+   *  6) This attribute should not be set on separators for which the
+   *     visibility state is "auto-detected."
+   * @param   aPopup
+   *          The menupopup to build children into.
+   * @return true if at least one item is visible, false otherwise.
+   */
+  buildContextMenu: function PC_buildContextMenu(aPopup) {
+    var metadata = this._buildSelectionMetadata();
+
+    var separator = null;
+    var visibleItemsBeforeSep = false;
+    var anyVisible = false;
+    for (var i = 0; i < aPopup.childNodes.length; ++i) {
+      var item = aPopup.childNodes[i];
+      if (item.localName != "menuseparator") {
+        item.hidden = !this._shouldShowMenuItem(item, metadata);
+
+        if (!item.hidden) {
+          visibleItemsBeforeSep = true;
+          anyVisible = true;
+
+          // Show the separator above the menu-item if any
+          if (separator) {
+            separator.hidden = false;
+            separator = null;
+          }
+        }
+      }
+      else { // menuseparator
+        // Initially hide it. It will be unhidden if there will be at least one
+        // visible menu-item above and below it.
+        item.hidden = true;
+
+        // We won't show the separator at all if no items are visible above it
+        if (visibleItemsBeforeSep)
+          separator = item;
+
+        // New separator, count again:
+        visibleItemsBeforeSep = false;
+      }
+    }
+
+    return anyVisible;
+  },
+
+  /**
+   * Select all links in the current view.
+   */
+  selectAll: function PC_selectAll() {
+    this._view.selectAll();
+  },
+
+  /**
+   * Gives the user a chance to cancel loading lots of tabs at once
+   */
+  _confirmOpenTabs: function(numTabsToOpen) {
+    var pref = Components.classes["@mozilla.org/preferences-service;1"]
+                         .getService(Components.interfaces.nsIPrefBranch);
+
+    const kWarnOnOpenPref = "browser.tabs.warnOnOpen";
+    var reallyOpen = true;
+    if (pref.getBoolPref(kWarnOnOpenPref)) {
+      if (numTabsToOpen >= pref.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
+        var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                                      .getService(Components.interfaces.nsIPromptService);
+
+        // default to true: if it were false, we wouldn't get this far
+        var warnOnOpen = { value: true };
+
+        var messageKey = "tabs.openWarningMultipleBranded";
+        var openKey = "tabs.openButtonMultiple";
+        var strings = document.getElementById("placeBundle");
+        const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+        var brandShortName = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                       .getService(Components.interfaces.nsIStringBundleService)
+                                       .createBundle(BRANDING_BUNDLE_URI)
+                                       .GetStringFromName("brandShortName");
+
+        var buttonPressed = promptService.confirmEx(window,
+          PlacesUIUtils.getString("tabs.openWarningTitle"),
+          PlacesUIUtils.getFormattedString(messageKey,
+            [numTabsToOpen, brandShortName]),
+          (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
+          + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
+          PlacesUIUtils.getString(openKey),
+          null, null,
+          PlacesUIUtils.getFormattedString("tabs.openWarningPromptMeBranded",
+            [brandShortName]),
+          warnOnOpen);
+
+         reallyOpen = (buttonPressed == 0);
+         // don't set the pref unless they press OK and it's false
+         if (reallyOpen && !warnOnOpen.value)
+           pref.setBoolPref(kWarnOnOpenPref, false);
+      }
+    }
+    return reallyOpen;
+  },
+
+  /**
+   * Opens the links in the selected folder, or the selected links in new tabs.
+   */
+  openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
+    var node = this._view.selectedNode;
+    if (node && PlacesUtils.nodeIsContainer(node))
+      PlacesUIUtils.openContainerNodeInTabs(this._view.selectedNode, aEvent);
+    else
+      PlacesUIUtils.openURINodesInTabs(this._view.getSelectionNodes(), aEvent);
+  },
+
+  /**
+   * Walk the list of folders we're removing in this delete operation, and
+   * see if the selected node specified is already implicitly being removed
+   * because it is a child of that folder.
+   * @param   node
+   *          Node to check for containment.
+   * @param   pastFolders
+   *          List of folders the calling function has already traversed
+   * @returns true if the node should be skipped, false otherwise.
+   */
+  _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
+    /**
+     * Determines if a node is contained by another node within a resultset.
+     * @param   node
+     *          The node to check for containment for
+     * @param   parent
+     *          The parent container to check for containment in
+     * @returns true if node is a member of parent's children, false otherwise.
+     */
+    function isContainedBy(node, parent) {
+      var cursor = node.parent;
+      while (cursor) {
+        if (cursor == parent)
+          return true;
+        cursor = cursor.parent;
+      }
+      return false;
+    }
+
+      for (var j = 0; j < pastFolders.length; ++j) {
+        if (isContainedBy(node, pastFolders[j]))
+          return true;
+      }
+      return false;
+  },
+
+  /**
+   * Removes the set of selected ranges from history.
+   */
+  _removeRowsFromHistory: function PC__removeRowsFromHistory() {
+    // Other containers are history queries, just delete from history
+    // history deletes are not undoable.
+    var nodes = this._view.getSelectionNodes();
+    var URIs = [];
+    var bhist = PlacesUtils.history.QueryInterface(Components.interfaces.nsIBrowserHistory);
+    var resultView = this._view.getResultView();
+    var root = this._view.getResultNode();
+
+    for (var i = 0; i < nodes.length; ++i) {
+      var node = nodes[i];
+      if (PlacesUtils.nodeIsHost(node))
+        bhist.removePagesFromHost(node.title, true);
+      else if (PlacesUtils.nodeIsURI(node)) {
+        var uri = PlacesUtils._uri(node.uri);
+        // avoid trying to delete the same url twice
+        if (URIs.indexOf(uri) < 0) {
+          URIs.push(uri);
+        }
+      }
+      else if (PlacesUtils.nodeIsDay(node)) {
+        // this is the oldest date
+        // for the last node endDate is end of epoch
+        var beginDate = 0;
+        // this is the newest date
+        // day nodes have time property set to the last day in the interval
+        var endDate = node.time;
+
+        var nodeIdx = 0;
+        var cc = root.childCount;
+
+        // Find index of current day node
+        while (nodeIdx < cc && root.getChild(nodeIdx) != node)
+          ++nodeIdx;
+
+        // We have an older day
+        if (nodeIdx+1 < cc)
+          beginDate = root.getChild(nodeIdx+1).time;
+
+        // we want to exclude beginDate from the removal
+        bhist.removePagesByTimeframe(beginDate+1, endDate);
+      }
+    }
+
+    // if we have to delete a lot of urls RemovePage will be slow, it's better
+    // to delete them in bunch and rebuild the full treeView
+    if (URIs.length > REMOVE_PAGES_MAX_SINGLEREMOVES) {
+      // do removal in chunks to avoid passing a too big array to removePages
+      for (var i = 0; i < URIs.length; i += REMOVE_PAGES_CHUNKLEN) {
+        var URIslice = URIs.slice(i, i + REMOVE_PAGES_CHUNKLEN);
+        // set DoBatchNotify (third param) only on the last chunk, so we update
+        // the treeView when we are done.
+        bhist.removePages(URIslice, URIslice.length,
+                          (i + REMOVE_PAGES_CHUNKLEN) >= URIs.length);
+      }
+    }
+    else {
+      // if we have to delete fewer urls, removepage will allow us to avoid
+      // rebuilding the full treeView
+      for (var i = 0; i < URIs.length; ++i)
+        bhist.removePage(URIs[i]);
+    }
+  },
+
+  /**
+   * Fills a DataTransfer object with the content of the selection that can be
+   * dropped elsewhere.
+   * @param   aEvent
+   *          The dragstart event.
+   */
+  setDataTransfer: function PC_setDataTransfer(aEvent) {
+    var dt = aEvent.dataTransfer;
+    var doCopy = dt.effectAllowed == "copyLink" || dt.effectAllowed == "copy";
+
+    var result = this._view.getResult();
+    var oldViewer = result.viewer;
+    try {
+      result.viewer = null;
+      var nodes = this._view.getDragableSelection();
+
+      for (var i = 0; i < nodes.length; ++i) {
+        var node = nodes[i];
+
+        function addData(type, index, overrideURI) {
+          var wrapNode = PlacesUtils.wrapNode(node, type, overrideURI, doCopy);
+          dt.mozSetDataAt(type, wrapNode, index);
+        }
+
+        function addURIData(index, overrideURI) {
+          addData(PlacesUtils.TYPE_X_MOZ_URL, index, overrideURI);
+          addData(PlacesUtils.TYPE_UNICODE, index, overrideURI);
+          addData(PlacesUtils.TYPE_HTML, index, overrideURI);
+        }
+
+        // This order is _important_! It controls how this and other
+        // applications select data to be inserted based on type.
+        addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
+
+        if (node.uri)
+          addURIData(i);
+      }
+    }
+    finally {
+      if (oldViewer)
+        result.viewer = oldViewer;
+    }
+  },
+
+  /**
+   * Copy Bookmarks and Folders to the clipboard
+   */
+  copy: function PC_copy() {
+    var result = this._view.getResult();
+    var oldViewer = result.viewer;
+    try {
+      result.viewer = null;
+      var nodes = this._view.getSelectionNodes();
+
+      var xferable = Components.classes["@mozilla.org/widget/transferable;1"]
+                               .createInstance(Components.interfaces.nsITransferable);
+      var foundFolder = false, foundLink = false;
+      var copiedFolders = [];
+      var placeString, mozURLString, htmlString, unicodeString;
+      placeString = mozURLString = htmlString = unicodeString = "";
+
+      for (var i = 0; i < nodes.length; ++i) {
+        var node = nodes[i];
+        if (this._shouldSkipNode(node, copiedFolders))
+          continue;
+        if (PlacesUtils.nodeIsFolder(node))
+          copiedFolders.push(node);
+
+        function generateChunk(type, overrideURI) {
+          var suffix = i < (nodes.length - 1) ? NEWLINE : "";
+          var uri = overrideURI;
+
+          mozURLString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_X_MOZ_URL,
+                                                 uri) + suffix);
+          unicodeString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_UNICODE,
+                                                 uri) + suffix);
+          htmlString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_HTML,
+                                                 uri) + suffix);
+
+          var placeSuffix = i < (nodes.length - 1) ? "," : "";
+          var resolveShortcuts = false; // !PlacesControllerDragHelper.canMoveNode(node);
+          return PlacesUtils.wrapNode(node, type, overrideURI, resolveShortcuts) + placeSuffix;
+        }
+
+        // all items wrapped as TYPE_X_MOZ_PLACE
+        placeString += generateChunk(PlacesUtils.TYPE_X_MOZ_PLACE);
+      }
+
+      function addData(type, data) {
+        xferable.addDataFlavor(type);
+        xferable.setTransferData(type, PlacesUIUtils._wrapString(data), data.length * 2);
+      }
+      // This order is _important_! It controls how this and other applications
+      // select data to be inserted based on type.
+      if (placeString)
+        addData(PlacesUtils.TYPE_X_MOZ_PLACE, placeString);
+      if (mozURLString)
+        addData(PlacesUtils.TYPE_X_MOZ_URL, mozURLString);
+      if (unicodeString)
+        addData(PlacesUtils.TYPE_UNICODE, unicodeString);
+      if (htmlString)
+        addData(PlacesUtils.TYPE_HTML, htmlString);
+
+      if (placeString || unicodeString || htmlString || mozURLString) {
+        PlacesUIUtils.clipboard.setData(xferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
+      }
+    }
+    catch(e) {
+      dump(e);
+    }
+    finally {
+      if (oldViewer)
+        result.viewer = oldViewer;
+    }
+  },
+
+  /**
+   * Cut Bookmarks and Folders to the clipboard
+   */
+  cut: function PC_cut() {
+    this.copy();
+    this._removeRowsFromHistory();
+  },
+};
+
+function goUpdatePlacesCommands() {
+  goUpdateCommand("placesCmd_open");
+  goUpdateCommand("placesCmd_open:window");
+  goUpdateCommand("placesCmd_open:tab");
+  goUpdateCommand("placesCmd_sortBy:name");
+}
deleted file mode 100644
--- a/suite/common/history/findHistory.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Ben Goodger <ben@netscape.com> (Original Author)
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-var gOKButton;
-var gSearchField;
-function Startup()
-{
-  var bundle = document.getElementById("historyBundle");
-  gOKButton = document.documentElement.getButton("accept");
-  gOKButton.disabled = true;
-  gSearchField = document.getElementById("searchField");
-  gSearchField.focus();
-}
-
-function find()
-{
-  // Build up a find URI from the search fields and open a new window
-  // rooted on the URI. 
-  var match = document.getElementById("matchList");
-  var method = document.getElementById("methodList");
-  var searchURI = "find:datasource=history"
-  searchURI += "&match=" + match.selectedItem.value;
-  searchURI += "&method=" + method.selectedItem.value;
-  searchURI += "&text=" + encodeURIComponent(gSearchField.value);
-  var hstWindow = findMostRecentWindow("history:searchresults", "chrome://communicator/content/history/history.xul", searchURI);
-  
-  // Update the root of the tree if we're using an existing search window. 
-  if (!gCreatingNewWindow) 
-    hstWindow.setRoot(searchURI);
-
-  hstWindow.focus();
-  return true;
-}
-
-var gCreatingNewWindow = false;
-function findMostRecentWindow(aType, aURI, aParam)
-{
-  var WM = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService();
-  WM = WM.QueryInterface(Components.interfaces.nsIWindowMediator);
-  var topWindow = WM.getMostRecentWindow(aType);
-  if (!topWindow) gCreatingNewWindow = true;
-  return topWindow || openDialog("chrome://communicator/content/history/history.xul", 
-                                 "", "chrome,all,dialog=no", aParam);
-}
-
-function doEnabling()
-{
-  gOKButton.disabled = !gSearchField.value;
-}
deleted file mode 100644
--- a/suite/common/history/findHistory.xul
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0"?> <!-- -*- Mode: HTML; indent-tabs-mode: nil; -*- -->
-<!--
-
- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is mozilla.org code.
-
- The Initial Developer of the Original Code is
- Netscape Communications Corporation.
- Portions created by the Initial Developer are Copyright (C) 1998
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
-   Ben Goodger <ben@netscape.com> (Original Author)
-
- Alternatively, the contents of this file may be used under the terms of
- either of the GNU General Public License Version 2 or later (the "GPL"),
- or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
-
-<!--
-    "Find in History" window
-  -->
-
-<?xml-stylesheet href="chrome://communicator/skin/"?>
-<!DOCTYPE dialog SYSTEM "chrome://communicator/locale/history/findHistory.dtd">
-
-<dialog id="findHistoryWindow" style="width: 36em;"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="&findHistory.title;" 
-        onload="Startup();"
-        ondialogaccept="return find();"
-        buttonlabelaccept="&findButton.label;">
-
-  <stringbundle id="historyBundle" src="chrome://communicator/locale/history/history.properties"/>
-
-  <script type="application/x-javascript" src="chrome://communicator/content/history/findHistory.js"/>
-
-  <label value="&search.for.label;" control="matchList"/>
-  <hbox align="center">
-    <menulist id="matchList">
-      <menupopup>
-        <menuitem value="Name" label="&search.name.label;"/>
-        <menuitem value="URL" label="&search.url.label;"/>
-      </menupopup>
-    </menulist>
-    <menulist id="methodList">
-      <menupopup>
-        <menuitem value="contains" label="&search.contains.label;"/>
-        <menuitem value="startswith" label="&search.startswith.label;"/>
-        <menuitem value="endswith" label="&search.endswith.label;"/>
-        <menuitem value="is" label="&search.is.label;"/>
-        <menuitem value="isnot" label="&search.isnot.label;"/>
-        <menuitem value="doesntcontain" label="&search.doesntcontain.label;"/>
-      </menupopup>
-    </menulist>
-    <textbox id="searchField" flex="1" oninput="doEnabling();"/>
-  </hbox>
-</dialog>
-
--- a/suite/common/history/history-panel.xul
+++ b/suite/common/history/history-panel.xul
@@ -1,82 +1,122 @@
 <?xml version="1.0"?> <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
-<!--
-
- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is mozilla.org code.
 
- The Initial Developer of the Original Code is
- Netscape Communications Corporation.
- Portions created by the Initial Developer are Copyright (C) 1998
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is mozilla.org code.
+   -
+   - The Initial Developer of the Original Code is
+   - Netscape Communications Corporation.
+   - Portions created by the Initial Developer are Copyright (C) 1998
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Alec Flett <alecf@netscape.com> (original author of history-panel.xul)
+   -   Seth Spitzer <sspitzer@mozilla.org> (port to Places)
+   -
+   - 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 LGPL or the GPL. 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 ***** -->
 
- Alternatively, the contents of this file may be used under the terms of
- either of the GNU General Public License Version 2 or later (the "GPL"),
- or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
-
+<?xml-stylesheet href="chrome://communicator/content/history/places.css"?>
 <?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://communicator/skin/sidebar/sidebarListView.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/history/places.css"?>
 
-<?xul-overlay href="chrome://communicator/content/history/historyTreeOverlay.xul"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/history/placesOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
 
-<!DOCTYPE page SYSTEM "chrome://communicator/locale/history/history.dtd" >
+<!DOCTYPE page SYSTEM "chrome://communicator/locale/history/history.dtd">
+
+<!-- we need to keep id="history-panel" for upgrade and switching
+     between versions of the browser -->
 
 <page id="history-panel" orient="vertical"
       xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-      onload="HistoryCommonInit();"
-      onunload="HistoryPanelUnload();"
-      elementtofocus="historyTree">
-  <stringbundle id="historyBundle"
-                src="chrome://communicator/locale/history/history.properties"/>
-  <commandset id="selectEditMenuItems">
-    <command id="cmd_delete"/>
-    <command id="cmd_copy"/>
-  </commandset>
-  <keyset id="historyKeys">
-    <key id="key_delete"/>
+      onload="HistorySidebarInit();"
+      onunload="SidebarUtils.clearURLFromStatusBar();"
+      elementtofocus="search-box">
+
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/sidebarUtils.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/history.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/bookmarks/bookmarks.js"/>
+
+  <commandset id="editMenuCommands"/>
+  <commandset id="placesCommands"/>
+
+  <keyset id="editMenuKeys">
     <key id="key_delete2"/>
-    <key id="key_copy"/>
   </keyset>
-  <popupset id="historyContextMenu"/>
-  <!-- use deep merging to hide certain columns by default -->
-  <tree id="historyTree" onfocus="goUpdateSelectEditMenuItems();" seltype="single">
-    <treecols id="historyTreeCols">
-      <treecol id="Name"/>
-      <splitter id="pre-URL-splitter"/>
-      <treecol id="URL" hidden="true"/>
-      <splitter id="pre-Date-splitter"/>
-      <treecol id="Date" hidden="true"/>
-      <splitter id="pre-FirstVisitDate-splitter"/>
-      <treecol id="FirstVisitDate" hidden="true"/>
-      <splitter id="pre-Hostname-splitter"/>
-      <treecol id="Hostname" hidden="true"/>
-      <splitter id="pre-Referrer-splitter"/>
-      <treecol id="Referrer" hidden="true"/>
-      <splitter id="pre-VisitCount-splitter"/>
-      <treecol id="VisitCount" hidden="true"/>
+
+  <popup id="placesContext"/>
+
+  <hbox align="center">
+    <textbox id="search-box" flex="1" type="search"
+             emptytext="&search.emptytext;"
+             oncommand="searchHistory(this.value);"/>
+    <toolbarbutton id="viewButton" type="menu"
+            label="&view.label;"
+            selectedsort="day" persist="selectedsort">
+      <menupopup>
+        <menuitem id="bydayandsite" label="&byDayAndSite.label;"
+                  accesskey="&byDayAndSite.accesskey;" type="radio"
+                  oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'dayandsite'); GroupBy('dayandsite');"/>
+        <menuitem id="bysite" label="&bySite.label;"
+                  accesskey="&bySite.accesskey;" type="radio"
+                  oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'site'); GroupBy('site');"/>
+        <menuitem id="byday" label="&byDate.label;"
+                  accesskey="&byDate.accesskey;"
+                  type="radio"
+                  oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'day'); GroupBy('day');"/>
+        <menuitem id="byvisited" label="&byMostVisited.label;"
+                  accesskey="&byMostVisited.accesskey;"
+                  type="radio"
+                  oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'visited'); GroupBy('visited');"/>
+        <menuitem id="bylastvisited" label="&byLastVisited.label;"
+                  accesskey="&byLastVisited.accesskey;"
+                  type="radio"
+                  oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'lastvisited'); GroupBy('lastvisited');"/>
+      </menupopup>
+    </toolbarbutton>
+  </hbox>
+
+  <tree id="historyTree"
+        class="sidebar-placesTree plain"
+        flex="1"
+        type="places"
+        context="placesContext"
+        hidecolumnpicker="true"
+        onkeypress="SidebarUtils.handleTreeKeyPress(event);"
+        onclick="SidebarUtils.handleTreeClick(this, event, true);"
+        onmousemove="SidebarUtils.handleTreeMouseMove(event);"
+        onmouseout="SidebarUtils.clearURLFromStatusBar();">
+    <treecols>
+      <treecol id="title" flex="1" primary="true" hideheader="true"/>
     </treecols>
+    <treechildren class="sidebar-placesTreechildren" flex="1"/>
   </tree>
 </page>
deleted file mode 100644
--- a/suite/common/history/history-test.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-function ShowLastPageVisited() {
-    dump("start of ShowLastPageVisited()\n");
-
-    var lastpagevisited = "failure...not set";
-    
-    var history = Components.classes['@mozilla.org/browser/global-history;1'];
-    if (history) {
-      history = history.getService();
-    }
-    if (history) {
-      history = history.QueryInterface(Components.interfaces.nsIGlobalHistory);
-    }
-    if (history) {
-      try {
-        lastpagevisited = history.lastPageVisited;
-	document.getElementById('result').value =  lastpagevisited;
-      } 
-      catch (ex) {
-	dump(ex + "\n");
-	document.getElementById('result').value = "Error check console";
-      }    
-    }
-}
deleted file mode 100644
--- a/suite/common/history/history-test.xul
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?> 
-<?xml-stylesheet href="xul.css" type="text/css"?> 
-<?xml-stylesheet href="chrome://navigator/skin/navigator.css" type="text/css"?> 
-
-<window title="history test" 
-	id="history-test-window" xmlns:html="http://www.w3.org/1999/xhtml"
-	xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-<script type="application/x-javascript" src="history-test.js"/>
-
-<html:b>history test</html:b>
-<html:hr/>
-
-<hbox flex="100%">
-<html:input id="result" type="text" style="min-width: 400px; min-height: 27px" flex="100%"/>
-</hbox>
-<html:hr/>
-<html:button onclick="ShowLastPageVisited();">Show Last Page Visited</html:button>  
-</window>
--- a/suite/common/history/history.js
+++ b/suite/common/history/history.js
@@ -16,528 +16,130 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Alec Flett <alecf@netscape.com>
+ *   Seth Spitzer <sspizer@mozilla.org> (port to Places)
+ *   Asaf Romano <mano@mozilla.com>
+ *   Robert Kaiser <kairo@kairo.at>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
-// The history window uses JavaScript in bookmarks.js too.
 
 var gHistoryTree;
-var gLastHostname;
-var gLastDomain;
-var gGlobalHistory;
-var gPrefService;
-var gIOService;
-var gDeleteByHostname;
-var gDeleteByDomain;
-var gHistoryBundle;
-var gHistoryStatus;
+var gSearchBox;
 var gHistoryGrouping = "";
-
-function HistoryCommonInit()
-{
-    gHistoryTree =  document.getElementById("historyTree");
-    gDeleteByHostname = document.getElementById("menu_deleteByHostname");
-    gDeleteByDomain =   document.getElementById("menu_deleteByDomain");
-    gHistoryBundle =    document.getElementById("historyBundle");
-    gHistoryStatus =    document.getElementById("statusbar-display");
-
-    var treeController = new nsTreeController(gHistoryTree);
-    var historyController = new nsHistoryController;
-    gHistoryTree.controllers.appendController(historyController);
-
-    gPrefService = Components.classes["@mozilla.org/preferences-service;1"]
-                              .getService(Components.interfaces.nsIPrefBranch);
-    PREF = gPrefService;    // need this for bookmarks.js
+var gSearching = false;
 
-    if ("arguments" in window && window.arguments[0] && window.arguments.length >= 1) {
-        // We have been supplied a resource URI to root the tree on
-        var uri = window.arguments[0];
-        gHistoryTree.setAttribute("ref", uri);
-        if (uri.substring(0,5) == "find:" &&
-            !(window.arguments.length > 1 && window.arguments[1] == "newWindow")) {
-            // Update the windowtype so that future searches are directed 
-            // there and the window is not re-used for bookmarks. 
-            var windowNode = document.getElementById("history-window");
-            windowNode.setAttribute("windowtype", "history:searchresults");
-            document.title = gHistoryBundle.getString("search_results_title");
+function HistorySidebarInit()
+{
+  gHistoryTree = document.getElementById("historyTree");
+  gSearchBox = document.getElementById("search-box");
 
-        }
-        document.getElementById("groupingMenu").setAttribute("hidden", "true");
-    }
-    else {
-        try {
-            gHistoryGrouping = gPrefService.getCharPref("browser.history.grouping");
-        }
-        catch(e) {
-            gHistoryGrouping = "day";
-        }
-        UpdateTreeGrouping();
-        if (gHistoryStatus) {  // must be the window
-            switch(gHistoryGrouping) {
-            case "none":
-                document.getElementById("groupByNone").setAttribute("checked", "true");
-                break;
-            case "site":
-                document.getElementById("groupBySite").setAttribute("checked", "true");
-                break;
-            case "day":
-            default:
-                document.getElementById("groupByDay").setAttribute("checked", "true");
-            }        
-        }
-        else {  // must be the sidebar panel
-            var pb = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
-            var pbi = pb.QueryInterface(Components.interfaces.nsIPrefBranch2);
-            pbi.addObserver("browser.history.grouping", groupObserver, false);
-        }
-    } 
-
-    SortInNewDirection(find_sort_direction(find_sort_column()));
-
-    if (gHistoryStatus)
-        gHistoryTree.focus();
+  gHistoryGrouping = document.getElementById("viewButton").
+                              getAttribute("selectedsort");
 
-    if (gHistoryTree.view.rowCount > 0)
-        gHistoryTree.view.selection.select(0);
-    else if (gHistoryStatus)
-        updateHistoryCommands();
-}
-
-function HistoryPanelUnload()
-{
-  var pb = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
-  var pbi = pb.QueryInterface(Components.interfaces.nsIPrefBranch2);
-  pbi.removeObserver("browser.history.grouping", groupObserver, false);
-}
-
-function updateHistoryCommands()
-{
-    goUpdateCommand("cmd_deleteByHostname");
-    goUpdateCommand("cmd_deleteByDomain");
-}
-
-function historyOnClick(aEvent)
-{
-  // This is kind of a hack but matches the currently implemented behaviour. 
-  // If a status bar is not present, assume we're in sidebar mode, and thus single clicks on containers
-  // will open the container. Single clicks on non-containers are handled below in historyOnSelect.
-  if (gHistoryStatus && aEvent.button == 0)
-    return;
-
-  var target = BookmarksUtils.getBrowserTargetFromEvent(aEvent);
-  if (!target)
-    return;
+  if (gHistoryGrouping == "site")
+    document.getElementById("bysite").setAttribute("checked", "true");
+  else if (gHistoryGrouping == "visited")
+    document.getElementById("byvisited").setAttribute("checked", "true");
+  else if (gHistoryGrouping == "lastvisited")
+    document.getElementById("bylastvisited").setAttribute("checked", "true");
+  else if (gHistoryGrouping == "dayandsite")
+    document.getElementById("bydayandsite").setAttribute("checked", "true");
+  else
+    document.getElementById("byday").setAttribute("checked", "true");
 
-  var row = { };
-  var col = { };
-  var elt = { };
-  gHistoryTree.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, row, col, elt);
-  if (row.value >= 0 && col.value) {
-    if (!isContainer(gHistoryTree, row.value))
-      OpenURL(target, aEvent);
-    else if (aEvent.button == 0 && elt.value != "twisty")
-      gHistoryTree.treeBoxObject.view.toggleOpenState(row.value);
-  }
-}
-
-function historyOnSelect()
-{
-    // every time selection changes, save the last hostname
-    gLastHostname = "";
-    gLastDomain = "";
-    var match;
-    var currentIndex = gHistoryTree.currentIndex;
-    var rowIsContainer = currentIndex < 0 || (gHistoryGrouping != "none" && isContainer(gHistoryTree, currentIndex));
-    var col = gHistoryTree.columns["URL"];
-    var url = rowIsContainer ? "" : gHistoryTree.view.getCellText(currentIndex, col);
-
-    if (url) {
-        if (!gIOService)
-            gIOService = Components.classes['@mozilla.org/network/io-service;1']
-                                   .getService(Components.interfaces.nsIIOService);
-        try {
-            gLastHostname = gIOService.newURI(url, null, null).host;
-            // matches the last foo.bar in foo.bar or baz.foo.bar
-            match = gLastHostname.match(/([^.]+\.[^.]+$)/);
-            if (match)
-                gLastDomain = match[1];
-        } catch (e) {}
-    }
-
-    if (gHistoryStatus)
-        gHistoryStatus.label = url;
-
-    document.commandDispatcher.updateCommands("select");
-}
-
-function nsHistoryController()
-{
+  searchHistory("");
 }
 
-nsHistoryController.prototype =
+function GroupBy(groupingType)
 {
-    supportsCommand: function(command)
-    {
-        switch(command) {
-        case "cmd_deleteByHostname":
-        case "cmd_deleteByDomain":
-            return true;
-        default:
-            return false;
-        }
-    },
-
-    isCommandEnabled: function(command)
-    {
-        var enabled = false;
-        var text;
-        switch(command) {
-        case "cmd_deleteByHostname":
-            if (gLastHostname) {
-                text = gHistoryBundle.getFormattedString("deleteHost", [ gLastHostname ]);
-                enabled = true;
-            } else {
-                text = gHistoryBundle.getString("deleteHostNoSelection");
-            }
-
-            gDeleteByHostname.label = text;
-            break;
-        case "cmd_deleteByDomain":
-            if (gLastDomain) {
-                text = gHistoryBundle.getFormattedString("deleteDomain", [ gLastDomain ]);
-                enabled = true;
-            } else {
-                text = gHistoryBundle.getString("deleteDomainNoSelection");
-            }
-
-            gDeleteByDomain.label = text;
-        }
-        return enabled;
-    },
-
-    doCommand: function(command)
-    {
-        switch(command) {
-        case "cmd_deleteByHostname":
-            if (!gGlobalHistory)
-                gGlobalHistory = Components.classes["@mozilla.org/browser/global-history;2"].getService(Components.interfaces.nsIBrowserHistory);
-            gGlobalHistory.removePagesFromHost(gLastHostname, false)
-            return true;
-
-        case "cmd_deleteByDomain":
-            if (!gGlobalHistory)
-                gGlobalHistory = Components.classes["@mozilla.org/browser/global-history;2"].getService(Components.interfaces.nsIBrowserHistory);
-            gGlobalHistory.removePagesFromHost(gLastDomain, true)
-            return true;
-
-        default:
-            return false;
-        }
-    }
-}
-
-var historyDNDObserver = {
-    onDragStart: function (aEvent, aXferData, aDragAction)
-    {
-        var currentIndex = gHistoryTree.currentIndex;
-        if (isContainer(gHistoryTree, currentIndex))
-            return false;
-        var col = gHistoryTree.columns["URL"];
-        var url = gHistoryTree.view.getCellText(currentIndex, col);
-        col = gHistoryTree.columns["Name"];
-        var title = gHistoryTree.view.getCellText(currentIndex, col);
-
-        var htmlString = "<A HREF='" + url + "'>" + title + "</A>";
-        aXferData.data = new TransferData();
-        aXferData.data.addDataForFlavour("text/unicode", url);
-        aXferData.data.addDataForFlavour("text/html", htmlString);
-        aXferData.data.addDataForFlavour("text/x-moz-url", url + "\n" + title);
-        return true;
-    }
-};
-
-function validClickConditions(event)
-{
-  var currentIndex = gHistoryTree.currentIndex;
-  if (currentIndex == -1) return false;
-  var container = isContainer(gHistoryTree, currentIndex);
-  return (event.button == 0 &&
-          event.originalTarget.localName == 'treechildren' &&
-          !container && gHistoryStatus);
-}
-
-function collapseExpand()
-{
-    var currentIndex = gHistoryTree.currentIndex;
-    gHistoryTree.treeBoxObject.view.toggleOpenState(currentIndex);
+  gHistoryGrouping = groupingType;
+  gSearchBox.value = "";
+  searchHistory("");
 }
 
-function OpenURL(aTarget, aEvent)
-{
-    var currentIndex = gHistoryTree.currentIndex;     
-    var builder = gHistoryTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder);
-    var url = builder.getResourceAtIndex(currentIndex).ValueUTF8;
-    if (!gIOService)
-      gIOService = Components.classes['@mozilla.org/network/io-service;1']
-                             .getService(Components.interfaces.nsIIOService);
-    try {
-      var scheme = gIOService.extractScheme(url);
-      if (scheme == "javascript" || scheme == "data") {
-        var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
-                                         .getService(Components.interfaces.nsIStringBundleService);
-        var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                                      .getService(Components.interfaces.nsIPromptService);
-        var historyBundle = strBundleService.createBundle("chrome://communicator/locale/history/history.properties");
-        var brandBundle = strBundleService.createBundle("chrome://branding/locale/brand.properties");      
-        var brandStr = brandBundle.GetStringFromName("brandShortName");
-        var errorStr = historyBundle.GetStringFromName("load-js-data-url-error");
-        promptService.alert(window, brandStr, errorStr);
-        return false;
-      }
-    } catch (e) {
-      return false;
-    }
-
-    if (aTarget != "current") {
-      var count = gHistoryTree.view.selection.count;
-      var URLArray = [];
-      if (count == 1) {
-        if (isContainer(gHistoryTree, currentIndex))
-          openDialog("chrome://communicator/content/history/history.xul", 
-                     "", "chrome,all,dialog=no", url, "newWindow");
-        else
-          URLArray.push(url);
-      }
-      else {
-        var col = gHistoryTree.columns["URL"];
-        var min = new Object(); 
-        var max = new Object();
-        var rangeCount = gHistoryTree.view.selection.getRangeCount();
-        for (var i = 0; i < rangeCount; ++i) {
-          gHistoryTree.view.selection.getRangeAt(i, min, max);
-          for (var k = max.value; k >= min.value; --k) {
-            url = gHistoryTree.view.getCellText(k, col);
-            URLArray.push(url);
-          }
-        }
-      }
-      if (aTarget == "window") {
-        for (i = 0; i < URLArray.length; i++) {
-          openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no", URLArray[i]);
-        }
-      } else {
-        if (URLArray.length > 0)
-          OpenURLArrayInTabs(URLArray, BookmarksUtils.shouldLoadTabInBackground(aEvent));
-      }
-    }
-    else if (!isContainer(gHistoryTree, currentIndex))
-      openTopWin(url);
-    return true;
-}
-
-// This opens the URLs contained in the given array in new tabs
-// of the most recent window, creates a new window if necessary.
-function OpenURLArrayInTabs(aURLArray, aBackground)
-{
-  var browserWin = getTopWin();
-  if (browserWin) {
-    var browser = browserWin.getBrowser();
-    var tab = browser.addTab(aURLArray[0]);
-    if (!aBackground)
-      browser.selectedTab = tab;
-    for (var i = 1; i < aURLArray.length; ++i)
-      browser.addTab(aURLArray[i]);
-  } else {
-    openTopWin(aURLArray.join("\n")); // Pretend that we're a home page group
-  }
-}
-
-/**
- * Root the tree on a given URI (used for displaying search results)
- */
-function setRoot(root)
-{
-  gHistoryTree.ref = root;
-}
-
-function GroupBy(aGroupingType)
-{
-  gHistoryGrouping = aGroupingType;
-  UpdateTreeGrouping();
-  gPrefService.setCharPref("browser.history.grouping", aGroupingType);
-}
-
-function UpdateTreeGrouping()
-{
-  var tree = document.getElementById("historyTree");
-  switch(gHistoryGrouping) {
-  case "none":
-    tree.setAttribute("ref", "NC:HistoryRoot");
-    break;
-  case "site":
-    tree.setAttribute("ref", "find:datasource=history&groupby=Hostname");
-    break;
-  case "day":
-  default:
-    tree.setAttribute("ref", "NC:HistoryByDate");
-    break;
-  }
-}
-
-var groupObserver = {
-  observe: function(aPrefBranch, aTopic, aPrefName) {
-    try {
-      gHistoryGrouping = gPrefService.getCharPref("browser.history.grouping");
-    }
-    catch(e) {
-      gHistoryGrouping = "day";
-    }
-    UpdateTreeGrouping();
-  }
-}
-
+// we need bookmarks.js to set bookmarks!
 function historyAddBookmarks()
 {
-  var urlCol = gHistoryTree.columns["URL"];
-  var titleCol = gHistoryTree.columns["Name"];
-
+  // if we're not sidebar, the tree ID is "placeContent"
+  if (!gHistoryTree)
+    gHistoryTree = document.getElementById("placeContent");
   var count = gHistoryTree.view.selection.count;
-  var url;
-  var title;
-  if (count == 1) {
-    var currentIndex = gHistoryTree.currentIndex;
-    url = gHistoryTree.treeBoxObject.view.getCellText(currentIndex, urlCol);
-    title = gHistoryTree.treeBoxObject.view.getCellText(currentIndex, titleCol);
-    BookmarksUtils.addBookmark(url, title, null, true);
-  }
+  if (count == 1)
+    BookmarksUtils.addBookmark(gHistoryTree.selectedNode.uri,
+                               gHistoryTree.selectedNode.title, null, true);
   else if (count > 1) {
-    var min = new Object(); 
-    var max = new Object();
-    var rangeCount = gHistoryTree.view.selection.getRangeCount();
     if (!BMSVC) {
       initServices();
       initBMService();
     }
-    for (var i = 0; i < rangeCount; ++i) {
-      gHistoryTree.view.selection.getRangeAt(i, min, max);
-      for (var k = max.value; k >= min.value; --k) {
-        url = gHistoryTree.view.getCellText(k, urlCol);
-        title = gHistoryTree.view.getCellText(k, titleCol);
-        BookmarksUtils.addBookmark(url, title, null, false);
-      }
+    selNodes = gHistoryTree.getSelectionNodes();
+    for (var i = 0; i < selNodes.length; i++) {
+      // skip over separators and folders
+      if (PlacesUtils.nodeIsURI(selNodes[i]))
+        BookmarksUtils.addBookmark(selNodes.uri, selNodes.title, null, false);
     }
   }
 }
 
-
-function updateItems()
+function searchHistory(aInput)
 {
-  var count = gHistoryTree.view.selection.count;
-  var openItem = document.getElementById("miOpen");
-  var bookmarkItem = document.getElementById("miAddBookmark");
-  var copyLocationItem = document.getElementById("miCopyLinkLocation");
-  var sep1 = document.getElementById("pre-bookmarks-separator");
-  var sep2 = document.getElementById("post-bookmarks-separator");
-  var openItemInNewWindow = document.getElementById("miOpenInNewWindow");
-  var openItemInNewTab = document.getElementById("miOpenInNewTab");
-  var collapseExpandItem = document.getElementById("miCollapseExpand");
-  if (count > 1) {
-    var hasContainer = false;
-    if (gHistoryGrouping != "none") {
-      var min = new Object(); 
-      var max = new Object();
-      var rangeCount = gHistoryTree.view.selection.getRangeCount();
-      for (var i = 0; i < rangeCount; ++i) {
-        gHistoryTree.view.selection.getRangeAt(i, min, max);
-        for (var k = max.value; k >= min.value; --k) {
-          if (isContainer(gHistoryTree, k)) {
-            hasContainer = true;
-            break;
-          }
-        }
-      }
-    }
-    collapseExpandItem.hidden = true;
-    openItem.hidden = true;
-    if (hasContainer) {   // several items selected, folder(s) amongst them
-      bookmarkItem.hidden = true;
-      copyLocationItem.hidden = true;
-      sep1.hidden = true;
-      sep2.hidden = true;
-      openItemInNewWindow.hidden = true;
-      openItemInNewTab.hidden = true;
-    }
-    else {                // several items selected, but no folder
-      bookmarkItem.hidden = false;
-      copyLocationItem.hidden = false;
-      sep1.hidden = false;
-      sep2.hidden = false;
-      bookmarkItem.setAttribute("label", document.getElementById('multipleBookmarks').getAttribute("label"));
-      bookmarkItem.setAttribute("accesskey", document.getElementById('multipleBookmarks').getAttribute("accesskey"));
-      openItem.removeAttribute("default");
-      openItemInNewWindow.setAttribute("default", "true");
-      openItemInNewWindow.hidden = false;
-      openItemInNewTab.hidden = false;
+  var query = PlacesUtils.history.getNewQuery();
+  var options = PlacesUtils.history.getNewQueryOptions();
+
+  const NHQO = Components.interfaces.nsINavHistoryQueryOptions;
+  var sortingMode;
+  var resultType;
+
+  if (aInput) {
+    query.searchTerms = aInput;
+    sortingMode = NHQO.SORT_BY_TITLE_ASCENDING;
+    resultType = NHQO.RESULTS_AS_URI;
+  }
+  else {
+    switch (gHistoryGrouping) {
+      case "visited":
+        resultType = NHQO.RESULTS_AS_URI;
+        sortingMode = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
+        break;
+      case "lastvisited":
+        resultType = NHQO.RESULTS_AS_URI;
+        sortingMode = NHQO.SORT_BY_DATE_DESCENDING;
+        break;
+      case "dayandsite":
+        resultType = NHQO.RESULTS_AS_DATE_SITE_QUERY;
+        break;
+      case "site":
+        resultType = NHQO.RESULTS_AS_SITE_QUERY;
+        sortingMode = NHQO.SORT_BY_TITLE_ASCENDING;
+        break;
+      case "day":
+      default:
+        resultType = NHQO.RESULTS_AS_DATE_QUERY;
+        break;
     }
   }
-  else {
-    openItemInNewWindow.hidden = false;
-    bookmarkItem.setAttribute("label", document.getElementById('oneBookmark').getAttribute("label"));
-    bookmarkItem.setAttribute("accesskey", document.getElementById('oneBookmark').getAttribute("accesskey"));
-    sep2.hidden = false;
-    var currentIndex = gHistoryTree.currentIndex;
-    if (isContainer(gHistoryTree, currentIndex)) {   // one folder selected
-        openItem.hidden = true;
-        openItem.removeAttribute("default");
-        openItemInNewWindow.removeAttribute("default");
-        openItemInNewTab.hidden = true;
-        collapseExpandItem.hidden = false;
-        collapseExpandItem.setAttribute("default", "true");
-        bookmarkItem.hidden = true;
-        copyLocationItem.hidden = true;
-        sep1.hidden = true;
-        if (isContainerOpen(gHistoryTree, currentIndex)) {
-          collapseExpandItem.setAttribute("label", gHistoryBundle.getString("collapseLabel"));
-          collapseExpandItem.setAttribute("accesskey", gHistoryBundle.getString("collapseAccesskey"));
-        }
-        else {
-          collapseExpandItem.setAttribute("label", gHistoryBundle.getString("expandLabel"));
-          collapseExpandItem.setAttribute("accesskey", gHistoryBundle.getString("expandAccesskey"));          
-        }
-        return true;
-    }                                                // one entry selected (not a folder)
-    openItemInNewTab.hidden = false;
-    collapseExpandItem.hidden = true;
-    bookmarkItem.hidden = false;
-    copyLocationItem.hidden = false;
-    sep1.hidden = false;
-    if (!getTopWin()) {
-        openItem.hidden = true;
-        openItem.removeAttribute("default");
-        openItemInNewWindow.setAttribute("default", "true");
-    }
-    else {
-      openItem.hidden = false;
-      if (!openItem.getAttribute("default"))
-        openItem.setAttribute("default", "true");
-      openItemInNewWindow.removeAttribute("default");
-    }
-  }
-  return true;
+
+  options.sortingMode = sortingMode;
+  options.resultType = resultType;
+
+  // call load() on the tree manually
+  // instead of setting the place attribute in history-panel.xul
+  // otherwise, we will end up calling load() twice
+  gHistoryTree.load([query], options);
 }
--- a/suite/common/history/history.xul
+++ b/suite/common/history/history.xul
@@ -1,160 +1,214 @@
-<?xml version="1.0"?> <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
-<!--
-
- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is mozilla.org code.
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is mozilla.org code.
+   -
+   - The Initial Developer of the Original Code is
+   - Netscape Communications Corporation.
+   - Portions created by the Initial Developer are Copyright (C) 1998
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Blake Ross <blaker@netscape.com> (original history.xul)
+   -   Ben Goodger <beng@google.com> (places organizer)
+   -   Annie Sullivan <annie.sullivan@gmail.com> (places organizer)
+   -   Asaf Romano <mano@mozilla.com> (places organizer)
+   -   Ehsan Akhgari <ehsan.akhgari@gmail.com> (places organizer)
+   -   Dietrich Ayala <dietrich@mozilla.com> (places organizer)
+   -   Robert Kaiser <kairo@kairo.at> (combining history.xul with places)
+   -
+   - 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 LGPL or the GPL. 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 ***** -->
 
- The Initial Developer of the Original Code is
- Netscape Communications Corporation.
- Portions created by the Initial Developer are Copyright (C) 1998
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
-   Blake Ross <blaker@netscape.com>
+<?xml-stylesheet href="chrome://communicator/content/history/places.css"?>
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/history/places.css"?>
 
- Alternatively, the contents of this file may be used under the terms of
- either of the GNU General Public License Version 2 or later (the "GPL"),
- or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
-
-<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
-
-<?xul-overlay href="chrome://communicator/content/history/historyTreeOverlay.xul"?>
 <?xul-overlay href="chrome://global/content/globalOverlay.xul"?>
 <?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/history/placesOverlay.xul"?>
 
-<!DOCTYPE window SYSTEM "chrome://communicator/locale/history/history.dtd" >
+<!DOCTYPE window [
+<!ENTITY % historyDTD SYSTEM "chrome://communicator/locale/history/history.dtd">
+%historyDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+%globalDTD;
+]>
 
 <window title="&historyWindowTitle.label;" id="history-window"
-        onload="HistoryCommonInit();"
+        onload="PlacesOrganizer.init();"
+        onunload="PlacesOrganizer.destroy();"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="500" height="400"
         persist="width height screenX screenY sizemode"
         windowtype="history:manager">
 
-  <stringbundle id="historyBundle"
-                src="chrome://communicator/locale/history/history.properties"/>
-  <stringbundle id="sortBundle" src="chrome://global/locale/nsTreeSorting.properties"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/places.js"/>
+  <script type="application/x-javascript"
+          src="chrome://global/content/globalOverlay.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/history.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/bookmarks/bookmarks.js"/>
 
-<commandset id="tasksCommands">
-  <commandset id="selectEditMenuItems"/>
-  <commandset id="globalEditMenuItems"/>
-  <commandset id="historyEditMenuItems"
-              commandupdater="true"
-              events="select"
-              oncommandupdate="updateHistoryCommands()"/>
-  <command id="cmd_searchHistory" oncommand="window.openDialog('chrome://communicator/content/history/findHistory.xul', 'FindHistoryWindow', 'dialog=no,centerscreen,resizable=no,chrome,dependent');"/>
+  <commandset id="tasksCommands">
+    <!-- File Menu -->
+    <command id="cmd_close" oncommand="window.close()"/>
+    <command id="cmd_quit"/>
+    <command id="bm_cmd_saveas"/>
+    <!-- Search Box -->
+    <command id="cmd_search_focus"
+             oncommand="PlacesSearchBox.focus();"/>
+  </commandset>
+  <commandset id="editMenuCommands"/>
+  <commandset id="placesCommands"/>
 
-  <!-- File Menu -->
-  <command id="cmd_close" oncommand="window.close()"/>
-  <command id="cmd_quit"/>
-  <command id="bm_cmd_saveas"/>
-  <!-- Edit Menu -->
-  <command id="cmd_deleteByHostname" oncommand="goDoCommand('cmd_deleteByHostname');"/>
-  <command id="cmd_deleteByDomain" oncommand="goDoCommand('cmd_deleteByDomain');"/>
-
-</commandset>
+  <broadcaster id="Communicator:WorkMode"/>
 
-<broadcaster id="Communicator:WorkMode"/>
+  <keyset id="tasksKeys">
+    <!-- File Menu -->
+    <key id="key_close"/>
+    <key id="key_quit"/>
+    <!-- Edit Menu -->
+    <key id="key_cut"/>
+    <key id="key_copy"/>
+    <key id="key_delete"/>
+    <key id="key_delete2"/>
+    <key id="key_selectAll"/>
+    <!-- Search Box -->
+    <key id="key_search_focus"
+         command="cmd_search_focus"
+         key="&search.key;"
+         modifiers="accel"/>
+  </keyset>
 
-<keyset id="tasksKeys">
-  <!-- File Menu -->
-  <key id="key_close"/>
-  <key id="key_quit"/>
-  <!-- Edit Menu -->
-  <key id="key_cut"/>
-  <key id="key_copy"/>
-  <key id="key_delete"/>
-  <key id="key_delete2"/>
-  <key id="key_selectAll"/>
-  <key id="key_searchHistory" key="&findHisCmd.commandkey;" command="cmd_searchHistory" modifiers="accel"/>
-</keyset>
+  <popupset id="placesPopupset">
+    <popup id="placesContext"/>
+    <popup id="placesColumnsContext"
+           onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
+           oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+  </popupset>
 
-<popupset id="historyContextMenu"/>
-
-<toolbox id="history-toolbox">
-<menubar id="history-menu" grippytooltiptext="&menuBar.tooltip;">
-  <menu id="menu_File">
-    <menupopup id="menu_FilePopup">
-      <menuitem id="menu_close"/>
-    </menupopup>
-  </menu>
+  <toolbox id="history-toolbox">
+  <menubar id="history-menu"
+           grippytooltiptext="&menuBar.tooltip;"
+           chromedir="&locale.dir;">
+    <menu id="menu_File">
+      <menupopup id="menu_FilePopup">
+        <menuitem id="menu_close"/>
+      </menupopup>
+    </menu>
 
-  <menu id="menu_Edit">
-    <menupopup>
-      <menuitem id="menu_cut"/>
-      <menuitem id="menu_copy"/>
-      <menuitem id="menu_delete"/>
-      <menuitem id="menu_deleteByHostname" command="cmd_deleteByHostname"
-                accesskey="&deleteHostnameCmd.accesskey;"/>
-      <menuitem id="menu_deleteByDomain" command="cmd_deleteByDomain"
-                accesskey="&deleteDomainCmd.accesskey;"/>
-      <menuseparator/>
-      <menuitem id="menu_selectAll"/>
-    </menupopup>
-  </menu>
-  <menu id="menu_View">
-    <menupopup onpopupshowing="fillViewMenu(this)">
-      <menu id="groupingMenu" label="&groupBy.label;" accesskey="&groupBy.accesskey;">
-        <menupopup>
-          <menuitem id="groupByDay" label="&groupByDay.label;" accesskey="&groupByDay.accesskey;" type="radio" oncommand="GroupBy('day');"/>
-          <menuitem id="groupBySite" label="&groupBySite.label;" accesskey="&groupBySite.accesskey;" type="radio" oncommand="GroupBy('site');"/>
-          <menuitem id="groupByNone" label="&groupByNone.label;" accesskey="&groupByNone.accesskey;" type="radio" oncommand="GroupBy('none');"/>
-        </menupopup>
-      </menu>
-      <menuitem type="radio" name="sort_column" id="unsorted_menuitem"
-        label="&menuitem.view.unsorted.label;"
-        accesskey="&menuitem.view.unsorted.accesskey;"
-        oncommand="return SortInNewDirection('natural');"/>
-      <menuseparator id="fill_after_this_node"/>
-      <menuseparator id="fill_before_this_node"/>
-      <menuitem type="radio" name="sort_direction" id="sort_ascending"
-        label="&sortAscending.label;"
-        accesskey="&sortAscending.accesskey;"
-        oncommand="return SortInNewDirection('ascending');"/>
-      <menuitem type="radio" name="sort_direction" id="sort_descending"
-        label="&sortDescending.label;"
-        accesskey="&sortDescending.accesskey;"
-        oncommand="return SortInNewDirection('descending');"/>
-    </menupopup>
-  </menu>
-  <menu id="tasksMenu">
-    <menupopup id="taskPopup">
-      <menuitem id="menu_searchHistory" label="&findHisCmd.label;" key="key_searchHistory"
-                accesskey="&findHisCmd.accesskey;" command="cmd_searchHistory"/>
-      <menuseparator/>
-    </menupopup>
-  </menu>
-  <menu id="windowMenu"/>
-  <menu id="menu_Help"/>
-</menubar>
-</toolbox>
+    <menu id="menu_Edit">
+      <menupopup>
+        <menuitem id="menu_cut"
+                  selection="separator|link|folder|mixed"/>
+        <menuitem id="menu_copy"
+                  selection="separator|link|folder|mixed"/>
+        <menuitem id="menu_delete"/>
+        <menuseparator/>
+        <menuitem id="cMenu_selectAll"/>
+      </menupopup>
+    </menu>
+    <menu id="menu_View">
+      <menupopup>
+        <menu id="viewColumns"
+              label="&view.columns.label;" accesskey="&view.columns.accesskey;">
+          <menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
+                     oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+        </menu>
+        <menu id="viewSort" label="&view.sort.label;"
+              accesskey="&view.sort.accesskey;">
+          <menupopup onpopupshowing="ViewMenu.populateSortMenu(event);"
+                     oncommand="ViewMenu.setSortColumn(event.target.column, null);">
+            <menuitem id="viewUnsorted" type="radio" name="columns"
+                      label="&view.unsorted.label;" accesskey="&view.unsorted.accesskey;"
+                      oncommand="ViewMenu.setSortColumn(null, null);"/>
+            <menuseparator id="directionSeparator"/>
+            <menuitem id="viewSortAscending" type="radio" name="direction"
+                      label="&view.sortAscending.label;" accesskey="&view.sortAscending.accesskey;"
+                      oncommand="ViewMenu.setSortColumn(null, 'ascending'); event.stopPropagation();"/>
+            <menuitem id="viewSortDescending" type="radio" name="direction"
+                      label="&view.sortDescending.label;" accesskey="&view.sortDescending.accesskey;"
+                      oncommand="ViewMenu.setSortColumn(null, 'descending'); event.stopPropagation();"/>
+          </menupopup>
+        </menu>
+      </menupopup>
+    </menu>
+    <menu id="tasksMenu"/>
+    <menu id="windowMenu"/>
+    <menu id="menu_Help"/>
+  </menubar>
+  <toolbar class="chromeclass-toolbar"
+           id="placesToolbar"
+           align="center"
+           grippytooltiptext="&searchBar.tooltip;"
+           chromedir="&locale.dir;">
+    <textbox id="searchFilter"
+             clickSelectsAll="true"
+             type="search"
+             emptytext="&search.emptytext;"
+             oncommand="PlacesSearchBox.search(this.value);"
+             collection="history">
+    </textbox>
+  </toolbar>
+  </toolbox>
 
-  <tree id="historyTree"/>
+  <tree id="placeContent"
+        class="placesTree"
+        context="placesContext"
+        flex="1" type="places"
+        flatList="true"
+        enableColumnDrag="true"
+        onkeypress="if (event.keyCode == KeyEvent.DOM_VK_RETURN) PlacesOrganizer.openSelectedNode(event);"
+        ondblclick="PlacesOrganizer.openSelectedNode(event);"
+        onfocus="PlacesOrganizer.onTreeFocus(event);"
+        onclick="PlacesOrganizer.onTreeClick(event);">
+    <treecols id="placeContentColumns" context="placesColumnsContext">
+      <treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1"
+               persist="width hidden ordinal sortActive sortDirection"/>
+      <splitter class="tree-splitter"/>
+      <treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5"
+               persist="width hidden ordinal sortActive sortDirection"/>
+      <splitter class="tree-splitter"/>
+      <treecol label="&col.lastvisit.label;" id="placesContentDate" anonid="date" flex="1"
+               persist="width hidden ordinal sortActive sortDirection"/>
+      <splitter class="tree-splitter"/>
+      <treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true"
+               persist="width hidden ordinal sortActive sortDirection"/>
+    </treecols>
+    <treechildren flex="1"/>
+  </tree>
   <statusbar id="status-bar" class="chromeclass-status">
     <statusbarpanel id="statusbar-display" flex="1"/>
     <statusbarpanel class="statusbarpanel-iconic" id="offline-status"/>
   </statusbar>
 
 </window>
deleted file mode 100644
--- a/suite/common/history/historyTreeOverlay.xul
+++ /dev/null
@@ -1,167 +0,0 @@
-<?xml version="1.0"?>
-<!--
-
- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is mozilla.org code.
-
- The Initial Developer of the Original Code is
- Netscape Communications Corporation.
- Portions created by the Initial Developer are Copyright (C) 1998
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
-   Ben Goodger <ben@netscape.com>
-   Blake Ross <blakeross@telocity.com>
-   Alec Flett <alecf@netscape.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either of the GNU General Public License Version 2 or later (the "GPL"),
- or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
-
-<!DOCTYPE overlay [
-<!ENTITY % historyDTD SYSTEM "chrome://communicator/locale/history/historyTreeOverlay.dtd" >
-%historyDTD;
-<!ENTITY % contentAreaCommandsDTD SYSTEM "chrome://communicator/locale/contentAreaCommands.dtd" >
-%contentAreaCommandsDTD;
-]>
-<?xml-stylesheet href="chrome://communicator/skin/bookmarks/bookmarks.css" type="text/css"?>
-
-<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
-
-<overlay id="historyTreeOverlay"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-         xmlns:web="http://home.netscape.com/WEB-rdf#"
-         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-
-  <script type="application/x-javascript" src="chrome://global/content/nsDragAndDrop.js"/>
-  <script type="application/x-javascript" src="chrome://global/content/nsTreeController.js"/>
-  <script type="application/x-javascript" src="chrome://global/content/globalOverlay.js"/>
-  <script type="application/x-javascript" src="chrome://communicator/content/history/history.js"/>
-  <script type="application/x-javascript" src="chrome://communicator/content/history/historyTreeSorting.js"/>
-  <script type="application/x-javascript" src="chrome://communicator/content/bookmarks/bookmarks.js"/>
-  
-  <popupset id="historyContextMenu">
-    <command id="cmd_selectAll"/>
-    <command id="cmd_cut"/>
-    <command id="cmd_copy"/>
-    <command id="cmd_delete"/>
-    <command id="cmd_selectAll"/>
-    <popup id="historyMenu" onpopupshowing="return updateItems();">
-      <menuitem id="miCollapseExpand" oncommand="collapseExpand();"/>
-      <menuitem id="miOpen" label="&openLinkInWindowCmd.label;" accesskey="&openLinkInWindowCmd.accesskey;" default="true"
-                oncommand="OpenURL('current');"/>
-      <menuitem id="miOpenInNewWindow" label="&openLinkCmd.label;" accesskey="&openLinkCmd.accesskey;"
-                oncommand="OpenURL('window');"/>
-      <menuitem id="miOpenInNewTab" label="&openLinkCmdInTab.label;" accesskey="&openLinkCmdInTab.accesskey;"
-                oncommand="OpenURL('tab', event);"/>
-      <menuseparator id="pre-bookmarks-separator"/>
-      <menuitem id="miAddBookmark" label="&bookmarkLinkCmd.label;" accesskey="&bookmarkLinkCmd.accesskey;" oncommand="historyAddBookmarks();"/>
-      <menuitem id="miCopyLinkLocation" label="&copyLinkCmd.label;" accesskey="&copyLinkCmd.accesskey;"
-                command="cmd_copy"/>
-      <menuseparator id="post-bookmarks-separator"/>
-      <menuitem id="miDelete" label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;"
-                command="cmd_delete"/>
-      <menuitem id="miSelectAll" label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;"
-                command="cmd_selectAll"/>
-    </popup>
-    <data id="multipleBookmarks" label="&bookmarkLinksCmd.label;" accesskey="&bookmarkLinksCmd.accesskey;"/>
-    <data id="oneBookmark" label="&bookmarkLinkCmd.label;" accesskey="&bookmarkLinkCmd.accesskey;"/>
-  </popupset>
-  <tree id="historyTree"
-        treelines="true"
-        flex="1"
-        enableColumnDrag="true"
-        class="plain"
-        context="historyMenu"
-        datasources="rdf:history"
-        ref="NC:HistoryByDate"
-        flags="dont-build-content"
-        onkeypress="if (event.keyCode == 13) OpenURL(BookmarksUtils.getBrowserTargetFromEvent(event), event);"
-        onselect="this.view.selectionChanged(); historyOnSelect();"
-        onclick="historyOnClick(event);"
-        ondraggesture="if (event.originalTarget.localName == 'treechildren') nsDragAndDrop.startDrag(event, historyDNDObserver);"
-        ondblclick="if (validClickConditions(event)) OpenURL(BookmarksUtils.getBrowserTargetFromEvent(event), event);">
-    <template>
-      <rule>
-        <treechildren>
-          <treeitem uri="rdf:*" rdf:type="rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#type">
-            <treerow>
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#Name"/>
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#URL"/>
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#Date"/>
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#FirstVisitDate"/>
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#Hostname" />
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#Referrer"/>
-              <treecell label="rdf:http://home.netscape.com/NC-rdf#VisitCount"/>
-            </treerow>
-          </treeitem>
-        </treechildren>
-      </rule>
-    </template>
-    <treecols id="historyTreeCols">
-      <treecol flex="4" id="Name" persist="hidden width sortActive sortDirection ordinal" 
-                   label="&tree.header.name.label;" primary="true"
-                   sort="rdf:http://home.netscape.com/NC-rdf#Name"
-                   accesskey="&tree.header.name.accesskey;"
-                   class="sortDirectionIndicator"/>
-      <splitter class="tree-splitter" id="pre-URL-splitter"/>
-      <treecol flex="4" id="URL"
-                   persist="hidden width sortActive sortDirection ordinal"
-                   label="&tree.header.url.label;" class="sortDirectionIndicator"
-                   accesskey="&tree.header.url.accesskey;"
-                   sort="rdf:http://home.netscape.com/NC-rdf#URL"/>
-      <splitter class="tree-splitter" id="pre-Date-splitter"/>
-      <treecol flex="1" id="Date"
-                   persist="hidden width sortActive sortDirection ordinal"
-                   label="&tree.header.date.label;" class="sortDirectionIndicator"
-                   accesskey="&tree.header.date.accesskey;"
-                   sort="rdf:http://home.netscape.com/NC-rdf#Date"/>
-      <splitter class="tree-splitter" id="pre-FirstVisitDate-splitter"/>
-      <treecol flex="1" id="FirstVisitDate" hidden="true"
-                   persist="hidden width sortActive sortDirection ordinal"
-                   label="&tree.header.firstvisitdate.label;" class="sortDirectionIndicator"
-                   accesskey="&tree.header.firstvisitdate.accesskey;"
-                   sort="rdf:http://home.netscape.com/NC-rdf#FirstVisitDate"/>
-      <splitter class="tree-splitter" id="pre-Hostname-splitter"/>
-      <treecol flex="1" id="Hostname" hidden="true"
-                   persist="hidden width sortActive sortDirection ordinal"
-                   label="&tree.header.hostname.label;" class="sortDirectionIndicator"
-                   accesskey="&tree.header.hostname.accesskey;"
-                   sort="rdf:http://home.netscape.com/NC-rdf#Hostname"/>
-      <splitter class="tree-splitter" id="pre-Referrer-splitter"/>
-      <treecol flex="1" id="Referrer" hidden="true"
-                   persist="hidden width sortActive sortDirection ordinal"
-                   label="&tree.header.referrer.label;" class="sortDirectionIndicator"
-                   accesskey="&tree.header.referrer.accesskey;"
-                   sort="rdf:http://home.netscape.com/NC-rdf#Referrer"/>
-      <splitter class="tree-splitter" id="pre-VisitCount-splitter"/>
-      <treecol flex="1" id="VisitCount" hidden="true"
-                   persist="hidden width sortActive sortDirection ordinal"
-                   label="&tree.header.visitcount.label;" class="sortDirectionIndicator"
-                   accesskey="&tree.header.visitcount.accesskey;"
-                   sort="rdf:http://home.netscape.com/NC-rdf#VisitCount"/>
-    </treecols>
-  </tree>
-</overlay>
deleted file mode 100644
--- a/suite/common/history/historyTreeSorting.js
+++ /dev/null
@@ -1,199 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 1998
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Peter Annema <disttsc@bart.nl>
- *   Blake Ross <blakeross@telocity.com>
- *   Alec Flett <alecf@netscape.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either of the GNU General Public License Version 2 or later (the "GPL"),
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-// This file got moved from
-// CVS /mozilla/xpfe/global/resources/content/nsTreeSorting.js.
-
-// utility routines for sorting
-
-// set the sort direction on the currently sorted column
-function SortInNewDirection(direction)
-{
-  var current_column = find_sort_column();
-  if (direction == "ascending")
-    direction = "natural";
-  else if (direction == "descending")
-    direction = "ascending";
-  else if (direction == "natural")
-    direction = "descending";
-  current_column.setAttribute("sortDirection", direction);
-  SortColumnElement(current_column);
-}
-
-function SortColumn(columnId)
-{
-  var column = document.getElementById(columnId);
-  SortColumnElement(column);
-}
-
-function SortColumnElement(column)
-{
-  var tree = column.parentNode.parentNode;
-  var col = tree.columns.getColumnFor(column);
-  tree.view.cycleHeader(col);
-}
-
-// search over the columns to find the first one with an active sort
-function find_sort_column()
-{
-  var columns = document.getElementsByTagName('treecol');
-  var i = 0;
-  var column;
-  while ((column = columns.item(i++)) != null) {
-      if (column.getAttribute('sortDirection'))
-          return column;
-  }
-  return columns.item(0);
-}
-
-// get the sort direction for the given column
-function find_sort_direction(column)
-{
-  var sortDirection = column.getAttribute('sortDirection');
-  return (sortDirection ? sortDirection : "natural");
-}
-
-// set up the menu items to reflect the specified sort column
-// and direction - put check marks next to the active ones, and clear
-// out the old ones
-// - disable ascending/descending direction if the tree isn't sorted
-// - disable columns that are not visible
-function update_sort_menuitems(column, direction)
-{
-  var unsorted_menuitem = document.getElementById("unsorted_menuitem");
-  var sort_ascending = document.getElementById('sort_ascending');
-  var sort_descending = document.getElementById('sort_descending');
-
-  // as this function may be called from various places, including the
-  // bookmarks sidebar panel (which doesn't have any menu items)
-  // ensure that the document contains the elements
-  if ((!unsorted_menuitem) || (!sort_ascending) || (!sort_descending))
-    return;
-
-  if (direction == "natural") {
-    unsorted_menuitem.setAttribute('checked','true');
-    sort_ascending.setAttribute('disabled','true');
-    sort_descending.setAttribute('disabled','true');
-    sort_ascending.removeAttribute('checked');
-    sort_descending.removeAttribute('checked');
-  } else {
-    sort_ascending.removeAttribute('disabled');
-    sort_descending.removeAttribute('disabled');
-    if (direction == "ascending") {
-      sort_ascending.setAttribute('checked','true');
-    } else {
-      sort_descending.setAttribute('checked','true');
-    }
-
-    var columns = document.getElementsByTagName('treecol');
-    var i = 0;
-    var column_node = columns[i];
-    var column_name = column.id;
-    var menuitem = document.getElementById('fill_after_this_node');
-    menuitem = menuitem.nextSibling
-    while (1) {
-      var name = menuitem.getAttribute('column_id');
-      if (!name) break;
-      if (column_name == name) {
-        menuitem.setAttribute('checked', 'true');
-        break;
-      }
-      menuitem = menuitem.nextSibling;
-      column_node = columns[++i];
-      if (column_node && column_node.tagName == "splitter") {
-        column_node = columns[++i];
-      }
-    }
-  }
-  enable_sort_menuitems();
-}
-
-function enable_sort_menuitems()
-{
-  var columns = document.getElementsByTagName('treecol');
-  var menuitem = document.getElementById('fill_after_this_node');
-  menuitem = menuitem.nextSibling
-  for (var i = 0; (i < columns.length) && menuitem; ++i) {
-    var column_node = columns[i];
-    if (column_node.getAttribute("hidden") == "true")
-      menuitem.setAttribute("disabled", "true");
-    else
-      menuitem.removeAttribute("disabled");
-    menuitem = menuitem.nextSibling;
-  }
-}
-
-function fillViewMenu(popup)
-{
-  var fill_after = document.getElementById('fill_after_this_node');
-  var fill_before = document.getElementById('fill_before_this_node');
-  var strBundle = document.getElementById('sortBundle');
-  var sortString;
-  if (strBundle)
-    sortString = strBundle.getString('SortMenuItems');
-  if (!sortString)
-    sortString = "Sorted by %COLNAME%";
-    
-  var firstTime = (fill_after.nextSibling == fill_before);
-  if (firstTime) {
-    var columns = document.getElementsByTagName('treecol');
-    for (var i = 0; i < columns.length; ++i) {
-      var column = columns[i];
-      // Construct an entry for each cell in the row.
-      var column_name = column.getAttribute("label");
-      var column_accesskey = column.getAttribute("accesskey");
-      var item = document.createElement("menuitem");
-      if (column_accesskey)
-        item.setAttribute("accesskey", column_accesskey);
-      item.setAttribute("type", "radio");
-      item.setAttribute("name", "sort_column");
-      if (column_name == "")
-        column_name = column.getAttribute("display");
-      var name = sortString.replace(/%COLNAME%/g, column_name);
-      item.setAttribute("label", name);
-      item.setAttribute("oncommand", "SortColumn('" + column.id + "');");
-      item.setAttribute("column_id", column.id);
-      popup.insertBefore(item, fill_before);
-    }
-  }
-  var sort_column = find_sort_column();
-  var sort_direction = find_sort_direction(sort_column);
-  update_sort_menuitems(sort_column, sort_direction);
-}
new file mode 100644
--- /dev/null
+++ b/suite/common/history/places.css
@@ -0,0 +1,3 @@
+tree[type="places"] {
+  -moz-binding: url("chrome://communicator/content/history/tree.xml#places-tree");
+}
new file mode 100755
--- /dev/null
+++ b/suite/common/history/places.js
@@ -0,0 +1,484 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Places Organizer.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005-2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Annie Sullivan <annie.sullivan@gmail.com>
+ *   Asaf Romano <mano@mozilla.com>
+ *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
+ *   Robert Kaiser <kairo@kairo.at>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var PlacesOrganizer = {
+  _content: null,
+
+  init: function PO_init() {
+    this._content = document.getElementById("placeContent");
+
+    var view = this._content.treeBoxObject.view;
+    if (view.rowCount > 0)
+      view.selection.select(0);
+
+    // do an initial load of the tree with history content
+    var query = PlacesUtils.history.getNewQuery();
+    var options = PlacesUtils.history.getNewQueryOptions();
+    query.searchTerms = "";
+    options.queryType = Components.interfaces.nsINavHistoryQueryOptions
+                                  .QUERY_TYPE_HISTORY;
+    this._content.load([query], options);
+
+    this._content.focus();
+
+    window.addEventListener("AppCommand", this, true);
+  },
+
+  QueryInterface: function PO_QueryInterface(aIID) {
+    if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
+        aIID.equals(Components.interfaces.nsISupports))
+      return this;
+
+    throw Components.results.NS_NOINTERFACE;
+  },
+
+  handleEvent: function PO_handleEvent(aEvent) {
+    if (aEvent.type != "AppCommand")
+      return;
+
+    aEvent.stopPropagation();
+    switch (aEvent.command) {
+      case "Search":
+        PlacesSearchBox.findAll();
+        break;
+    }
+  },
+
+  destroy: function PO_destroy() {
+  },
+
+  _location: null,
+  get location() {
+    return this._location;
+  },
+
+  set location(aLocation) {
+    if (!aLocation || this._location == aLocation)
+      return aLocation;
+
+    this._location = aLocation;
+    // just load place: uri directly
+    this._content.place = aLocation;
+
+    return aLocation;
+  },
+
+  /**
+   * Handle clicks on the tree. If the user middle clicks on a URL, load that
+   * URL according to rules. Single clicks or modified clicks do not result in
+   * any special action, since they're related to selection.
+   * @param   aEvent
+   *          The mouse event.
+   */
+  onTreeClick: function PO_onTreeClick(aEvent) {
+    if (aEvent.target.localName != "treechildren")
+      return;
+
+    var currentView = aEvent.currentTarget;
+    var selectedNode = currentView.selectedNode;
+    if (selectedNode && aEvent.button == 1) {
+      if (PlacesUtils.nodeIsURI(selectedNode))
+        PlacesUIUtils.openNodeWithEvent(selectedNode, aEvent);
+      else if (PlacesUtils.nodeIsContainer(selectedNode)) {
+        // The command execution function will take care of seeing the
+        // selection is a folder/container and loading its contents in
+        // tabs for us.
+        PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent);
+      }
+    }
+  },
+
+  /**
+   * Handle focus changes on the trees.
+   * When moving focus between panes we should update the details pane contents.
+   * @param   aEvent
+   *          The mouse event.
+   */
+  onTreeFocus: function PO_onTreeFocus(aEvent) {
+  },
+
+  openSelectedNode: function PU_openSelectedNode(aEvent) {
+    PlacesUIUtils.openNodeWithEvent(this._content.selectedNode, aEvent);
+  },
+
+  /**
+   * Returns the options associated with the query currently loaded in the
+   * main places pane.
+   */
+  getCurrentOptions: function PO_getCurrentOptions() {
+    return asQuery(this._content.getResult().root).queryOptions;
+  },
+
+  /**
+   * Returns the queries associated with the query currently loaded in the
+   * main places pane.
+   */
+  getCurrentQueries: function PO_getCurrentQueries() {
+    return asQuery(this._content.getResult().root).getQueries({});
+  },
+
+  _showErrorAlert: function PO__showErrorAlert(aMsg) {
+    var brandShortName = document.getElementById("brandStrings").
+                                  getString("brandShortName");
+
+    Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+              .getService(Components.interfaces.nsIPromptService)
+              .alert(window, brandShortName, aMsg);
+  },
+};
+
+/**
+ * A set of utilities relating to search within Bookmarks and History.
+ */
+var PlacesSearchBox = {
+
+  /**
+   * The Search text field
+   */
+  get searchFilter() {
+    return document.getElementById("searchFilter");
+  },
+
+  /**
+   * Folders to include when searching.
+   */
+  _folders: [],
+  get folders() {
+    if (this._folders.length == 0)
+      this._folders.push(PlacesUtils.bookmarksMenuFolderId,
+                         PlacesUtils.unfiledBookmarksFolderId,
+                         PlacesUtils.toolbarFolderId);
+    return this._folders;
+  },
+  set folders(aFolders) {
+    this._folders = aFolders;
+    return aFolders;
+  },
+
+  /**
+   * Run a search for the specified text, over the collection specified by
+   * the dropdown arrow. The default is all bookmarks, but can be
+   * localized to the active collection.
+   * @param   filterString
+   *          The text to search for.
+   */
+  search: function PSB_search(filterString) {
+    var PO = PlacesOrganizer;
+
+    var currentOptions = PO.getCurrentOptions();
+    var content = PO._content;
+
+      if (currentOptions.queryType != Components.interfaces.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+        var query = PlacesUtils.history.getNewQuery();
+        query.searchTerms = filterString;
+        var options = currentOptions.clone();
+        options.queryType = Components.interfaces.nsINavHistoryQueryOptions
+                                      .QUERY_TYPE_HISTORY;
+        content.load([query], options);
+      }
+      else
+        content.applyFilter(filterString);
+  },
+
+  /**
+   * Focus the search box
+   */
+  focus: function PSB_focus() {
+    this.searchFilter.focus();
+  },
+
+  /**
+   * Gets or sets the text shown in the Places Search Box
+   */
+  get value() {
+    return this.searchFilter.value;
+  },
+  set value(value) {
+    return this.searchFilter.value = value;
+  },
+};
+
+/**
+ * Population and commands for the View Menu.
+ */
+var ViewMenu = {
+  /**
+   * Removes content generated previously from a menupopup.
+   * @param   popup
+   *          The popup that contains the previously generated content.
+   * @param   startID
+   *          The id attribute of an element that is the start of the
+   *          dynamically generated region - remove elements after this
+   *          item only.
+   *          Must be contained by popup. Can be null (in which case the
+   *          contents of popup are removed).
+   * @param   endID
+   *          The id attribute of an element that is the end of the
+   *          dynamically generated region - remove elements up to this
+   *          item only.
+   *          Must be contained by popup. Can be null (in which case all
+   *          items until the end of the popup will be removed). Ignored
+   *          if startID is null.
+   * @returns The element for the caller to insert new items before,
+   *          null if the caller should just append to the popup.
+   */
+  _clean: function VM__clean(popup, startID, endID) {
+    if (endID)
+      NS_ASSERT(startID, "meaningless to have valid endID and null startID");
+    if (startID) {
+      var startElement = document.getElementById(startID);
+      NS_ASSERT(startElement.parentNode ==
+                popup, "startElement is not in popup");
+      NS_ASSERT(startElement,
+                "startID does not correspond to an existing element");
+      var endElement = null;
+      if (endID) {
+        endElement = document.getElementById(endID);
+        NS_ASSERT(endElement.parentNode == popup,
+                  "endElement is not in popup");
+        NS_ASSERT(endElement,
+                  "endID does not correspond to an existing element");
+      }
+      while (startElement.nextSibling != endElement)
+        popup.removeChild(startElement.nextSibling);
+      return endElement;
+    }
+    else {
+      while(popup.hasChildNodes())
+        popup.removeChild(popup.firstChild);
+    }
+    return null;
+  },
+
+  /**
+   * Fills a menupopup with a list of columns
+   * @param   event
+   *          The popupshowing event that invoked this function.
+   * @param   startID
+   *          see _clean
+   * @param   endID
+   *          see _clean
+   * @param   type
+   *          the type of the menuitem, e.g. "radio" or "checkbox".
+   *          Can be null (no-type).
+   *          Checkboxes are checked if the column is visible.
+   * @param   propertyPrefix
+   *          If propertyPrefix is non-null:
+   *          propertyPrefix + column ID + ".label" will be used to get the
+   *          localized label string.
+   *          propertyPrefix + column ID + ".accesskey" will be used to get the
+   *          localized accesskey.
+   *          If propertyPrefix is null, the column label is used as label and
+   *          no accesskey is assigned.
+   */
+  fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) {
+    var popup = event.target;
+    var pivot = this._clean(popup, startID, endID);
+
+    // If no column is "sort-active", the "Unsorted" item needs to be checked,
+    // so track whether or not we find a column that is sort-active.
+    var isSorted = false;
+    var content = document.getElementById("placeContent");
+    var columns = content.columns;
+    for (var i = 0; i < columns.count; ++i) {
+      var column = columns.getColumnAt(i).element;
+      var menuitem = document.createElement("menuitem");
+      menuitem.id = "menucol_" + column.id;
+      menuitem.column = column;
+      var label = column.getAttribute("label");
+      if (propertyPrefix) {
+        var menuitemPrefix = propertyPrefix;
+        // for string properties, use "name" as the id, instead of "title"
+        // see bug #386287 for details
+        var columnId = column.getAttribute("anonid");
+        menuitemPrefix += columnId == "title" ? "name" : columnId;
+        label = PlacesUIUtils.getString(menuitemPrefix + ".label");
+        var accesskey = PlacesUIUtils.getString(menuitemPrefix + ".accesskey");
+        menuitem.setAttribute("accesskey", accesskey);
+      }
+      menuitem.setAttribute("label", label);
+      if (type == "radio") {
+        menuitem.setAttribute("type", "radio");
+        menuitem.setAttribute("name", "columns");
+        // This column is the sort key. Its item is checked.
+        if (column.getAttribute("sortDirection") != "") {
+          menuitem.setAttribute("checked", "true");
+          isSorted = true;
+        }
+      }
+      else if (type == "checkbox") {
+        menuitem.setAttribute("type", "checkbox");
+        // Cannot uncheck the primary column.
+        if (column.getAttribute("primary") == "true")
+          menuitem.setAttribute("disabled", "true");
+        // Items for visible columns are checked.
+        if (!column.hidden)
+          menuitem.setAttribute("checked", "true");
+      }
+      if (pivot)
+        popup.insertBefore(menuitem, pivot);
+      else
+        popup.appendChild(menuitem);
+    }
+    event.stopPropagation();
+  },
+
+  /**
+   * Set up the content of the view menu.
+   */
+  populateSortMenu: function VM_populateSortMenu(event) {
+    this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.");
+
+    var sortColumn = this._getSortColumn();
+    var viewSortAscending = document.getElementById("viewSortAscending");
+    var viewSortDescending = document.getElementById("viewSortDescending");
+    // We need to remove an existing checked attribute because the unsorted
+    // menu item is not rebuilt every time we open the menu like the others.
+    var viewUnsorted = document.getElementById("viewUnsorted");
+    if (!sortColumn) {
+      viewSortAscending.removeAttribute("checked");
+      viewSortDescending.removeAttribute("checked");
+      viewUnsorted.setAttribute("checked", "true");
+    }
+    else if (sortColumn.getAttribute("sortDirection") == "ascending") {
+      viewSortAscending.setAttribute("checked", "true");
+      viewSortDescending.removeAttribute("checked");
+      viewUnsorted.removeAttribute("checked");
+    }
+    else if (sortColumn.getAttribute("sortDirection") == "descending") {
+      viewSortDescending.setAttribute("checked", "true");
+      viewSortAscending.removeAttribute("checked");
+      viewUnsorted.removeAttribute("checked");
+    }
+  },
+
+  /**
+   * Shows/Hides a tree column.
+   * @param   element
+   *          The menuitem element for the column
+   */
+  showHideColumn: function VM_showHideColumn(element) {
+    var column = element.column;
+
+    if (element.getAttribute("checked") == "true") {
+      column.setAttribute("hidden", "false");
+    }
+    else {
+      column.setAttribute("hidden", "true");
+    }
+  },
+
+  /**
+   * Gets the last column that was sorted.
+   * @returns  the currently sorted column, null if there is no sorted column.
+   */
+  _getSortColumn: function VM__getSortColumn() {
+    var content = document.getElementById("placeContent");
+    var cols = content.columns;
+    for (var i = 0; i < cols.count; ++i) {
+      var column = cols.getColumnAt(i).element;
+      var sortDirection = column.getAttribute("sortDirection");
+      if (sortDirection == "ascending" || sortDirection == "descending")
+        return column;
+    }
+    return null;
+  },
+
+  /**
+   * Sorts the view by the specified column.
+   * @param   aColumn
+   *          The colum that is the sort key. Can be null - the
+   *          current sort column or the title column will be used.
+   * @param   aDirection
+   *          The direction to sort - "ascending" or "descending".
+   *          Can be null - the last direction or descending will be used.
+   *
+   * If both aColumnID and aDirection are null, the view will be unsorted.
+   */
+  setSortColumn: function VM_setSortColumn(aColumn, aDirection) {
+    var result = document.getElementById("placeContent").getResult();
+    if (!aColumn && !aDirection) {
+      result.sortingMode = Components.interfaces.nsINavHistoryQueryOptions
+                                     .SORT_BY_NONE;
+      return;
+    }
+
+    var columnId;
+    if (aColumn) {
+      columnId = aColumn.getAttribute("anonid")
+      if (!aDirection) {
+        var sortColumn = this._getSortColumn();
+        aDirection = sortColumn ?
+                     sortColumn.getAttribute("sortDirection") : "descending";
+      }
+    }
+    else {
+      var sortColumn = this._getSortColumn();
+      columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
+    }
+
+    var sortingMode;
+    var sortingAnnotation = "";
+    const NHQO = Components.interfaces.nsINavHistoryQueryOptions;
+    switch (columnId) {
+      case "title":
+        sortingMode = aDirection == "descending" ?
+          NHQO.SORT_BY_TITLE_DESCENDING : NHQO.SORT_BY_TITLE_ASCENDING;
+        break;
+      case "url":
+        sortingMode = aDirection == "descending" ?
+          NHQO.SORT_BY_URI_DESCENDING : NHQO.SORT_BY_URI_ASCENDING;
+        break;
+      case "date":
+        sortingMode = aDirection == "descending" ?
+          NHQO.SORT_BY_DATE_DESCENDING : NHQO.SORT_BY_DATE_ASCENDING;
+        break;
+      case "visitCount":
+        sortingMode = aDirection == "descending" ?
+          NHQO.SORT_BY_VISITCOUNT_DESCENDING : NHQO.SORT_BY_VISITCOUNT_ASCENDING;
+        break;
+      default:
+        throw("Invalid Column");
+    }
+    result.sortingAnnotation = sortingAnnotation;
+    result.sortingMode = sortingMode;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/suite/common/history/placesOverlay.xul
@@ -0,0 +1,130 @@
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is mozilla.org code.
+   -
+   - The Initial Developer of the Original Code is
+   - Netscape Communications Corporation.
+   - Portions created by the Initial Developer are Copyright (C) 1998
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Ben Goodger <beng@google.com>
+   -   Asaf Romano <mano@mozilla.com>
+   -   Robert Kaiser <kairo@kairo.at>
+   -
+   - 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 LGPL or the GPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE overlay [
+<!ENTITY % historyDTD SYSTEM "chrome://communicator/locale/history/history.dtd">
+%historyDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+]>
+
+<overlay id="placesOverlay"
+         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/x-javascript"
+          src="chrome://global/content/globalOverlay.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/utils.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/controller.js"/>
+  <script type="application/x-javascript"
+          src="chrome://communicator/content/history/treeView.js"/>
+  <script type="application/x-javascript"
+          src="chrome://global/content/nsDragAndDrop.js"/>
+
+  <commandset id="placesCommands"
+              commandupdater="true"
+              events="focus,sort"
+              oncommandupdate="goUpdatePlacesCommands();">
+    <command id="placesCmd_open"
+             oncommand="goDoCommand('placesCmd_open');"/>
+    <command id="placesCmd_open:window"
+             oncommand="goDoCommand('placesCmd_open:window');"/>
+    <command id="placesCmd_open:tab"
+             oncommand="goDoCommand('placesCmd_open:tab');"/>
+    <command id="placesCmd_sortBy:name"
+             oncommand="goDoCommand('placesCmd_sortBy:name');"/>
+  </commandset>
+
+  <popup id="placesContext"
+         onpopupshowing="this._view = PlacesUIUtils.getViewForNode(document.popupNode);
+                         return this._view.buildContextMenu(this);"
+         onpopuphiding="this._view.destroyContextMenu();">
+    <menuitem id="placesContext_open"
+              command="placesCmd_open"
+              label="&cmd.open.label;"
+              accesskey="&cmd.open.accesskey;"
+              default="true"
+              selectiontype="single"
+              selection="link"/>
+    <menuitem id="placesContext_open:newwindow"
+              command="placesCmd_open:window"
+              label="&cmd.open_window.label;"
+              accesskey="&cmd.open_window.accesskey;"
+              selectiontype="single"
+              selection="link"/>
+    <menuitem id="placesContext_open:newtab"
+              command="placesCmd_open:tab"
+              label="&cmd.open_tab.label;"
+              accesskey="&cmd.open_tab.accesskey;"
+              selectiontype="single"
+              selection="link"/>
+    <menuitem id="placesContext_openLinks:tabs"
+              oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
+                         view.controller.openSelectionInTabs(event);"
+              onclick="checkForMiddleClick(this, event);"
+              label="&cmd.open_all_in_tabs.label;"
+              accesskey="&cmd.open_all_in_tabs.accesskey;"
+              selectiontype="multiple"
+              selection="link"/>
+    <menuseparator id="placesContext_openSeparator"/>
+    <menuitem id="addBookmarkContextItem"
+              label="&bookmarkCmd.label;"
+              accesskey="&bookmarkCmd.accesskey;"
+              selection="link"
+              selectiontype="single"
+              oncommand="historyAddBookmarks();"/>
+    <menuitem id="placesContext_copy"
+              command="cmd_copy"
+              label="&copyCmd.label;"
+              closemenu="single"
+              accesskey="&copyCmd.accesskey;"
+              selection="any"/>
+    <menuseparator id="placesContext_editSeparator"/>
+    <menuitem id="placesContext_delete"
+              command="cmd_delete"
+              label="&deleteCmd.label;"
+              accesskey="&deleteCmd.accesskey;"
+              closemenu="single"
+              selection="any"
+              forcehideselection="livemarkChild"/>
+  </popup>
+
+</overlay>
new file mode 100644
--- /dev/null
+++ b/suite/common/history/sidebarUtils.js
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Places Organizer.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005-2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dan Mills <thunder@mozilla.com> (Ported from history-panel.js)
+ *   Marco Bonardo <mak77@supereva.it>
+ *   Robert Kaiser <kairo@kairo.at>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var SidebarUtils = {
+  handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) {
+    // right-clicks are not handled here
+    if (aEvent.button == 2)
+      return;
+
+    var tbo = aTree.treeBoxObject;
+    var row = { }, col = { }, obj = { };
+    tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
+
+    if (row.value == -1 || obj.value == "twisty")
+      return;
+
+    var mouseInGutter = false;
+    if (aGutterSelect) {
+      var x = { }, y = { }, w = { }, h = { };
+      tbo.getCoordsForCellItem(row.value, col.value, "image",
+                               x, y, w, h);
+      mouseInGutter = aEvent.clientX < x.value;
+    }
+
+    var openWhere = whereToOpenLink(aEvent);
+
+    var isContainer = tbo.view.isContainer(row.value);
+    var openInTabs = isContainer &&
+                     (openWhere == "tab" || openWhere == "tabshifted") &&
+                     PlacesUtils.hasChildURIs(tbo.view.nodeForTreeIndex(row.value));
+
+    if (aEvent.button == 0 && isContainer && !openInTabs) {
+      tbo.view.toggleOpenState(row.value);
+      return;
+    }
+    else if (!mouseInGutter && openInTabs &&
+            aEvent.originalTarget.localName == "treechildren") {
+      tbo.view.selection.select(row.value);
+      PlacesUIUtils.openContainerNodeInTabs(aTree.selectedNode, aEvent);
+    }
+    else if (!mouseInGutter && !isContainer &&
+             aEvent.originalTarget.localName == "treechildren") {
+      // Clear all other selection since we're loading a link now. We must
+      // do this *before* attempting to load the link since openURL uses
+      // selection as an indication of which link to load.
+      tbo.view.selection.select(row.value);
+      PlacesUIUtils.openNodeWithEvent(aTree.selectedNode, aEvent);
+    }
+  },
+
+  handleTreeKeyPress: function SU_handleTreeKeyPress(aEvent) {
+    if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+      PlacesUIUtils.openNodeWithEvent(aEvent.target.selectedNode, aEvent);
+  },
+
+  /**
+   * The following function displays the URL of a node that is being
+   * hovered over.
+   */
+  handleTreeMouseMove: function SU_handleTreeMouseMove(aEvent) {
+    if (aEvent.target.localName != "treechildren")
+      return;
+
+    var tree = aEvent.target.parentNode;
+    var tbo = tree.treeBoxObject;
+    var row = { }, col = { }, obj = { };
+    tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, col, obj);
+
+    // row.value is -1 when the mouse is hovering an empty area within the tree.
+    // To avoid showing a URL from a previously hovered node,
+    // for a currently hovered non-url node, we must clear the URL from the
+    // status bar in these cases.
+    if (row.value != -1) {
+      var cell = tree.view.nodeForTreeIndex(row.value);
+      if (PlacesUtils.nodeIsURI(cell))
+        window.top.XULBrowserWindow.setOverLink(cell.uri, null);
+      else
+        this.clearURLFromStatusBar();
+    }
+    else
+      this.clearURLFromStatusBar();
+  },
+
+  clearURLFromStatusBar: function SU_clearURLFromStatusBar() {
+    window.top.XULBrowserWindow.setOverLink("", null);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/suite/common/history/tree.xml
@@ -0,0 +1,360 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is mozilla.org code.
+   -
+   - The Initial Developer of the Original Code is
+   - Netscape Communications Corporation.
+   - Portions created by the Initial Developer are Copyright (C) 1998
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Ben Goodger <beng@google.com>
+   -   Annie Sullivan <annie.sullivan@gmail.com>
+   -   Marco Bonardo <mak77@bonardo.net>
+   -
+   - 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 LGPL or the GPL. 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 ***** -->
+
+<bindings id="placesTreeBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:html="http://www.w3.org/1999/xhtml"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
+    <implementation>
+      <constructor><![CDATA[
+        // Force an initial build.
+        if (this.place)
+          this.place = this.place;
+      ]]></constructor>
+
+      <destructor><![CDATA[
+        // Break the treeviewer->result->treeviewer cycle.
+        // Note: unsetting the result's viewer also unsets
+        // the viewer's reference to our treeBoxObject.
+        var result = this.getResult();
+        if (result)
+          result.viewer = null;
+        this.view = null;
+      ]]></destructor>
+
+      <property name="controller"
+                readonly="true"
+                onget="return this._controller;"/>
+
+      <!-- overriding -->
+      <property name="view">
+        <getter><![CDATA[
+          return this.treeBoxObject.view
+            .QueryInterface(Components.interfaces.nsINavHistoryResultTreeViewer);
+        ]]></getter>
+        <setter><![CDATA[
+          return this.treeBoxObject.view = val;
+        ]]></setter>
+      </property>
+
+      <method name="applyFilter">
+        <parameter name="filterString"/>
+        <parameter name="folderRestrict"/>
+        <body><![CDATA[
+          // preserve grouping
+          var queryNode = asQuery(this.getResultNode());
+          var options = queryNode.queryOptions.clone();
+
+          var query = PlacesUtils.history.getNewQuery();
+          query.searchTerms = filterString;
+
+          if (folderRestrict) {
+            query.setFolders(folderRestrict, folderRestrict.length);
+            options.queryType = options.QUERY_TYPE_BOOKMARKS;
+          }
+
+          this.load([query], options);
+        ]]></body>
+      </method>
+
+      <method name="load">
+        <parameter name="queries"/>
+        <parameter name="options"/>
+        <body><![CDATA[
+          var result = PlacesUtils.history.executeQueries(queries, queries.length,
+                                                          options);
+
+          var treeView = new PlacesTreeView(this.flatList);
+          result.viewer = treeView;
+          this.view = treeView;
+          if (!this._controller) {
+            this._controller = new PlacesController(this);
+            this.controllers.appendController(this._controller);
+          }
+          this._cachedInsertionPoint = undefined;
+        ]]></body>
+      </method>
+
+      <property name="flatList">
+        <getter><![CDATA[
+          return this.getAttribute("flatList") == "true";
+        ]]></getter>
+        <setter><![CDATA[
+          if (this.flatList != val) {
+            this.setAttribute("flatList", val);
+            // reload with the last place set
+            if (this.place)
+              this.place = this.place;
+          }
+          return val;
+        ]]></setter>
+      </property>
+
+      <property name="onOpenFlatContainer">
+        <getter><![CDATA[
+          return this.getAttribute("onopenflatcontainer");
+        ]]></getter>
+        <setter><![CDATA[
+          if (this.onOpenFlatContainer != val) {
+            this.setAttribute("onopenflatcontainer", val);
+            // reload with the last place set
+            if (this.place)
+              this.place = this.place;
+          }
+          return val;
+        ]]></setter>
+      </property>
+
+      <!-- nsIPlacesView -->
+      <method name="getResult">
+        <body><![CDATA[
+          try {
+            return this.view.QueryInterface(Components.interfaces.nsINavHistoryResultViewer).result;
+          }
+          catch (e) {
+            return null;
+          }
+        ]]></body>
+      </method>
+
+      <!-- nsIPlacesView -->
+      <method name="getResultNode">
+        <body><![CDATA[
+          return this.getResult().root;
+        ]]></body>
+      </method>
+
+      <method name="getResultView">
+        <body><![CDATA[
+          try {
+            return this.view.QueryInterface(Components.interfaces.nsINavHistoryResultTreeViewer);
+          }
+          catch (e) {
+          }
+          return null;
+        ]]></body>
+      </method>
+
+      <!-- nsIPlacesView -->
+      <property name="place">
+        <getter><![CDATA[
+          return this.getAttribute("place");
+        ]]></getter>
+        <setter><![CDATA[
+          this.setAttribute("place", val);
+
+          var queriesRef = { };
+          var queryCountRef = { };
+          var optionsRef = { };
+          PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
+          if (queryCountRef.value == 0)
+            queriesRef.value = [PlacesUtils.history.getNewQuery()];
+          if (!optionsRef.value)
+            optionsRef.value = PlacesUtils.history.getNewQueryOptions();
+
+          this.load(queriesRef.value, optionsRef.value);
+
+          return val;
+        ]]></setter>
+      </property>
+
+      <!-- nsIPlacesView -->
+      <property name="hasSelection">
+        <getter><![CDATA[
+          return this.view.selection.count >= 1;
+        ]]></getter>
+      </property>
+
+      <!-- nsIPlacesView -->
+      <method name="getSelectionNodes">
+        <body><![CDATA[
+          var selection = this.view.selection;
+          var rc = selection.getRangeCount();
+          var nodes = [];
+          var resultview = this.getResultView();
+          for (var i = 0; i < rc; ++i) {
+            var min = { }, max = { };
+            selection.getRangeAt(i, min, max);
+
+            for (var j = min.value; j <= max.value; ++j)
+              nodes.push(resultview.nodeForTreeIndex(j));
+          }
+          return nodes;
+        ]]></body>
+      </method>
+
+      <!-- nsIPlacesView -->
+      <method name="getDragableSelection">
+        <body><![CDATA[
+          return this.getSelectionNodes();
+        ]]></body>
+      </method>
+
+      <!-- nsIPlacesView -->
+      <property name="selectedNode">
+        <getter><![CDATA[
+          var view = this.view;
+          if (view.selection.count != 1)
+            return null;
+
+          var selection = view.selection;
+          var min = { }, max = { };
+          selection.getRangeAt(0, min, max);
+
+          return this.getResultView().nodeForTreeIndex(min.value);
+        ]]></getter>
+      </property>
+
+      <!-- nsIPlacesView -->
+      <property name="insertionPoint">
+        <getter><![CDATA[
+          // there is no insertion point for history queries
+          // so just bail out
+          return this._cachedInsertionPoint = null;
+        ]]></getter>
+      </property>
+
+      <!-- nsIPlacesView -->
+      <method name="selectAll">
+        <body><![CDATA[
+          this.view.selection.selectAll();
+        ]]></body>
+      </method>
+
+      <!-- nsDragAndDrop -->
+      <method name="onDragStart">
+        <parameter name="aEvent"/>
+        <parameter name="aXferData"/>
+        <parameter name="aDragAction"/>
+        <body><![CDATA[
+          var nodes = this.getSelectionNodes();
+          for (var i = 0; i < nodes.length; ++i) {
+            var node = nodes[i];
+
+            // Disallow dragging the root node of a tree
+            var parent = node.parent;
+            if (!parent)
+              return;
+
+            // History nodes can always be dragged off
+          }
+
+          // Stuff the encoded selection into the transferable data object
+          this._controller.setDataTransfer(aEvent);
+        ]]></body>
+      </method>
+
+      <!-- nsDragAndDrop -->
+      <method name="canDrop">
+        <parameter name="aEvent"/>
+        <parameter name="aDragSession"/>
+        <body><![CDATA[
+          // compat to general places tree but you never can drop on history
+          return false
+        ]]></body>
+      </method>
+
+      <!-- nsDragAndDrop -->
+      <method name="onDragOver">
+        <parameter name="aEvent"/>
+        <parameter name="aFlavour"/>
+        <parameter name="aDragSession"/>
+        <body><![CDATA[
+          if (!this.canDrop(aEvent, aDragSession))
+            aEvent.dataTransfer.effectAllowed = "none";
+        ]]></body>
+      </method>
+
+      <!-- nsDragAndDrop -->
+      <method name="getSupportedFlavours">
+        <body><![CDATA[
+          // nothing can be dropped on history, actually.
+          return [];
+        ]]></body>
+      </method>
+
+      <method name="buildContextMenu">
+        <parameter name="aPopup"/>
+        <body><![CDATA[
+          return this.controller.buildContextMenu(aPopup);
+        ]]></body>
+      </method>
+
+      <method name="destroyContextMenu">
+        <parameter name="aPopup"/>
+        <body/>
+      </method>
+    </implementation>
+    <handlers>
+      <handler event="focus"><![CDATA[
+        this._cachedInsertionPoint = undefined;
+
+        // See select handler. We need the sidebar's places commandset to be
+        // updated as well
+        document.commandDispatcher.updateCommands("focus");
+      ]]></handler>
+      <handler event="select"><![CDATA[
+        this._cachedInsertionPoint = undefined;
+
+        // This additional complexity is here for the sidebars
+        var win = window;
+        while (true) {
+          win.document.commandDispatcher.updateCommands("focus");
+          if (win == window.top)
+            break;
+
+          win = win.parent;
+        }
+      ]]></handler>
+      <handler event="draggesture"><![CDATA[
+        if (event.target.localName == "treechildren")
+          nsDragAndDrop.startDrag(event, this);
+      ]]></handler>
+      <handler event="dragover"><![CDATA[
+        if (event.target.localName == "treechildren")
+          nsDragAndDrop.dragOver(event, this);
+      ]]></handler>
+    </handlers>
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/suite/common/history/treeView.js
@@ -0,0 +1,1144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla History System
+ *
+ * The Initial Developer of the Original Code is
+ * Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brett Wilson <brettw@gmail.com> (original author)
+ *   Asaf Romano <mano@mozilla.com> (Javascript version)
+ *
+ * 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 ***** */
+
+PlacesTreeView.prototype = {
+  _makeAtom: function PTV__makeAtom(aString) {
+    return  Components.classes["@mozilla.org/atom-service;1"]
+                      .getService(Components.interfaces.nsIAtomService)
+                      .getAtom(aString);
+  },
+
+  _atoms: [],
+  _getAtomFor: function PTV__getAtomFor(aName) {
+    if (!this._atoms[aName])
+      this._atoms[aName] = this._makeAtom(aName);
+
+    return this._atoms[aName];
+  },
+
+  _ensureValidRow: function PTV__ensureValidRow(aRow) {
+    if (aRow < 0 || aRow >= this._visibleElements.length)
+      throw Components.results.NS_ERROR_INVALID_ARG;
+  },
+
+  __dateService: null,
+  get _dateService() {
+    if (!this.__dateService) {
+      this.__dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+                                     .getService(Components.interfaces.nsIScriptableDateFormat);
+    }
+    return this.__dateService;
+  },
+
+  QueryInterface: function PTV_QueryInterface(aIID) {
+    if (aIID.equals(Components.interfaces.nsITreeView) ||
+        aIID.equals(Components.interfaces.nsINavHistoryResultViewer) ||
+        aIID.equals(Components.interfaces.nsINavHistoryResultTreeViewer) ||
+        aIID.equals(Components.interfaces.nsISupports))
+      return this;
+
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+
+  /**
+   * This is called when the result or tree may have changed.
+   * It reinitializes everything. Result and/or tree can be null
+   * when calling.
+   */
+  _finishInit: function PTV__finishInit() {
+    if (this._tree && this._result)
+      this.sortingChanged(this._result.sortingMode);
+
+    var qoInt = Components.interfaces.nsINavHistoryQueryOptions;
+    var options = asQuery(this._result.root).queryOptions;
+
+    // if there is no tree, BuildVisibleList will clear everything for us
+    this._buildVisibleList();
+  },
+
+  _computeShowSessions: function PTV__computeShowSessions() {
+    NS_ASSERT(this._result, "Must have a result to show sessions!");
+    this._showSessions = false;
+
+    var options = asQuery(this._result.root).queryOptions;
+    NS_ASSERT(options, "navHistoryResults must have valid options");
+
+    if (!options.showSessions)
+      return; // sessions are off
+
+    var resultType = options.resultType;
+    if (resultType != Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_VISIT &&
+        resultType != Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_FULL_VISIT)
+      return; // not visits
+
+    var sortType = this._result.sortingMode;
+    if (sortType != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
+        sortType != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
+      return; // not date sorting
+
+    this._showSessions = true;
+  },
+
+  SESSION_STATUS_NONE: 0,
+  SESSION_STATUS_START: 1,
+  SESSION_STATUS_CONTINUE: 2,
+  _getRowSessionStatus: function PTV__getRowSessionStatus(aRow) {
+    var node = this._visibleElements[aRow].node;
+    if (!PlacesUtils.nodeIsVisit(node) || asVisit(node).sessionId == 0)
+      return this.SESSION_STATUS_NONE;
+
+    if (aRow == 0)
+      return this.SESSION_STATUS_START;
+
+    var previousNode = this._visibleElements[aRow - 1].node;
+    if (!PlacesUtils.nodeIsVisit(previousNode) ||
+        node.sessionId != asVisit(previousNode).sessionId)
+      return this.SESSION_STATUS_START;
+
+    return this.SESSION_STATUS_CONTINUE;
+  },
+
+  /**
+   * Call to completely rebuild the list of visible items. Note if there is no
+   * tree or root this will just clear out the list, so you can also call this
+   * when a tree is detached to clear the list.
+   */
+  _buildVisibleList: function PTV__buildVisibleList() {
+    var selection = this.selection;
+    if (selection)
+      selection.selectEventsSuppressed = true;
+
+    if (this._result) {
+      // Any current visible elements need to be marked as invisible.
+      for (var i = 0; i < this._visibleElements.length; i++) {
+        this._visibleElements[i].node.viewIndex = -1;
+      }
+    }
+
+    var rootNode = this._result.root;
+    if (rootNode && this._tree) {
+      this._computeShowSessions();
+
+      asContainer(rootNode);
+      if (!rootNode.containerOpen) {
+        // this triggers containerOpened which then builds the visible
+        // section
+        rootNode.containerOpen = true;
+      }
+      else
+        this.invalidateContainer(rootNode);
+    }
+    if (selection)
+      selection.selectEventsSuppressed = false;
+  },
+
+  /**
+   * This takes a container and recursively appends visible elements to the
+   * given array. This is used to build the visible element list (with
+   * this._visibleElements passed as the array), or portions thereof (with
+   * a separate array that is merged with the main list later.
+   *
+   * aVisibleStartIndex is the visible index of the beginning of the 'aVisible'
+   * array. When aVisible is this._visibleElements, this is 0. This is non-zero
+   * when we are building up a sub-region for insertion. Then, this is the
+   * index where the new array will be inserted into this._visibleElements.
+   * It is used to compute each node's viewIndex.
+   */
+  _buildVisibleSection:
+  function PTV__buildVisibleSection(aContainer, aVisible, aToOpen, aVisibleStartIndex)
+  {
+    if (!aContainer.containerOpen)
+      return;  // nothing to do
+
+    const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
+    const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+
+    var cc = aContainer.childCount;
+    for (var i=0; i < cc; i++) {
+      var curChild = aContainer.getChild(i);
+      var curChildType = curChild.type;
+
+      // add item
+      curChild.viewIndex = aVisibleStartIndex + aVisible.length;
+      aVisible.push({ node: curChild, properties: null });
+
+      // recursively do containers
+      if (!this._flatList && PlacesUtils.containerTypes.indexOf(curChildType) != -1) {
+        asContainer(curChild);
+
+        var resource = this._getResourceForNode(curChild);
+        var isopen = resource != null &&
+                     PlacesUIUtils.localStore.HasAssertion(resource, openLiteral,
+                                                           trueLiteral, true);
+        if (isopen != curChild.containerOpen)
+          aToOpen.push(curChild);
+        else if (curChild.containerOpen && curChild.childCount > 0)
+          this._buildVisibleSection(curChild, aVisible, aToOpen, aVisibleStartIndex);
+      }
+    }
+  },
+
+  /**
+   * This counts how many rows an item takes in the tree, that is, the
+   * item itself plus any nodes following it with an increased indent.
+   * This allows you to figure out how many rows an item (=1) or a
+   * container with all of its children takes.
+   */
+  _countVisibleRowsForItem: function PTV__countVisibleRowsForItem(aNode) {
+    if (aNode == this._result.root)
+      return this._visibleElements.length;
+
+    var viewIndex = aNode.viewIndex;
+    NS_ASSERT(viewIndex >= 0, "Item is not visible, no rows to count");
+    var outerLevel = aNode.indentLevel;
+    for (var i = viewIndex + 1; i < this._visibleElements.length; i++) {
+      if (this._visibleElements[i].node.indentLevel <= outerLevel)
+        return i - viewIndex;
+    }
+    // this node plus its children occupy the bottom of the list
+    return this._visibleElements.length - viewIndex;
+  },
+
+  /**
+   * This is called by containers when they change and we need to update
+   * everything about the container. We build a new visible section with
+   * the container as a separate object so we first know how the list
+   * changes. This way we only have to do one realloc/memcpy to update
+   * the list.
+   *
+   * We also try to be smart here about redrawing the screen.
+   */
+  _refreshVisibleSection: function PTV__refreshVisibleSection(aContainer) {
+    NS_ASSERT(this._result, "Need to have a result to update");
+    if (!this._tree)
+      return;
+
+    // aContainer must be visible
+    if (aContainer != this._result.root) {
+      if (aContainer.viewIndex < 0 ||
+          aContainer.viewIndex > this._visibleElements.length)
+        throw "Trying to expand a node that is not visible";
+
+      NS_ASSERT(this._visibleElements[aContainer.viewIndex].node == aContainer,
+                "Visible index is out of sync!");
+    }
+
+    var startReplacement = aContainer.viewIndex + 1;
+    var replaceCount = this._countVisibleRowsForItem(aContainer);
+
+    // We don't replace the container item itself so we decrease the
+    // replaceCount by 1. We don't do so though if there is no visible item
+    // for the container. This happens when aContainer is the root node
+    if (aContainer.viewIndex != -1)
+      replaceCount-=1;
+
+    // Persist selection state
+    var previouslySelectedNodes = [];
+    var selection = this.selection;
+    var rc = selection.getRangeCount();
+    for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
+      var min = { }, max = { };
+      selection.getRangeAt(rangeIndex, min, max);
+      var lastIndex = Math.min(max.value, startReplacement + replaceCount -1);
+      if (min.value < startReplacement || min.value > lastIndex)
+        continue;
+
+      for (var nodeIndex = min.value; nodeIndex <= lastIndex; nodeIndex++)
+        previouslySelectedNodes.push(
+          { node: this._visibleElements[nodeIndex].node, oldIndex: nodeIndex });
+    }
+
+    // Mark the removes as invisible
+    for (var i = 0; i < replaceCount; i++)
+      this._visibleElements[startReplacement + i].node.viewIndex = -1;
+
+    // Building the new list will set the new elements' visible indices.
+    var newElements = [];
+    var toOpenElements = [];
+    this._buildVisibleSection(aContainer, newElements, toOpenElements, startReplacement);
+
+    // actually update the visible list
+    this._visibleElements =
+      this._visibleElements.slice(0, startReplacement).concat(newElements)
+          .concat(this._visibleElements.slice(startReplacement + replaceCount,
+                                              this._visibleElements.length));
+
+    // If the new area has a different size, we'll have to renumber the
+    // elements following the area.
+    if (replaceCount != newElements.length) {
+      for (i = startReplacement + newElements.length;
+           i < this._visibleElements.length; i ++) {
+        this._visibleElements[i].node.viewIndex = i;
+      }
+    }
+
+    // now update the number of elements
+    selection.selectEventsSuppressed = true;
+    this._tree.beginUpdateBatch();
+
+    if (replaceCount)
+      this._tree.rowCountChanged(startReplacement, -replaceCount);
+    if (newElements.length)
+      this._tree.rowCountChanged(startReplacement, newElements.length);
+
+    if (!this._flatList) {
+      // now, open any containers that were persisted
+      for (var i = 0; i < toOpenElements.length; i++) {
+        var item = toOpenElements[i];
+        var parent = item.parent;
+        // avoid recursively opening containers
+        while (parent) {
+          if (parent.uri == item.uri)
+            break;
+          parent = parent.parent;
+        }
+        // if we don't have a parent, we made it all the way to the root
+        // and didn't find a match, so we can open our item
+        if (!parent && !item.containerOpen)
+          item.containerOpen = true;
+      }
+    }
+
+    this._tree.endUpdateBatch();
+
+    // restore selection
+    if (previouslySelectedNodes.length > 0) {
+      for (var i = 0; i < previouslySelectedNodes.length; i++) {
+        var nodeInfo = previouslySelectedNodes[i];
+        var index = nodeInfo.node.viewIndex;
+
+        // if the same node was used (happens on sorting-changes),
+        // just use viewIndex
+        if (index == -1) { // otherwise, try to find an equal node
+          var itemId = PlacesUtils.getConcreteItemId(nodeInfo.node);
+          if (itemId != 1) { // bookmark-nodes in queries case
+            for (var j = 0; j < newElements.length && index == -1; j++) {
+              if (PlacesUtils.getConcreteItemId(newElements[j]) == itemId)
+                index = newElements[j].viewIndex;
+            }
+          }
+          else { // history nodes
+            var uri = nodeInfo.node.uri;
+            if (uri) {
+              for (var j = 0; j < newElements.length && index == -1; j++) {
+                if (newElements[j].uri == uri)
+                  index = newElements[j].viewIndex;
+              }
+            }
+          }
+        }
+        if (index != -1)
+          selection.rangedSelect(index, index, true);
+      }
+
+      // if only one node was previously selected and there's no selection now,
+      // select the node at its old-viewIndex, if any
+      if (previouslySelectedNodes.length == 1 &&
+          selection.getRangeCount() == 0 &&
+          this._visibleElements.length > previouslySelectedNodes[0].oldIndex) {
+        selection.rangedSelect(previouslySelectedNodes[0].oldIndex,
+                               previouslySelectedNodes[0].oldIndex, true);
+      }
+    }
+    selection.selectEventsSuppressed = false;
+  },
+
+  _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
+    var timeInMilliseconds = aTime / 1000; // PRTime is in microseconds
+    var timeObj = new Date(timeInMilliseconds);
+
+    // Check if it is today and only display the time.  Only bother
+    // checking for today if it's within the last 24 hours, since
+    // computing midnight is not really cheap. Sometimes we may get dates
+    // in the future, so always show those.
+    var ago = new Date(Date.now() - timeInMilliseconds);
+    var dateFormat = Components.interfaces.nsIScriptableDateFormat.dateFormatShort;
+    if (ago > -10000 && ago < (1000 * 24 * 60 * 60)) {
+      var midnight = new Date(timeInMilliseconds);
+      midnight.setHours(0);
+      midnight.setMinutes(0);
+      midnight.setSeconds(0);
+      midnight.setMilliseconds(0);
+
+      if (timeInMilliseconds > midnight.getTime())
+        dateFormat = Components.interfaces.nsIScriptableDateFormat.dateFormatNone;
+    }
+
+    return (this._dateService.FormatDateTime("", dateFormat,
+      Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
+      timeObj.getFullYear(), timeObj.getMonth() + 1,
+      timeObj.getDate(), timeObj.getHours(),
+      timeObj.getMinutes(), timeObj.getSeconds()));
+  },
+
+  COLUMN_TYPE_UNKNOWN: 0,
+  COLUMN_TYPE_TITLE: 1,
+  COLUMN_TYPE_URI: 2,
+  COLUMN_TYPE_DATE: 3,
+  COLUMN_TYPE_VISITCOUNT: 4,
+
+  _getColumnType: function PTV__getColumnType(aColumn) {
+    var columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
+
+    switch (columnType) {
+      case "title":
+        return this.COLUMN_TYPE_TITLE;
+      case "url":
+        return this.COLUMN_TYPE_URI;
+      case "date":
+        return this.COLUMN_TYPE_DATE;
+      case "visitCount":
+        return this.COLUMN_TYPE_VISITCOUNT;
+    }
+    return this.COLUMN_TYPE_UNKNOWN;
+  },
+
+  _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) {
+    switch (aSortType) {
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING:
+        return [this.COLUMN_TYPE_TITLE, false];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
+        return [this.COLUMN_TYPE_TITLE, true];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
+        return [this.COLUMN_TYPE_DATE, false];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
+        return [this.COLUMN_TYPE_DATE, true];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
+        return [this.COLUMN_TYPE_URI, false];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
+        return [this.COLUMN_TYPE_URI, true];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
+        return [this.COLUMN_TYPE_VISITCOUNT, false];
+      case Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
+        return [this.COLUMN_TYPE_VISITCOUNT, true];
+    }
+    return [this.COLUMN_TYPE_UNKNOWN, false];
+  },
+
+  // nsINavHistoryResultViewer
+  itemInserted: function PTV_itemInserted(aParent, aItem, aNewIndex) {
+    if (!this._tree)
+      return;
+    if (!this._result)
+      throw Components.results.NS_ERROR_UNEXPECTED;
+
+    // update parent when inserting the first item because twisty may
+    // have changed
+    if (aParent.childCount == 1)
+      this.itemChanged(aParent);
+
+    // compute the new view index of the item
+    var newViewIndex = -1;
+    if (aNewIndex == 0) {
+      // item is the first thing in our child list, it takes our index +1. Note
+      // that this computation still works if the parent is an invisible root
+      // node, because root_index + 1 = -1 + 1 = 0
+      newViewIndex = aParent.viewIndex + 1;
+    }
+    else {
+      // Here, we try to find the next visible element in the child list so we
+      // can set the new visible index to be right before that. Note that we
+      // have to search DOWN instead of up, because some siblings could have
+      // children themselves that would be in the way.
+      for (var i = aNewIndex + 1; i < aParent.childCount; i ++) {
+        var viewIndex = aParent.getChild(i).viewIndex;
+        if (viewIndex >= 0) {
+          // the view indices of subsequent children have not been shifted so
+          // the next item will have what should be our index
+          newViewIndex = viewIndex;
+          break;
+        }
+      }
+      if (newViewIndex < 0) {
+        // At the end of the child list without finding a visible sibling: This
+        // is a little harder because we don't know how many rows the last item
+        // in our list takes up (it could be a container with many children).
+        var prevChild = aParent.getChild(aNewIndex - 1);
+        newViewIndex = prevChild.viewIndex + this._countVisibleRowsForItem(prevChild);
+      }
+    }
+
+    aItem.viewIndex = newViewIndex;
+    this._visibleElements.splice(newViewIndex, 0,
+                                 { node: aItem, properties: null });
+    for (var i = newViewIndex + 1;
+         i < this._visibleElements.length; i ++) {
+      this._visibleElements[i].node.viewIndex = i;
+    }
+    this._tree.rowCountChanged(newViewIndex, 1);
+
+    // Need to redraw the rows around this one because session boundaries
+    // may have changed. For example, if we add a page to a session, the
+    // previous page will need to be redrawn because its session border
+    // will disappear.
+    if (this._showSessions) {
+      if (newViewIndex > 0)
+        this._tree.invalidateRange(newViewIndex - 1, newViewIndex - 1);
+      if (newViewIndex < this._visibleElements.length -1)
+        this._tree.invalidateRange(newViewIndex + 1, newViewIndex + 1);
+    }
+
+    if (PlacesUtils.nodeIsContainer(aItem) && asContainer(aItem).containerOpen)
+      this._refreshVisibleSection(aItem);
+  },
+
+  // this is used in itemRemoved and itemMoved to fix viewIndex values
+  // throw if the item has an invalid viewIndex
+  _fixViewIndexOnRemove: function PTV_fixViewIndexOnRemove(aItem, aParent) {
+    var oldViewIndex = aItem.viewIndex;
+    // this may have been a container, in which case it has a lot of rows
+    var count = this._countVisibleRowsForItem(aItem);
+
+    if (oldViewIndex > this._visibleElements.length)
+      throw("Trying to remove an item with an invalid viewIndex");
+
+    this._visibleElements.splice(oldViewIndex, count);
+    for (var i = oldViewIndex; i < this._visibleElements.length; i++)
+      this._visibleElements[i].node.viewIndex = i;
+
+    this._tree.rowCountChanged(oldViewIndex, -count);
+
+    // redraw parent because twisty may have changed
+    if (!aParent.hasChildren)
+      this.itemChanged(aParent);
+
+    return;
+  },
+
+  /**
+   * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being
+   * removed but the node it is collapsed with is not being removed (this then
+   * just swap out the removee with its collapsing partner). The only time
+   * when we really remove things is when deleting URIs, which will apply to
+   * all collapsees. This function is called sometimes when resorting items.
+   * However, we won't do this when sorted by date because dates will never
+   * change for visits, and date sorting is the only time things are collapsed.
+   */
+  itemRemoved: function PTV_itemRemoved(aParent, aItem, aOldIndex) {
+    NS_ASSERT(this._result, "Got a notification but have no result!");
+    if (!this._tree)
+      return; // nothing to do
+
+    var oldViewIndex = aItem.viewIndex;
+    if (oldViewIndex < 0)
+      return; // item was already invisible, nothing to do
+
+    // if the item was exclusively selected, the node next to it will be
+    // selected
+    var selectNext = false;
+    var selection = this.selection;
+    if (selection.getRangeCount() == 1) {
+      var min = { }, max = { };
+      selection.getRangeAt(0, min, max);
+      if (min.value == max.value &&
+          this.nodeForTreeIndex(min.value) == aItem)
+        selectNext = true;
+    }
+
+    // remove the item and fix viewIndex values
+    this._fixViewIndexOnRemove(aItem, aParent);
+
+    // restore selection if the item was exclusively selected
+    if (!selectNext)
+      return;
+    // restore selection
+    if (this._visibleElements.length > oldViewIndex)
+      selection.rangedSelect(oldViewIndex, oldViewIndex, true);
+    else if (this._visibleElements.length > 0) {
+      // if we removed the last child, we select the new last child if exists
+      selection.rangedSelect(this._visibleElements.length - 1,
+                             this._visibleElements.length - 1, true);
+    }
+  },
+
+  /**
+   * Be careful, aOldIndex and aNewIndex specify the index in the
+   * corresponding parent nodes, not the visible indexes.
+   */
+  itemMoved:
+  function PTV_itemMoved(aItem, aOldParent, aOldIndex, aNewParent, aNewIndex) {
+    NS_ASSERT(this._result, "Got a notification but have no result!");
+    if (!this._tree)
+      return; // nothing to do
+
+    var oldViewIndex = aItem.viewIndex;
+    if (oldViewIndex < 0)
+      return; // item was already invisible, nothing to do
+
+    // this may have been a container, in which case it has a lot of rows
+    var count = this._countVisibleRowsForItem(aItem);
+
+    // Persist selection state
+    var nodesToSelect = [];
+    var selection = this.selection;
+    var rc = selection.getRangeCount();
+    for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
+      var min = { }, max = { };
+      selection.getRangeAt(rangeIndex, min, max);
+      var lastIndex = Math.min(max.value, oldViewIndex + count -1);
+      if (min.value < oldViewIndex || min.value > lastIndex)
+        continue;
+
+      for (var nodeIndex = min.value; nodeIndex <= lastIndex; nodeIndex++)
+        nodesToSelect.push(this._visibleElements[nodeIndex].node);
+    }
+    if (nodesToSelect.length > 0)
+      selection.selectEventsSuppressed = true;
+
+    // remove item from the old position
+    this._fixViewIndexOnRemove(aItem, aOldParent);
+
+    // insert the item into the new position
+    this.itemInserted(aNewParent, aItem, aNewIndex);
+
+    // restore selection
+    if (nodesToSelect.length > 0) {
+      for (var i = 0; i < nodesToSelect.length; i++) {
+        var node = nodesToSelect[i];
+        var index = node.viewIndex;
+        selection.rangedSelect(index, index, true);
+      }
+      selection.selectEventsSuppressed = false;
+    }
+  },
+
+  /**
+   * Be careful, the parameter 'aIndex' here specifies the index in the parent
+   * node of the item, not the visible index.
+   *
+   * This is called from the result when the item is replaced, but this object
+   * calls this function internally also when duplicate collapsing changes. In
+   * this case, aIndex will be 0, so we should be careful not to use the value.
+   */
+  itemReplaced:
+  function PTV_itemReplaced(aParent, aOldItem, aNewItem, aIndexDoNotUse) {
+    if (!this._tree)
+      return;
+
+    var viewIndex = aOldItem.viewIndex;
+    aNewItem.viewIndex = viewIndex;
+    if (viewIndex >= 0 &&
+        viewIndex < this._visibleElements.length) {
+      this._visibleElements[viewIndex].node = aNewItem;
+      this._visibleElements[viewIndex].properties = null;
+    }
+    aOldItem.viewIndex = -1;
+    this._tree.invalidateRow(viewIndex);
+  },
+
+  itemChanged: function PTV_itemChanged(aItem) {
+    NS_ASSERT(this._result, "Got a notification but have no result!");
+    var viewIndex = aItem.viewIndex;
+    if (this._tree && viewIndex >= 0)
+      this._tree.invalidateRow(viewIndex);
+  },
+
+  containerOpened: function PTV_containerOpened(aItem) {
+    this.invalidateContainer(aItem);
+  },
+
+  containerClosed: function PTV_containerClosed(aItem) {
+    this.invalidateContainer(aItem);
+  },
+
+  invalidateContainer: function PTV_invalidateContainer(aItem) {
+    NS_ASSERT(this._result, "Got a notification but have no result!");
+    if (!this._tree)
+      return; // nothing to do, container is not visible
+    var viewIndex = aItem.viewIndex;
+    if (viewIndex >= this._visibleElements.length) {
+      // be paranoid about visible indices since others can change it
+      throw Components.results.NS_ERROR_UNEXPECTED;
+    }
+    this._refreshVisibleSection(aItem);
+  },
+
+  invalidateAll: function PTV_invalidateAll() {
+    NS_ASSERT(this._result, "Got message but don't have a result!");
+    if (!this._tree)
+      return;
+
+    var oldRowCount = this._visibleElements.length;
+
+    // update flat list to new contents
+    this._buildVisibleList();
+  },
+
+  sortingChanged: function PTV__sortingChanged(aSortingMode) {
+    if (!this._tree || !this._result)
+      return;
+
+    // depending on the sort mode, certain commands may be disabled
+    window.updateCommands("sort");
+
+    var columns = this._tree.columns;
+
+    // clear old sorting indicator
+    var sortedColumn = columns.getSortedColumn();
+    if (sortedColumn)
+      sortedColumn.element.removeAttribute("sortDirection");
+
+    // set new sorting indicator by looking through all columns for ours
+    if (aSortingMode == Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_NONE)
+      return;
+    var [desiredColumn, desiredIsDescending] =
+      this._sortTypeToColumnType(aSortingMode);
+    var colCount = columns.count;
+    for (var i = 0; i < colCount; i ++) {
+      var column = columns.getColumnAt(i);
+      if (this._getColumnType(column) == desiredColumn) {
+        // found our desired one, set
+        if (desiredIsDescending)
+          column.element.setAttribute("sortDirection", "descending");
+        else
+          column.element.setAttribute("sortDirection", "ascending");
+        break;
+      }
+    }
+  },
+
+  get result() {
+    return this._result;
+  },
+
+  set result(val) {
+    // some methods (e.g. getURLsFromContainer) temporarily null out the
+    // viewer when they do temporary changes to the view, this does _not_
+    // call setResult(null), but then, we're called again with the result
+    // object which is already set for this viewer. At that point,
+    // we should do nothing.
+    if (this._result != val) {
+      this._result = val;
+      this._finishInit();
+    }
+    return val;
+  },
+
+  nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) {
+    if (aIndex > this._visibleElements.length)
+      throw Components.results.NS_ERROR_INVALID_ARG;
+
+    return this._visibleElements[aIndex].node;
+  },
+
+  treeIndexForNode: function PTV_treeNodeForIndex(aNode) {
+    var viewIndex = aNode.viewIndex;
+    if (viewIndex < 0)
+      return Components.interfaces.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
+
+    NS_ASSERT(this._visibleElements[viewIndex].node == aNode,
+              "Node's visible index and array out of sync");
+    return viewIndex;
+  },
+
+  _getResourceForNode: function PTV_getResourceForNode(aNode)
+  {
+    var uri = aNode.uri;
+    NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
+    return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
+  },
+
+  // nsITreeView
+  get rowCount() {
+    return this._visibleElements.length;
+  },
+
+  get selection() {
+    return this._selection;
+  },
+
+  set selection(val) {
+    return this._selection = val;
+  },
+
+  getRowProperties: function PTV_getRowProperties(aRow, aProperties) {
+    this._ensureValidRow(aRow);
+
+    // Handle properties for session information.
+    if (!this._showSessions)
+      return;
+
+    var status = this._getRowSessionStatus(aRow);
+    switch (status) {
+      case this.SESSION_STATUS_NONE:
+        break;
+      case this.SESSION_STATUS_START:
+        aProperties.AppendElement(this._getAtomFor("session-start"));
+        break;
+      case this.SESSION_STATUS_CONTINUE:
+        aProperties.AppendElement(this._getAtomFor("session-continue"));
+        break
+    }
+  },
+
+  getCellProperties: function PTV_getCellProperties(aRow, aColumn, aProperties) {
+    this._ensureValidRow(aRow);
+
+    // for anonid-trees, we need to add the column-type manually
+    var columnType = aColumn.element.getAttribute("anonid");
+    if (columnType)
+      aProperties.AppendElement(this._getAtomFor(columnType));
+    else
+      var columnType = aColumn.id;
+
+    if (columnType != "title")
+      return;
+
+    var node = this._visibleElements[aRow].node;
+    var properties = this._visibleElements[aRow].properties;
+
+    if (!properties) {
+      properties = new Array();
+      var itemId = node.itemId;
+      var nodeType = node.type;
+      if (PlacesUtils.containerTypes.indexOf(nodeType) != -1) {
+        if (nodeType == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
+          properties.push(this._getAtomFor("query"));
+          if (PlacesUtils.nodeIsTagQuery(node))
+            properties.push(this._getAtomFor("tagContainer"));
+          else if (PlacesUtils.nodeIsDay(node))
+            properties.push(this._getAtomFor("dayContainer"));
+          else if (PlacesUtils.nodeIsHost(node))
+            properties.push(this._getAtomFor("hostContainer"));
+        }
+
+        if (itemId != -1) {
+          var oqAnno;
+          try {
+            oqAnno = PlacesUtils.annotations
+                                .getItemAnnotation(itemId,
+                                                   ORGANIZER_QUERY_ANNO);
+            properties.push(this._getAtomFor("OrganizerQuery_" + oqAnno));
+          }
+          catch (ex) { /* not a special query */ }
+        }
+      }
+
+      this._visibleElements[aRow].properties = properties;
+    }
+    for (var i = 0; i < properties.length; i++)
+      aProperties.AppendElement(properties[i]);
+  },
+
+  getColumnProperties: function(aColumn, aProperties) { },
+
+  isContainer: function PTV_isContainer(aRow) {
+    this._ensureValidRow(aRow);
+
+    var node = this._visibleElements[aRow].node;
+    if (PlacesUtils.nodeIsContainer(node)) {
+      // the root node is always expandable
+      if (!node.parent)
+        return true;
+
+      // Flat-lists may ignore expandQueries and other query options when
+      // they are asked to open a container.
+      if (this._flatList)
+        return true;
+
+      // treat non-expandable childless queries as non-containers
+      if (PlacesUtils.nodeIsQuery(node)) {
+        var parent = node.parent;
+        if((PlacesUtils.nodeIsQuery(parent) ||
+            PlacesUtils.nodeIsFolder(parent)) &&
+           !node.hasChildren)
+          return asQuery(parent).queryOptions.expandQueries;
+      }
+      return true;
+    }
+    return false;
+  },
+
+  isContainerOpen: function PTV_isContainerOpen(aRow) {
+    if (this._flatList)
+      return false;
+
+    this._ensureValidRow(aRow);
+    if (!PlacesUtils.nodeIsContainer(this._visibleElements[aRow].node))
+      throw Components.results.NS_ERROR_INVALID_ARG;
+
+    return this._visibleElements[aRow].node.containerOpen;
+  },
+
+  isContainerEmpty: function PTV_isContainerEmpty(aRow) {
+    if (this._flatList)
+      return true;
+
+    this._ensureValidRow(aRow);
+
+    if (!PlacesUtils.nodeIsContainer(this._visibleElements[aRow].node))
+      throw Components.results.NS_ERROR_INVALID_ARG;
+
+    return !this._visibleElements[aRow].node.hasChildren;
+  },
+
+  isSeparator: function PTV_isSeparator(aRow) {
+    this._ensureValidRow(aRow);
+    return PlacesUtils.nodeIsSeparator(this._visibleElements[aRow].node);
+  },
+
+  isSorted: function PTV_isSorted() {
+    return this._result.sortingMode !=
+           Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_NONE;
+  },
+
+  canDrop: function PTV_canDrop(aRow, aOrientation) {
+    // compat to general places tree, but you can never drop URLs into history
+    return false;
+  },
+
+  drop: function PTV_drop(aRow, aOrientation) {
+    // compat to general places tree, but you can never drop into history
+    return;
+  },
+
+  getParentIndex: function PTV_getParentIndex(aRow) {
+    this._ensureValidRow(aRow);
+    var parent = this._visibleElements[aRow].node.parent;
+    if (!parent || parent.viewIndex < 0)
+      return -1;
+
+    return parent.viewIndex;
+  },
+
+  hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) {
+    this._ensureValidRow(aRow);
+    if (aRow == this._visibleElements.length -1) {
+      // this is the last thing in the list -> no next sibling
+      return false;
+    }
+
+    var thisLevel = this._visibleElements[aRow].node.indentLevel;
+    for (var i = aAfterIndex + 1; i < this._visibleElements.length; ++i) {
+      var nextLevel = this._visibleElements[i].node.indentLevel;
+      if (nextLevel == thisLevel)
+        return true;
+      if (nextLevel < thisLevel)
+        break;
+    }
+    return false;
+  },
+
+  getLevel: function PTV_getLevel(aRow) {
+    this._ensureValidRow(aRow);
+
+    return this._visibleElements[aRow].node.indentLevel;
+  },
+
+  getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
+    this._ensureValidRow(aRow);
+
+    // only the title column has an image
+    if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
+      return "";
+
+    var node = this._visibleElements[aRow].node;
+    var icon = node.icon;
+    if (icon)
+      return icon.spec;
+    return "";
+  },
+
+  getProgressMode: function(aRow, aColumn) { },
+  getCellValue: function(aRow, aColumn) { },
+
+  getCellText: function PTV_getCellText(aRow, aColumn) {
+    this._ensureValidRow(aRow);
+
+    var node = this._visibleElements[aRow].node;
+    var columnType = this._getColumnType(aColumn);
+    switch (columnType) {
+      case this.COLUMN_TYPE_TITLE:
+        // normally, this is just the title, but we don't want empty items in
+        // the tree view so return a special string if the title is empty.
+        // Do it here so that callers can still get at the 0 length title
+        // if they go through the "result" API.
+        return PlacesUIUtils.getBestTitle(node);
+      case this.COLUMN_TYPE_TAGS:
+        return node.tags;
+      case this.COLUMN_TYPE_URI:
+        if (PlacesUtils.nodeIsURI(node))
+          return node.uri;
+        return "";
+      case this.COLUMN_TYPE_DATE:
+        if (node.time == 0 || !PlacesUtils.nodeIsURI(node)) {
+          // hosts and days shouldn't have a value for the date column.
+          // Actually, you could argue this point, but looking at the
+          // results, seeing the most recently visited date is not what
+          // I expect, and gives me no information I know how to use.
+          // Only show this for URI-based items.
+          return "";
+        }
+        if (this._getRowSessionStatus(aRow) != this.SESSION_STATUS_CONTINUE)
+          return this._convertPRTimeToString(node.time);
+        return "";
+      case this.COLUMN_TYPE_VISITCOUNT:
+        return node.accessCount;
+    }
+    return "";
+  },
+
+  setTree: function PTV_setTree(aTree) {
+    var hasOldTree = this._tree != null;
+    this._tree = aTree;
+
+    // do this before detaching from result when there is no tree.
+    // This ensures that the visible indices of the elements in the
+    // result have been set to -1
+    this._finishInit();
+
+    if (!aTree && hasOldTree && this._result) {
+      // detach from result when we are detaching from the tree.
+      // This breaks the reference cycle between us and the result.
+      this._result.viewer = null;
+    }
+  },
+
+  toggleOpenState: function PTV_toggleOpenState(aRow) {
+    if (!this._result)
+      throw Components.results.NS_ERROR_UNEXPECTED;
+    this._ensureValidRow(aRow);
+
+    var node = this._visibleElements[aRow].node;
+    if (!PlacesUtils.nodeIsContainer(node))
+      return; // not a container, nothing to do
+
+    var resource = this._getResourceForNode(node);
+    if (resource) {
+      const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
+      const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+
+      if (node.containerOpen)
+        PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
+      else
+        PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+    }
+
+    node.containerOpen = !node.containerOpen;
+  },
+
+  cycleHeader: function PTV_cycleHeader(aColumn) {
+    if (!this._result)
+      throw Components.results.NS_ERROR_UNEXPECTED;
+
+    var oldSort = this._result.sortingMode;
+    var oldSortingAnnotation = this._result.sortingAnnotation;
+    var newSort;
+    var newSortingAnnotation = "";
+    const NHQO = Components.interfaces.nsINavHistoryQueryOptions;
+    var columnType = this._getColumnType(aColumn);
+    switch (columnType) {
+      case this.COLUMN_TYPE_TITLE:
+        if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING)
+          newSort = NHQO.SORT_BY_TITLE_DESCENDING;
+        else
+          newSort = NHQO.SORT_BY_TITLE_ASCENDING;
+
+        break;
+      case this.COLUMN_TYPE_URI:
+        if (oldSort == NHQO.SORT_BY_URI_ASCENDING)
+          newSort = NHQO.SORT_BY_URI_DESCENDING;
+        else
+          newSort = NHQO.SORT_BY_URI_ASCENDING;
+
+        break;
+      case this.COLUMN_TYPE_DATE:
+        if (oldSort == NHQO.SORT_BY_DATE_ASCENDING)
+          newSort = NHQO.SORT_BY_DATE_DESCENDING;
+        else
+          newSort = NHQO.SORT_BY_DATE_ASCENDING;
+
+        break;
+      case this.COLUMN_TYPE_VISITCOUNT:
+        // visit count default is unusual because we sort by descending
+        // by default because you are most likely to be looking for
+        // highly visited sites when you click it
+        if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
+          newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
+        else
+          newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
+
+        break;
+      default:
+        throw Components.results.NS_ERROR_INVALID_ARG;
+    }
+    this._result.sortingAnnotation = newSortingAnnotation;
+    this._result.sortingMode = newSort;
+  },
+
+  isEditable: function PTV_isEditable(aRow, aColumn) {
+    // At this point we only support editing the title field.
+    if (aColumn.index != 0)
+      return false;
+
+    var node = this.nodeForTreeIndex(aRow);
+    if (!PlacesUtils.nodeIsReadOnly(node) &&
+        (PlacesUtils.nodeIsFolder(node) ||
+         (PlacesUtils.nodeIsBookmark(node) &&
+          !PlacesUtils.nodeIsLivemarkItem(node))))
+      return true;
+
+    return false;
+  },
+
+  setCellText: function PTV_setCellText(aRow, aColumn, aText) {
+    // we may only get here if the cell is editable
+    var node = this.nodeForTreeIndex(aRow);
+    if (node.title != aText) {
+      var txn = PlacesUIUtils.ptm.editItemTitle(node.itemId, aText);
+      PlacesUIUtils.ptm.doTransaction(txn);
+    }
+  },
+
+  selectionChanged: function() { },
+  cycleCell: function PTV_cycleCell(aRow, aColumn) { },
+  isSelectable: function(aRow, aColumn) { return false; },
+  performAction: function(aAction) { },
+  performActionOnRow: function(aAction, aRow) { },
+  performActionOnCell: function(aAction, aRow, aColumn) { }
+};
+
+function PlacesTreeView(aFlatList) {
+  this._tree = null;
+  this._result = null;
+  this._showSessions = false;
+  this._selection = null;
+  this._visibleElements = [];
+  this._flatList = aFlatList;
+}
new file mode 100644
--- /dev/null
+++ b/suite/common/history/utils.js
@@ -0,0 +1,432 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Places Command Controller.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Myk Melez <myk@mozilla.org>
+ *   Asaf Romano <mano@mozilla.com>
+ *   Sungjoon Steve Won <stevewon@gmail.com>
+ *   Dietrich Ayala <dietrich@mozilla.com>
+ *   Marco Bonardo <mak77@bonardo.net>
+ *
+ * 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 ***** */
+
+function LOG(str) {
+  dump("*** " + str + "\n");
+}
+
+__defineGetter__("PlacesUtils", function() {
+  delete this.PlacesUtils
+  var tmpScope = {};
+  Components.utils.import("resource://gre/modules/utils.js", tmpScope);
+  return this.PlacesUtils = tmpScope.PlacesUtils;
+});
+
+const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
+const DESCRIPTION_ANNO = "bookmarkProperties/description";
+const LMANNO_FEEDURI = "livemark/feedURI";
+const LMANNO_SITEURI = "livemark/siteURI";
+const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder";
+const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
+const ORGANIZER_LEFTPANE_VERSION = 4;
+
+#ifdef XP_MACOSX
+// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where we
+// really just want "\n".
+const NEWLINE= "\n";
+#else
+// On other platforms, the transferable system converts "\r\n" to "\n".
+const NEWLINE = "\r\n";
+#endif
+
+function QI_node(aNode, aIID) {
+  return aNode.QueryInterface(aIID);
+}
+function asVisit(aNode)    {
+  return QI_node(aNode, Components.interfaces.nsINavHistoryVisitResultNode);
+}
+function asFullVisit(aNode){
+  return QI_node(aNode, Components.interfaces.nsINavHistoryFullVisitResultNode);
+}
+function asContainer(aNode){
+  return QI_node(aNode, Components.interfaces.nsINavHistoryContainerResultNode);
+}
+function asQuery(aNode)    {
+  return QI_node(aNode, Components.interfaces.nsINavHistoryQueryResultNode);
+}
+
+var PlacesUIUtils = {
+  /**
+   * The Microsummary Service
+   */
+  get microsummaries() {
+    delete this.microsummaries;
+    return this.microsummaries = Components.classes["@mozilla.org/microsummary/service;1"]
+                                           .getService(Components.interfaces.nsIMicrosummaryService);
+  },
+
+  get RDF() {
+    delete this.RDF;
+    return this.RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
+                                .getService(Components.interfaces.nsIRDFService);
+  },
+
+  get localStore() {
+    delete this.localStore;
+    return this.localStore = this.RDF.GetDataSource("rdf:local-store");
+  },
+
+  get clipboard() {
+    delete this.clipboard;
+    return this.clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
+                                      .getService(Components.interfaces.nsIClipboard);
+  },
+
+  get URIFixup() {
+    delete this.URIFixup;
+    return this.URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
+                                     .getService(Components.interfaces.nsIURIFixup);
+  },
+
+  get ellipsis() {
+    delete this.ellipsis;
+    var pref = Components.classes["@mozilla.org/preferences-service;1"]
+                         .getService(Components.interfaces.nsIPrefBranch);
+    return this.ellipsis = pref.getComplexValue("intl.ellipsis",
+                                                Components.interfaces.nsIPrefLocalizedString).data;
+  },
+
+  /**
+   * Makes a URI from a spec, and do fixup
+   * @param   aSpec
+   *          The string spec of the URI
+   * @returns A URI object for the spec.
+   */
+  createFixedURI: function PU_createFixedURI(aSpec) {
+    return this.URIFixup.createFixupURI(aSpec, 0);
+  },
+
+  /**
+   * Wraps a string in a nsISupportsString wrapper
+   * @param   aString
+   *          The string to wrap
+   * @returns A nsISupportsString object containing a string.
+   */
+  _wrapString: function PU__wrapString(aString) {
+    var s = Components.classes["@mozilla.org/supports-string;1"]
+                      .createInstance(Components.interfaces.nsISupportsString);
+    s.data = aString;
+    return s;
+  },
+
+  /**
+   * String bundle helpers
+   */
+  get _bundle() {
+    const PLACES_STRING_BUNDLE_URI =
+        "chrome://communicator/locale/history/places.properties";
+    delete this._bundle;
+    return this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                    .getService(Components.interfaces.nsIStringBundleService)
+                                    .createBundle(PLACES_STRING_BUNDLE_URI);
+  },
+
+  getFormattedString: function PU_getFormattedString(key, params) {
+    return this._bundle.formatStringFromName(key, params, params.length);
+  },
+
+  getString: function PU_getString(key) {
+    return this._bundle.GetStringFromName(key);
+  },
+
+  /**
+   * Returns the closet ancestor places view for the given DOM node
+   * @param aNode
+   *        a DOM node
+   * @return the closet ancestor places view if exists, null otherwsie.
+   */
+  getViewForNode: function PU_getViewForNode(aNode) {
+    var node = aNode;
+
+    // the view for a <menu> of which its associated menupopup is a places view,
+    // is the menupopup
+    if (node.localName == "menu" && !node.node &&
+        node.firstChild.getAttribute("type") == "places")
+      return node.firstChild;
+
+    while (node) {
+      // XXXmano: Use QueryInterface(nsIPlacesView) once we implement it...
+      if (node.getAttribute("type") == "places")
+        return node;
+
+      node = node.parentNode;
+    }
+
+    return null;
+  },
+
+  /**
+   * By calling this before we visit a URL, we will use TRANSITION_TYPED
+   * as the transition for the visit to that URL (if we don't have a referrer).
+   * This is used when visiting pages from the history menu, history sidebar,
+   * url bar, url autocomplete results, and history searches from the places
+   * organizer.  If we don't call this, we'll treat those visits as
+   * TRANSITION_LINK.
+   */
+  markPageAsTyped: function PU_markPageAsTyped(aURL) {
+    PlacesUtils.history.QueryInterface(Components.interfaces.nsIBrowserHistory)
+               .markPageAsTyped(this.createFixedURI(aURL));
+  },
+
+  /**
+   * By calling this before we visit a URL, we will use TRANSITION_BOOKMARK
+   * as the transition for the visit to that URL (if we don't have a referrer).
+   * This is used when visiting pages from the bookmarks menu,
+   * personal toolbar, and bookmarks from within the places organizer.
+   * If we don't call this, we'll treat those visits as TRANSITION_LINK.
+   */
+  markPageAsFollowedBookmark: function PU_markPageAsFollowedBookmark(aURL) {
+    PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL));
+  },
+
+  /**
+   * Allows opening of javascript/data URI only if the given node is
+   * bookmarked (see bug 224521).
+   * @param aURINode
+   *        a URI node
+   * @return true if it's safe to open the node in the browser, false otherwise.
+   *
+   */
+  checkURLSecurity: function PU_checkURLSecurity(aURINode) {
+    if (!PlacesUtils.nodeIsBookmark(aURINode)) {
+      var uri = PlacesUtils._uri(aURINode.uri);
+      if (uri.schemeIs("javascript") || uri.schemeIs("data")) {
+        const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+        var brandShortName = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                       .getService(Components.interfaces.nsIStringBundleService)
+                                       .createBundle(BRANDING_BUNDLE_URI)
+                                       .GetStringFromName("brandShortName");
+        var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                                      .getService(Components.interfaces.nsIPromptService);
+
+        var errorStr = this.getString("load-js-data-url-error");
+        promptService.alert(window, brandShortName, errorStr);
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * Get the description associated with a document, as specified in a <META>
+   * element.
+   * @param   doc
+   *          A DOM Document to get a description for
+   * @returns A description string if a META element was discovered with a
+   *          "description" or "httpequiv" attribute, empty string otherwise.
+   */
+  getDescriptionFromDocument: function PU_getDescriptionFromDocument(doc) {
+    var metaElements = doc.getElementsByTagName("META");
+    for (var i = 0; i < metaElements.length; ++i) {
+      if (metaElements[i].name.toLowerCase() == "description" ||
+          metaElements[i].httpEquiv.toLowerCase() == "description") {
+        return metaElements[i].content;
+      }
+    }
+    return "";
+  },
+
+  /**
+   * Retrieve the description of an item
+   * @param aItemId
+   *        item identifier
+   * @returns the description of the given item, or an empty string if it is
+   * not set.
+   */
+  getItemDescription: function PU_getItemDescription(aItemId) {
+    if (PlacesUtils.annotations.itemHasAnnotation(aItemId, DESCRIPTION_ANNO))
+      return PlacesUtils.annotations.getItemAnnotation(aItemId, DESCRIPTION_ANNO);
+    return "";
+  },
+
+  /**
+   * Gives the user a chance to cancel loading lots of tabs at once
+   */
+  _confirmOpenInTabs: function PU__confirmOpenInTabs(numTabsToOpen) {
+    var pref = Components.classes["@mozilla.org/preferences-service;1"]
+                         .getService(Components.interfaces.nsIPrefBranch);
+
+    const kWarnOnOpenPref = "browser.tabs.warnOnOpen";
+    var reallyOpen = true;
+    if (pref.getBoolPref(kWarnOnOpenPref)) {
+      if (numTabsToOpen >= pref.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
+        var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                                      .getService(Components.interfaces.nsIPromptService);
+
+        // default to true: if it were false, we wouldn't get this far
+        var warnOnOpen = { value: true };
+
+        var messageKey = "tabs.openWarningMultipleBranded";
+        var openKey = "tabs.openButtonMultiple";
+        const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+        var brandShortName = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                       .getService(Components.interfaces.nsIStringBundleService)
+                                       .createBundle(BRANDING_BUNDLE_URI)
+                                       .GetStringFromName("brandShortName");
+
+        var buttonPressed = promptService.confirmEx(window,
+          this.getString("tabs.openWarningTitle"),
+          this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]),
+          (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
+           + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
+          this.getString(openKey), null, null,
+          this.getFormattedString("tabs.openWarningPromptMeBranded",
+                                  [brandShortName]), warnOnOpen);
+
+        reallyOpen = (buttonPressed == 0);
+        // don't set the pref unless they press OK and it's false
+        if (reallyOpen && !warnOnOpen.value)
+          pref.setBoolPref(kWarnOnOpenPref, false);
+      }
+    }
+    return reallyOpen;
+  },
+
+  /** aItemsToOpen needs to be an array of objects of the form:
+    * {uri: string, isBookmark: boolean}
+    */
+  _openTabset: function PU__openTabset(aItemsToOpen, aEvent) {
+    var urls = [];
+    for (var i = 0; i < aItemsToOpen.length; i++) {
+      var item = aItemsToOpen[i];
+      if (item.isBookmark)
+        this.markPageAsFollowedBookmark(item.uri);
+      else
+        this.markPageAsTyped(item.uri);
+
+      urls.push(item.uri);
+    }
+
+    var browserWindow = getTopWin();
+    var where = browserWindow ?
+                whereToOpenLink(aEvent, false, true) : "window";
+    openUILinkArrayIn(urls, where);
+  },
+
+  openContainerNodeInTabs: function PU_openContainerInTabs(aNode, aEvent) {
+    var urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
+    if (!this._confirmOpenInTabs(urlsToOpen.length))
+      return;
+
+    this._openTabset(urlsToOpen, aEvent);
+  },
+
+  openURINodesInTabs: function PU_openURINodesInTabs(aNodes, aEvent) {
+    var urlsToOpen = [];
+    for (var i=0; i < aNodes.length; i++) {
+      // skip over separators and folders
+      if (PlacesUtils.nodeIsURI(aNodes[i]))
+        urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
+    }
+    this._openTabset(urlsToOpen, aEvent);
+  },
+
+  /**
+   * Loads the node's URL in the appropriate tab or window or as a web
+   * panel given the user's preference specified by modifier keys tracked by a
+   * DOM mouse/key event.
+   * @param   aNode
+   *          An uri result node.
+   * @param   aEvent
+   *          The DOM mouse/key event with modifier keys set that track the
+   *          user's preferred destination window or tab.
+   */
+  openNodeWithEvent: function PU_openNodeWithEvent(aNode, aEvent) {
+    this.openNodeIn(aNode, whereToOpenLink(aEvent));
+  },
+
+  /**
+   * Loads the node's URL in the appropriate tab or window or as a
+   * web panel.
+   * see also openUILinkIn
+   */
+  openNodeIn: function PU_openNodeIn(aNode, aWhere) {
+    if (aNode && PlacesUtils.nodeIsURI(aNode) &&
+        this.checkURLSecurity(aNode)) {
+      var isBookmark = PlacesUtils.nodeIsBookmark(aNode);
+
+      if (isBookmark)
+        this.markPageAsFollowedBookmark(aNode.uri);
+      else
+        this.markPageAsTyped(aNode.uri);
+
+      // Check whether the node is a bookmark which should be opened as
+      // a web panel
+      if (aWhere == "current" && isBookmark) {
+        if (PlacesUtils.annotations
+                       .itemHasAnnotation(aNode.itemId, LOAD_IN_SIDEBAR_ANNO)) {
+          var w = getTopWin();
+          if (w) {
+            w.openWebPanel(aNode.title, aNode.uri);
+            return;
+          }
+        }
+      }
+      openUILinkIn(aNode.uri, aWhere);
+    }
+  },
+
+  getBestTitle: function PU_getBestTitle(aNode) {
+    var title;
+    if (!aNode.title && PlacesUtils.uriTypes.indexOf(aNode.type) != -1) {
+      // if node title is empty, try to set the label using host and filename
+      // PlacesUtils._uri() will throw if aNode.uri is not a valid URI
+      try {
+        var uri = PlacesUtils._uri(aNode.uri);
+        var host = uri.host;
+        var fileName = uri.QueryInterface(Components.interfaces.nsIURL).fileName;
+        // if fileName is empty, use path to distinguish labels
+        title = host + (fileName ?
+                        (host ? "/" + this.ellipsis + "/" : "") + fileName :
+                        uri.path);
+      }
+      catch (e) {
+        // Use (no title) for non-standard URIs (data:, javascript:, ...)
+        title = "";
+      }
+    }
+    else
+      title = aNode.title;
+
+    return title || this.getString("noTitle");
+  },
+};
--- a/suite/common/jar.mn
+++ b/suite/common/jar.mn
@@ -116,25 +116,27 @@ comm.jar:
    content/communicator/bookmarks/bookmarksTree.xml                 (bookmarks/bookmarksTree.xml)
    content/communicator/bookmarks/findBookmark.js                   (bookmarks/findBookmark.js)
    content/communicator/bookmarks/findBookmark.xul                  (bookmarks/findBookmark.xul)
    content/communicator/bookmarks/sortFolder.js                     (bookmarks/sortFolder.js)
    content/communicator/bookmarks/sortFolder.xul                    (bookmarks/sortFolder.xul)
    content/communicator/directory/directory.html                    (directory/directory.html)
    content/communicator/directory/directory.js                      (directory/directory.js)
    content/communicator/directory/directory.xul                     (directory/directory.xul)
+   content/communicator/history/controller.js                       (history/controller.js)
    content/communicator/history/history.js                          (history/history.js)
    content/communicator/history/history.xul                         (history/history.xul)
-   content/communicator/history/historyTreeOverlay.xul              (history/historyTreeOverlay.xul)
-   content/communicator/history/historyTreeSorting.js               (history/historyTreeSorting.js)
    content/communicator/history/history-panel.xul                   (history/history-panel.xul)
-   content/communicator/history/history-test.js                     (history/history-test.js)
-   content/communicator/history/history-test.xul                    (history/history-test.xul)
-   content/communicator/history/findHistory.js                      (history/findHistory.js)
-   content/communicator/history/findHistory.xul                     (history/findHistory.xul)
+   content/communicator/history/places.css                          (history/places.css)
+   content/communicator/history/places.js                           (history/places.js)
+   content/communicator/history/placesOverlay.xul                   (history/placesOverlay.xul)
+   content/communicator/history/sidebarUtils.js                     (history/sidebarUtils.js)
+   content/communicator/history/tree.xml                            (history/tree.xml)
+   content/communicator/history/treeView.js                         (history/treeView.js)
+*  content/communicator/history/utils.js                            (history/utils.js)
    content/communicator/permissions/cookieViewer.js                 (permissions/cookieViewer.js)
    content/communicator/permissions/cookieViewer.xul                (permissions/cookieViewer.xul)
    content/communicator/permissions/imageContextOverlay.xul         (permissions/imageContextOverlay.xul)
    content/communicator/permissions/permissionsManager.js           (permissions/permissionsManager.js)
    content/communicator/permissions/permissionsManager.xul          (permissions/permissionsManager.xul)
    content/communicator/permissions/permissionsNavigatorOverlay.xul (permissions/permissionsNavigatorOverlay.xul)
    content/communicator/permissions/permissionsOverlay.js           (permissions/permissionsOverlay.js)
    content/communicator/permissions/treeUtils.js                    (permissions/treeUtils.js)
--- a/suite/confvars.sh
+++ b/suite/confvars.sh
@@ -44,10 +44,11 @@ if [ "$COMM_BUILD" ]; then
 fi
 MOZ_STATIC_MAIL_BUILD=1
 MOZ_COMPOSER=1
 MOZ_SUITE=1
 MOZ_APP_VERSION=`cat $topsrcdir/$MOZ_BUILD_APP/config/version.txt`
 SEAMONKEY_VERSION=$MOZ_APP_VERSION
 MOZ_NO_XPCOM_OBSOLETE=1
 MOZ_EXTENSIONS_DEFAULT=" wallet venkman inspector irc gnomevfs reporter"
+MOZ_PLACES=1
 MOZ_UPDATER=1
 MOZ_HELP_VIEWER=1
--- a/suite/installer/unix/packages
+++ b/suite/installer/unix/packages
@@ -124,17 +124,16 @@ bin/components/exthandler.xpt
 bin/components/exthelper.xpt
 bin/components/fastfind.xpt
 bin/components/feeds.xpt
 bin/components/filepicker.xpt
 bin/components/libfileview.so
 bin/components/find.xpt
 bin/components/gfx.xpt
 bin/components/libgkgfxthebes.so
-bin/components/history.xpt
 bin/components/htmlparser.xpt
 bin/components/libgklayout.so
 bin/components/libgkplugin.so
 bin/components/libhtmlpars.so
 bin/components/libi18n.so
 bin/components/libimgicon.so
 bin/components/imgicon.xpt
 bin/components/libimglib2.so
@@ -169,16 +168,18 @@ bin/components/necko_http.xpt
 bin/components/necko_res.xpt
 bin/components/necko_socket.xpt
 bin/components/necko_strconv.xpt
 bin/components/necko_viewsource.xpt
 bin/components/liboji.so
 bin/components/oji.xpt
 bin/components/parentalcontrols.xpt
 bin/components/libpermissions.so
+bin/components/places.xpt
+bin/components/libplaces.so
 bin/components/plugin.xpt
 bin/components/libpref.so
 bin/components/pref.xpt
 bin/components/prefetch.xpt
 bin/components/profile.xpt
 bin/components/proxyObjInst.xpt
 bin/components/libunixproxy.so
 bin/components/libremoteservice.so
@@ -258,25 +259,28 @@ bin/components/nsBlocklistService.js
 bin/components/nsBrowserContentHandler.js
 bin/components/nsComposerCmdLineHandler.js
 bin/components/nsContentDispatchChooser.js
 bin/components/nsContentPrefService.js
 bin/components/nsDefaultCLH.js
 bin/components/nsDownloadProgressListener.js
 bin/components/nsExtensionManager.js
 bin/components/nsFilePicker.js
+bin/components/nsLivemarkService.js
 bin/components/nsLoginInfo.js
 bin/components/nsLoginManager.js
 bin/components/nsLoginManagerPrompter.js
 bin/components/nsHandlerService.js
 bin/components/nsHelperAppDlg.js
+bin/components/nsPlacesDBFlush.js
 bin/components/nsProgressDialog.js
 bin/components/nsProxyAutoConfig.js
 bin/components/nsSidebar.js
 bin/components/nsSuiteGlue.js
+bin/components/nsTaggingService.js
 bin/components/nsTypeAheadFind.js
 bin/components/nsTryToClose.js
 bin/components/nsUpdateService.js
 bin/components/nsURLFormatter.js
 bin/components/nsWebHandlerApp.js
 bin/components/pluginGlue.js
 bin/components/storage-Legacy.js
 bin/components/txEXSLTRegExFunctions.js
--- a/suite/installer/windows/packages
+++ b/suite/installer/windows/packages
@@ -132,17 +132,16 @@ bin\components\extensions.xpt
 bin\components\exthandler.xpt
 bin\components\exthelper.xpt
 bin\components\fastfind.xpt
 bin\components\feeds.xpt
 bin\components\find.xpt
 bin\components\gklayout.dll
 bin\components\gkparser.dll
 bin\components\gkplugin.dll
-bin\components\history.xpt
 bin\components\htmlparser.xpt
 bin\components\i18n.dll
 bin\components\imgicon.dll
 bin\components\imgicon.xpt
 bin\components\imglib2.dll
 bin\components\imglib2.xpt
 bin\components\inspector.xpt
 bin\components\intl.xpt
@@ -258,24 +257,27 @@ bin\components\nsAxSecurityPolicy.js
 bin\components\nsBadCertHandler.js
 bin\components\nsBlocklistService.js
 bin\components\nsBrowserContentHandler.js
 bin\components\nsTryToClose.js
 bin\components\nsComposerCmdLineHandler.js
 bin\components\nsContentDispatchChooser.js
 bin\components\nsContentPrefService.js
 bin\components\nsExtensionManager.js
+bin\components\nsLivemarkService.js
 bin\components\nsLoginInfo.js
 bin\components\nsLoginManager.js
 bin\components\nsLoginManagerPrompter.js
+bin\components\nsPlacesDBFlush.js
 bin\components\nsPostUpdateWin.js
 bin\components\nsProxyAutoConfig.js
 bin\components\nsSetDefault.js
 bin\components\nsSidebar.js
 bin\components\nsSuiteGlue.js
+bin\components\nsTaggingService.js
 bin\components\nsTypeAheadFind.js
 bin\components\nsUpdateService.js
 bin\components\nsURLFormatter.js
 bin\components\pluginGlue.js
 bin\components\storage-Legacy.js
 bin\components\txEXSLTRegExFunctions.js
 bin\components\nsAboutAbout.js
 bin\components\nsDefaultCLH.js
deleted file mode 100644
--- a/suite/locales/en-US/chrome/common/history/findHistory.dtd
+++ /dev/null
@@ -1,13 +0,0 @@
-
-<!ENTITY search.name.label              "title">
-<!ENTITY search.url.label               "location">
-<!ENTITY search.startswith.label        "starts with">
-<!ENTITY search.endswith.label          "ends with">
-<!ENTITY search.is.label                "is">
-<!ENTITY search.isnot.label             "is not">
-<!ENTITY search.contains.label          "contains">
-<!ENTITY search.doesntcontain.label     "doesn't contain">
-<!ENTITY search.for.label               "Find visited documents whose">
-<!ENTITY findHistory.title              "Find in History">
-<!ENTITY findButton.label               "Find">
-
--- a/suite/locales/en-US/chrome/common/history/history.dtd
+++ b/suite/locales/en-US/chrome/common/history/history.dtd
@@ -1,35 +1,47 @@
-<!-- extracted from ./history.xul -->
+<!-- history.xul, history-panel.xul, placesOverlay.xul -->
+
+<!ENTITY historyWindowTitle.label       "History">
+<!ENTITY menuBar.tooltip                "Menu Bar">
+<!ENTITY searchBar.tooltip              "Search Bar">
+
+<!ENTITY search.emptytext               "Search History">
+<!ENTITY search.key                     "f">
+<!ENTITY view.label                     "View">
+<!ENTITY byDate.label                   "By Date">
+<!ENTITY byDate.accesskey               "D">
+<!ENTITY bySite.label                   "By Site">
+<!ENTITY bySite.accesskey               "S">
+<!ENTITY byMostVisited.label            "By Most Visited">
+<!ENTITY byMostVisited.accesskey        "V">
+<!ENTITY byLastVisited.label            "By Last Visited">
+<!ENTITY byLastVisited.accesskey        "L">
+<!ENTITY byDayAndSite.label             "By Date and Site">
+<!ENTITY byDayAndSite.accesskey         "t">
 
-<!ENTITY menuBar.tooltip            "Menu Bar">
-<!ENTITY fileMenu.label             "File">
-<!ENTITY closeCmd.label             "Close">
-<!ENTITY editMenu.label             "Edit"> 
-<!ENTITY undoCmd.label              "Undo">
-<!ENTITY redoCmd.label              "Redo">
-<!ENTITY cutCmd.label               "Cut">
-<!ENTITY copyCmd.label              "Copy">
-<!ENTITY pasteCmd.label             "Paste">
-<!ENTITY deleteCmd.label            "Delete">
-<!ENTITY deleteHostnameCmd.accesskey "H">
-<!ENTITY deleteDomainCmd.accesskey  "s">
-<!ENTITY selAllCmd.label            "Select All">
-<!ENTITY findHisCmd.label           "Search History…">
-<!ENTITY findHisCmd.commandkey      "f">
-<!ENTITY findHisCmd.accesskey       "S">
-<!ENTITY groupBy.label              "Group By">
-<!ENTITY groupBy.accesskey          "G">
-<!ENTITY groupByDay.label           "Day">
-<!ENTITY groupByDay.accesskey       "D">
-<!ENTITY groupBySite.label          "Site">
-<!ENTITY groupBySite.accesskey      "S">
-<!ENTITY groupByNone.label          "None">
-<!ENTITY groupByNone.accesskey      "N">
-<!ENTITY historyWindowTitle.label   "History">
-<!ENTITY menuitem.view.unsorted.label "Unsorted">
-<!ENTITY menuitem.view.unsorted.accesskey "u">
-<!ENTITY sortAscending.label         "Ascending">
-<!ENTITY sortAscending.accesskey     "A">
-<!ENTITY sortDescending.label        "Descending">
-<!ENTITY sortDescending.accesskey    "D">
-<!ENTITY menuitem.view.show_columns.label "Show columns">
-<!ENTITY menuitem.view.show_columns.accesskey "S">
+<!ENTITY view.columns.label             "Show Columns">
+<!ENTITY view.columns.accesskey         "C">
+<!ENTITY view.sort.label                "Sort">
+<!ENTITY view.sort.accesskey            "S">
+<!ENTITY view.unsorted.label            "Unsorted">
+<!ENTITY view.unsorted.accesskey        "U">
+<!ENTITY view.sortAscending.label       "A > Z Sort Order">
+<!ENTITY view.sortAscending.accesskey   "A">
+<!ENTITY view.sortDescending.label      "Z > A Sort Order">
+<!ENTITY view.sortDescending.accesskey  "Z">
+
+<!ENTITY cmd.open.label                 "Open">
+<!ENTITY cmd.open.accesskey             "O">
+<!ENTITY cmd.open_window.label          "Open Link in New Window">
+<!ENTITY cmd.open_window.accesskey      "N">
+<!ENTITY cmd.open_tab.label             "Open Link in New Tab">
+<!ENTITY cmd.open_tab.accesskey         "w">
+<!ENTITY cmd.open_all_in_tabs.label     "Open All in Tabs">
+<!ENTITY cmd.open_all_in_tabs.accesskey "O">
+
+<!ENTITY bookmarkCmd.label              "Bookmark This Link…">
+<!ENTITY bookmarkCmd.accesskey          "B">
+
+<!ENTITY col.name.label                 "Title">
+<!ENTITY col.url.label                  "Location">
+<!ENTITY col.lastvisit.label            "Last Visited">
+<!ENTITY col.visitcount.label           "Visit Count">
deleted file mode 100644
--- a/suite/locales/en-US/chrome/common/history/history.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-deleteHost=Delete History for %S
-deleteHostNoSelection=Delete History for Site
-deleteDomain=Delete History for *.%S
-deleteDomainNoSelection=Delete History for Domain
-
-finduri-AgeInDays-is-0=Today
-finduri-AgeInDays-is-1=Yesterday
-finduri-AgeInDays-is=%S days ago
-finduri-AgeInDays-isgreater=Older than %S days
-
-finduri-Hostname-is-=(no host)
-
-search_results_title=Search Results
-
-collapseLabel=Collapse
-expandLabel=Expand
-collapseAccesskey=C
-expandAccesskey=x
-
-load-js-data-url-error=For security reasons, javascript or data urls cannot be loaded from the history window or sidebar.
deleted file mode 100644
--- a/suite/locales/en-US/chrome/common/history/historyTreeOverlay.dtd
+++ /dev/null
@@ -1,16 +0,0 @@
-<!ENTITY tree.header.name.label               "Title">
-<!ENTITY tree.header.name.accesskey           "T">
-<!ENTITY tree.header.url.label                "Location">
-<!ENTITY tree.header.url.accesskey            "L">
-<!ENTITY tree.header.date.label               "Last Visited">
-<!ENTITY tree.header.date.accesskey           "V">
-<!ENTITY tree.header.firstvisitdate.label     "First Visited">
-<!ENTITY tree.header.firstvisitdate.accesskey "F">
-<!ENTITY tree.header.referrer.label           "Referrer">
-<!ENTITY tree.header.referrer.accesskey       "R">
-<!ENTITY tree.header.hostname.label           "Hostname">
-<!ENTITY tree.header.hostname.accesskey       "H">
-<!ENTITY tree.header.visitcount.label         "Visit Count">
-<!ENTITY tree.header.visitcount.accesskey     "C">
-<!ENTITY bookmarkLinksCmd.label               "Bookmark These Links">
-<!ENTITY bookmarkLinksCmd.accesskey           "L">
new file mode 100644
--- /dev/null
+++ b/suite/locales/en-US/chrome/common/history/places.properties
@@ -0,0 +1,25 @@
+load-js-data-url-error=For security reasons, javascript or data urls cannot be loaded from the history window or sidebar.
+noTitle=(no title)
+
+bookmarksMenuEmptyFolder=(Empty)
+
+view.sortBy.name.label=Sort by Name
+view.sortBy.name.accesskey=N
+view.sortBy.url.label=Sort by Location
+view.sortBy.url.accesskey=L
+view.sortBy.date.label=Sort by Visit Date
+view.sortBy.date.accesskey=V
+view.sortBy.visitCount.label=Sort by Visit Count
+view.sortBy.visitCount.accesskey=C
+view.sortBy.dateAdded.label=Sort by Added
+view.sortBy.dateAdded.accesskey=e
+view.sortBy.lastModified.label=Sort by Last Modified
+view.sortBy.lastModified.accesskey=M
+
+searchHistory=Search History
+searchCurrentDefault=Search in '%S'
+
+tabs.openWarningTitle=Confirm open
+tabs.openWarningMultipleBranded=You are about to open %S tabs.  This might slow down %S while the pages are loading.  Are you sure you want to continue?
+tabs.openButtonMultiple=Open tabs
+tabs.openWarningPromptMeBranded=Warn me when opening multiple tabs might slow down %S
--- a/suite/locales/jar.mn
+++ b/suite/locales/jar.mn
@@ -116,19 +116,17 @@
   locale/@AB_CD@/communicator/help/images/taskbar.png                       (%chrome/common/help/images/taskbar.png)
   locale/@AB_CD@/communicator/help/images/taskbar-ab.png                    (%chrome/common/help/images/taskbar-ab.png)
   locale/@AB_CD@/communicator/help/images/task_mail.png                     (%chrome/common/help/images/task_mail.png)
   locale/@AB_CD@/communicator/help/images/task_newmail.png                  (%chrome/common/help/images/task_newmail.png)
   locale/@AB_CD@/communicator/help/images/threadbutton.png                  (%chrome/common/help/images/threadbutton.png)
   locale/@AB_CD@/communicator/help/images/web-links.png                     (%chrome/common/help/images/web-links.png)
   locale/@AB_CD@/communicator/help/images/wink.png                          (%chrome/common/help/images/wink.png)
   locale/@AB_CD@/communicator/history/history.dtd                           (%chrome/common/history/history.dtd)
-  locale/@AB_CD@/communicator/history/historyTreeOverlay.dtd                (%chrome/common/history/historyTreeOverlay.dtd)
-  locale/@AB_CD@/communicator/history/history.properties                    (%chrome/common/history/history.properties)
-  locale/@AB_CD@/communicator/history/findHistory.dtd                       (%chrome/common/history/findHistory.dtd)
+  locale/@AB_CD@/communicator/history/places.properties                     (%chrome/common/history/places.properties)
   locale/@AB_CD@/communicator/migration/migration.dtd                       (%chrome/common/migration/migration.dtd)
   locale/@AB_CD@/communicator/migration/migration.properties                (%chrome/common/migration/migration.properties)
   locale/@AB_CD@/communicator/permissions/cookieViewer.properties           (%chrome/common/permissions/cookieViewer.properties)
   locale/@AB_CD@/communicator/permissions/cookieViewer.dtd                  (%chrome/common/permissions/cookieViewer.dtd)
   locale/@AB_CD@/communicator/permissions/permissionsManager.dtd            (%chrome/common/permissions/permissionsManager.dtd)
   locale/@AB_CD@/communicator/permissions/permissionsManager.properties     (%chrome/common/permissions/permissionsManager.properties)
   locale/@AB_CD@/communicator/permissions/permissionsNavigatorOverlay.dtd   (%chrome/common/permissions/permissionsNavigatorOverlay.dtd)
   locale/@AB_CD@/communicator/pref/prefutilities.properties                 (%chrome/common/pref/prefutilities.properties)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1855c9f507f4794b9fc392c1700ca07f963c0ece
GIT binary patch
literal 614
zc$@)d0-61ZP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!2uVaiRCwB?Q$1)DQ4pRx4k2Q#MmP~I
z5-mK)j}Vh0212gUQc|U{v#_uc1d}2th+Ltd!KAy+CMhIEPKY^3Asz-Ih?U5(f*=xO
z?l#`OeeaF)^EM`63UOfeeY~A-=9}5s+=sb`2!|LVJF0s<{XX1(`(CcVffoio!M|GL
z^(i?e0ep!8QVUZrk3e;F3!syz|LOoFMzJw4wKWXYqpL!3XKu)um#1f~9;&yx2zz?V
z?g9>{5ATCHj$vC_|BQ#3b%=>Yj1A&;2l0E1__dAL{E7H<evLyQ@3;cl<~Mb=+M5WH
z+3Vm)*jgiku9-4eD){bw1Sjs7!4M@)m6vzc|8+?9@e0`ReO_H#5E55hE<Xc9l+>g;
zL~Auu>3HakNTY*AsmMk8OGP0w>0Iw=C9?u!Re=qFW#iql%rBG8fsKtOoP418ZG98Y
zSF#qe1VKS2HS4H$bzbmp+<_NEkEC*IJBBC4J92Ns3<{iSzijrtl47oOaj*wc##FFx
z;<C2JaiMVDB<VrfjO!owrd?ZHhu(_b|7b?giXvdHvsnwIK&nxqr7Mqx)ZU5VdC#=q
z)=nE{kKL36drO3W7O7xJ8^0X7bXJE$cw~G)hQ@u5O=@-aLU&P@6nQZ*d`3#JFt-A{
z51f@3%ZJlZkf(c3YtDD_H~ND=Z;mhj$9@Yi0O3ZMSR`QizW@LL07*qoM6N<$f>u`&
AQ~&?~
new file mode 100755
--- /dev/null
+++ b/suite/themes/classic/communicator/history/places.css
@@ -0,0 +1,42 @@
+/* Trees */
+treechildren::-moz-tree-image(title) {
+  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+  padding-right: 2px;
+  margin: 0px 2px;
+  width: 16px;
+  height: 16px;
+}
+
+treechildren::-moz-tree-image(title, separator) {
+  list-style-image: none;
+  width: 0;
+  height: 0;
+}
+
+treechildren::-moz-tree-image(title, container) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-folder-closed.png");
+}
+
+treechildren::-moz-tree-image(title, open) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-folder-open.png");
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+  list-style-image: url("chrome://communicator/skin/history/calendar.png");
+}
+
+treechildren::-moz-tree-row(session-start) {
+  border-top:1px dotted ThreeDShadow;
+  font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(date, session-continue) {
+  color: -moz-Field;
+}
+
+/* Browser Sidebars */
+
+#viewButton {
+  min-width: 0px !important;
+}
--- a/suite/themes/classic/communicator/sidebar/sidebarListView.css
+++ b/suite/themes/classic/communicator/sidebar/sidebarListView.css
@@ -31,12 +31,21 @@
  * 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 ***** */
 
-treechildren::-moz-tree-image,
-treechildren::-moz-tree-cell-text {
-  cursor: pointer;  
+treechildren::-moz-tree-image(leaf),
+treechildren::-moz-tree-cell(leaf) {
+  cursor: pointer;
 }
+
+treechildren::-moz-tree-cell-text(leaf, hover) {
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+treechildren::-moz-tree-cell(separator) {
+  cursor: default;
+}
--- a/suite/themes/classic/jar.mn
+++ b/suite/themes/classic/jar.mn
@@ -35,16 +35,18 @@ classic.jar:
   skin/classic/communicator/bookmarks/location-dis.gif                  (communicator/bookmarks/location-dis.gif)
   skin/classic/communicator/bookmarks/location-hov.gif                  (communicator/bookmarks/location-hov.gif)
   skin/classic/communicator/bookmarks/location.gif                      (communicator/bookmarks/location.gif)
   skin/classic/communicator/bookmarks/bookmarksToolbar.css              (communicator/bookmarks/bookmarksToolbar.css)
   skin/classic/communicator/directory/directory.css                     (communicator/directory/directory.css)
   skin/classic/communicator/directory/folder-clsd.gif                   (communicator/directory/folder-clsd.gif)
   skin/classic/communicator/directory/folder-open.gif                   (communicator/directory/folder-open.gif)
   skin/classic/communicator/directory/file.gif                          (communicator/directory/file.gif)
+  skin/classic/communicator/history/calendar.png                        (communicator/history/calendar.png)
+  skin/classic/communicator/history/places.css                          (communicator/history/places.css)
   skin/classic/communicator/profile/migrate.gif                         (communicator/profile/migrate.gif)
   skin/classic/communicator/profile/profile.css                         (communicator/profile/profile.css)
   skin/classic/communicator/profile/profileManager.css                  (communicator/profile/profileManager.css)
   skin/classic/communicator/profile/profileicon-large.gif               (communicator/profile/profileicon-large.gif)
   skin/classic/communicator/related/related.css                         (communicator/related/related.css)
   skin/classic/communicator/related/sitemap.gif                         (communicator/related/sitemap.gif)
   skin/classic/communicator/search/category.gif                         (communicator/search/category.gif)
   skin/classic/communicator/search/result.gif                           (communicator/search/result.gif)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1855c9f507f4794b9fc392c1700ca07f963c0ece
GIT binary patch
literal 614
zc$@)d0-61ZP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!2uVaiRCwB?Q$1)DQ4pRx4k2Q#MmP~I
z5-mK)j}Vh0212gUQc|U{v#_uc1d}2th+Ltd!KAy+CMhIEPKY^3Asz-Ih?U5(f*=xO
z?l#`OeeaF)^EM`63UOfeeY~A-=9}5s+=sb`2!|LVJF0s<{XX1(`(CcVffoio!M|GL
z^(i?e0ep!8QVUZrk3e;F3!syz|LOoFMzJw4wKWXYqpL!3XKu)um#1f~9;&yx2zz?V
z?g9>{5ATCHj$vC_|BQ#3b%=>Yj1A&;2l0E1__dAL{E7H<evLyQ@3;cl<~Mb=+M5WH
z+3Vm)*jgiku9-4eD){bw1Sjs7!4M@)m6vzc|8+?9@e0`ReO_H#5E55hE<Xc9l+>g;
zL~Auu>3HakNTY*AsmMk8OGP0w>0Iw=C9?u!Re=qFW#iql%rBG8fsKtOoP418ZG98Y
zSF#qe1VKS2HS4H$bzbmp+<_NEkEC*IJBBC4J92Ns3<{iSzijrtl47oOaj*wc##FFx
z;<C2JaiMVDB<VrfjO!owrd?ZHhu(_b|7b?giXvdHvsnwIK&nxqr7Mqx)ZU5VdC#=q
z)=nE{kKL36drO3W7O7xJ8^0X7bXJE$cw~G)hQ@u5O=@-aLU&P@6nQZ*d`3#JFt-A{
z51f@3%ZJlZkf(c3YtDD_H~ND=Z;mhj$9@Yi0O3ZMSR`QizW@LL07*qoM6N<$f>u`&
AQ~&?~
new file mode 100755
--- /dev/null
+++ b/suite/themes/modern/communicator/history/places.css
@@ -0,0 +1,42 @@
+/* Trees */
+treechildren::-moz-tree-image(title) {
+  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+  padding-right: 2px;
+  margin: 0px 2px;
+  width: 16px;
+  height: 16px;
+}
+
+treechildren::-moz-tree-image(title, separator) {
+  list-style-image: none;
+  width: 0;
+  height: 0;
+}
+
+treechildren::-moz-tree-image(title, container) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-folder-closed.png");
+}
+
+treechildren::-moz-tree-image(title, open) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-folder-open.png");
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+  list-style-image: url("chrome://communicator/skin/history/calendar.png");
+}
+
+treechildren::-moz-tree-row(session-start) {
+  border-top:1px dotted #808080;
+  font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(date, session-continue) {
+  color: #FFFFFF;
+}
+
+/* Browser Sidebars */
+
+#viewButton {
+  min-width: 0px !important;
+}
--- a/suite/themes/modern/communicator/sidebar/sidebarListView.css
+++ b/suite/themes/modern/communicator/sidebar/sidebarListView.css
@@ -31,11 +31,21 @@
  * 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 ***** */
 
-treechildren {
-  cursor: pointer;  
+treechildren::-moz-tree-image(leaf),
+treechildren::-moz-tree-cell(leaf) {
+  cursor: pointer;
 }
+
+treechildren::-moz-tree-cell-text(leaf, hover) {
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+treechildren::-moz-tree-cell(separator) {
+  cursor: default;
+}
--- a/suite/themes/modern/jar.mn
+++ b/suite/themes/modern/jar.mn
@@ -30,16 +30,18 @@ modern.jar:
   skin/modern/communicator/brand/throbber-anim.gif                 (communicator/brand/throbber-anim.gif)
   skin/modern/communicator/brand/throbber-single.gif               (communicator/brand/throbber-single.gif)
   skin/modern/communicator/brand/throbber16-anim.gif               (communicator/brand/throbber16-anim.gif)
   skin/modern/communicator/brand/throbber16-single.gif             (communicator/brand/throbber16-single.gif)
   skin/modern/communicator/directory/file-folder-closed.gif        (communicator/directory/file-folder-closed.gif)
   skin/modern/communicator/directory/file-folder-open.gif          (communicator/directory/file-folder-open.gif)
   skin/modern/communicator/directory/file-icon.gif                 (communicator/directory/file-icon.gif)
   skin/modern/communicator/directory/directory.css                 (communicator/directory/directory.css)
+  skin/modern/communicator/history/calendar.png                    (communicator/history/calendar.png)
+  skin/modern/communicator/history/places.css                      (communicator/history/places.css)
   skin/modern/communicator/icons/alwaysAsk.png                     (communicator/icons/alwaysAsk.png)
   skin/modern/communicator/icons/application.png                   (communicator/icons/application.png)
   skin/modern/communicator/icons/btn1.gif                          (communicator/icons/btn1.gif)
   skin/modern/communicator/icons/common.png                        (communicator/icons/common.png)
   skin/modern/communicator/icons/common-small.png                  (communicator/icons/common-small.png)
   skin/modern/communicator/icons/feedIcon16.png                    (communicator/icons/feedIcon16.png)
   skin/modern/communicator/icons/loading.gif                       (communicator/icons/loading.gif)
   skin/modern/communicator/icons/lock-broken.gif                   (communicator/icons/lock-broken.gif)