home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / firefox-3.0.14 / chrome / browser.jar / content / browser / places / treeView.js < prev    next >
Encoding:
Text File  |  2008-05-05  |  51.5 KB  |  1,422 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Mozilla History System
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  * Google Inc.
  19.  * Portions created by the Initial Developer are Copyright (C) 2005
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Brett Wilson <brettw@gmail.com> (original author)
  24.  *   Asaf Romano <mano@mozilla.com> (Javascript version)
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. PlacesTreeView.prototype = {
  41.   _makeAtom: function PTV__makeAtom(aString) {
  42.     return  Cc["@mozilla.org/atom-service;1"].
  43.             getService(Ci.nsIAtomService).
  44.             getAtom(aString);
  45.   },
  46.  
  47.   _atoms: [],
  48.   _getAtomFor: function PTV__getAtomFor(aName) {
  49.     if (!this._atoms[aName])
  50.       this._atoms[aName] = this._makeAtom(aName);
  51.  
  52.     return this._atoms[aName];
  53.   },
  54.  
  55.   _ensureValidRow: function PTV__ensureValidRow(aRow) {
  56.     if (aRow < 0 || aRow >= this._visibleElements.length)
  57.       throw Cr.NS_ERROR_INVALID_ARG;
  58.   },
  59.  
  60.   __dateService: null,
  61.   get _dateService() {
  62.     if (!this.__dateService) {
  63.       this.__dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
  64.                            getService(Ci.nsIScriptableDateFormat);
  65.     }
  66.     return this.__dateService;
  67.   },
  68.  
  69.   QueryInterface: function PTV_QueryInterface(aIID) {
  70.     if (aIID.equals(Ci.nsITreeView) ||
  71.         aIID.equals(Ci.nsINavHistoryResultViewer) ||
  72.         aIID.equals(Ci.nsINavHistoryResultTreeViewer) ||
  73.         aIID.equals(Ci.nsISupports))
  74.       return this;
  75.  
  76.     throw Cr.NS_ERROR_NO_INTERFACE;
  77.   },
  78.  
  79.   /**
  80.    * This is called when the result or tree may have changed.
  81.    * It reinitializes everything. Result and/or tree can be null
  82.    * when calling.
  83.    */
  84.   _finishInit: function PTV__finishInit() {
  85.     if (this._tree && this._result)
  86.       this.sortingChanged(this._result.sortingMode);
  87.  
  88.     var qoInt = Ci.nsINavHistoryQueryOptions;
  89.     var options = asQuery(this._result.root).queryOptions;
  90.  
  91.     // if there is no tree, BuildVisibleList will clear everything for us
  92.     this._buildVisibleList();
  93.   },
  94.  
  95.   _computeShowSessions: function PTV__computeShowSessions() {
  96.     NS_ASSERT(this._result, "Must have a result to show sessions!");
  97.     this._showSessions = false;
  98.  
  99.     var options = asQuery(this._result.root).queryOptions;
  100.     NS_ASSERT(options, "navHistoryResults must have valid options");
  101.  
  102.     if (!options.showSessions)
  103.       return; // sessions are off
  104.  
  105.     var resultType = options.resultType;
  106.     if (resultType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT &&
  107.         resultType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_FULL_VISIT)
  108.       return; // not visits
  109.  
  110.     var sortType = this._result.sortingMode;
  111.     if (sortType != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
  112.         sortType != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
  113.       return; // not date sorting
  114.  
  115.     this._showSessions = true;
  116.   },
  117.  
  118.   SESSION_STATUS_NONE: 0,
  119.   SESSION_STATUS_START: 1,
  120.   SESSION_STATUS_CONTINUE: 2,
  121.   _getRowSessionStatus: function PTV__getRowSessionStatus(aRow) {
  122.     var node = this._visibleElements[aRow].node;
  123.     if (!PlacesUtils.nodeIsVisit(node) || asVisit(node).sessionId == 0)
  124.       return this.SESSION_STATUS_NONE;
  125.  
  126.     if (aRow == 0)
  127.       return this.SESSION_STATUS_START;
  128.  
  129.     var previousNode = this._visibleElements[aRow - 1].node;
  130.     if (!PlacesUtils.nodeIsVisit(previousNode) ||
  131.         node.sessionId != asVisit(previousNode).sessionId)
  132.       return this.SESSION_STATUS_START;
  133.  
  134.     return this.SESSION_STATUS_CONTINUE;
  135.   },
  136.  
  137.   /**
  138.    * Call to completely rebuild the list of visible items. Note if there is no
  139.    * tree or root this will just clear out the list, so you can also call this
  140.    * when a tree is detached to clear the list.
  141.    */
  142.   _buildVisibleList: function PTV__buildVisibleList() {
  143.     var selection = this.selection;
  144.     if (selection)
  145.       selection.selectEventsSuppressed = true;
  146.  
  147.     if (this._result) {
  148.       // Any current visible elements need to be marked as invisible.
  149.       for (var i = 0; i < this._visibleElements.length; i++) {
  150.         this._visibleElements[i].node.viewIndex = -1;
  151.       }
  152.     }
  153.  
  154.     var rootNode = this._result.root;
  155.     if (rootNode && this._tree) {
  156.       this._computeShowSessions();
  157.  
  158.       asContainer(rootNode);
  159.       if (this._showRoot) {
  160.         // List the root node
  161.         this._visibleElements.push(
  162.           { node: this._result.root, properties: null });
  163.         this._tree.rowCountChanged(0, 1);
  164.         this._result.root.viewIndex = 0;
  165.       }
  166.       else if (!rootNode.containerOpen) {
  167.         // this triggers containerOpened which then builds the visible
  168.         // section
  169.         rootNode.containerOpen = true;
  170.       }
  171.       else
  172.         this.invalidateContainer(rootNode);
  173.     }
  174.     if (selection)
  175.       selection.selectEventsSuppressed = false;
  176.   },
  177.  
  178.   /**
  179.    * This takes a container and recursively appends visible elements to the
  180.    * given array. This is used to build the visible element list (with
  181.    * this._visibleElements passed as the array), or portions thereof (with
  182.    * a separate array that is merged with the main list later.
  183.    *
  184.    * aVisibleStartIndex is the visible index of the beginning of the 'aVisible'
  185.    * array. When aVisible is this._visibleElements, this is 0. This is non-zero
  186.    * when we are building up a sub-region for insertion. Then, this is the
  187.    * index where the new array will be inserted into this._visibleElements.
  188.    * It is used to compute each node's viewIndex.
  189.    */
  190.   _buildVisibleSection:
  191.   function PTV__buildVisibleSection(aContainer, aVisible, aToOpen, aVisibleStartIndex)
  192.   {
  193.     if (!aContainer.containerOpen)
  194.       return;  // nothing to do
  195.  
  196.     const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
  197.     const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
  198.  
  199.     var cc = aContainer.childCount;
  200.     for (var i=0; i < cc; i++) {
  201.       var curChild = aContainer.getChild(i);
  202.       var curChildType = curChild.type;
  203.  
  204.       // don't display separators when sorted
  205.       if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
  206.         if (this._result.sortingMode !=
  207.             Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
  208.           curChild.viewIndex = -1;
  209.           continue;
  210.         }
  211.       }
  212.  
  213.       // add item
  214.       curChild.viewIndex = aVisibleStartIndex + aVisible.length;
  215.       aVisible.push({ node: curChild, properties: null });
  216.  
  217.       // recursively do containers
  218.       if (!this._flatList && PlacesUtils.containerTypes.indexOf(curChildType) != -1) {
  219.         asContainer(curChild);
  220.  
  221.         var resource = this._getResourceForNode(curChild);
  222.         var isopen = resource != null &&
  223.                      PlacesUIUtils.localStore.HasAssertion(resource, openLiteral,
  224.                                                            trueLiteral, true);
  225.         if (isopen != curChild.containerOpen)
  226.           aToOpen.push(curChild);
  227.         else if (curChild.containerOpen && curChild.childCount > 0)
  228.           this._buildVisibleSection(curChild, aVisible, aToOpen, aVisibleStartIndex);
  229.       }
  230.     }
  231.   },
  232.  
  233.   /**
  234.    * This counts how many rows an item takes in the tree, that is, the
  235.    * item itself plus any nodes following it with an increased indent.
  236.    * This allows you to figure out how many rows an item (=1) or a
  237.    * container with all of its children takes.
  238.    */
  239.   _countVisibleRowsForItem: function PTV__countVisibleRowsForItem(aNode) {
  240.     if (aNode == this._result.root)
  241.       return this._visibleElements.length;
  242.  
  243.     var viewIndex = aNode.viewIndex;
  244.     NS_ASSERT(viewIndex >= 0, "Item is not visible, no rows to count");
  245.     var outerLevel = aNode.indentLevel;
  246.     for (var i = viewIndex + 1; i < this._visibleElements.length; i++) {
  247.       if (this._visibleElements[i].node.indentLevel <= outerLevel)
  248.         return i - viewIndex;
  249.     }
  250.     // this node plus its children occupy the bottom of the list
  251.     return this._visibleElements.length - viewIndex;
  252.   },
  253.  
  254.   /**
  255.    * This is called by containers when they change and we need to update
  256.    * everything about the container. We build a new visible section with
  257.    * the container as a separate object so we first know how the list
  258.    * changes. This way we only have to do one realloc/memcpy to update
  259.    * the list.
  260.    *
  261.    * We also try to be smart here about redrawing the screen.
  262.    */
  263.   _refreshVisibleSection: function PTV__refreshVisibleSection(aContainer) {
  264.     NS_ASSERT(this._result, "Need to have a result to update");
  265.     if (!this._tree)
  266.       return;
  267.  
  268.     // The root node is invisible if showRoot is not set. Otherwise aContainer
  269.     // must be visible
  270.     if (this._showRoot || aContainer != this._result.root) {
  271.       if (aContainer.viewIndex < 0 ||
  272.           aContainer.viewIndex > this._visibleElements.length)
  273.         throw "Trying to expand a node that is not visible";
  274.  
  275.       NS_ASSERT(this._visibleElements[aContainer.viewIndex].node == aContainer,
  276.                 "Visible index is out of sync!");
  277.     }
  278.  
  279.     var startReplacement = aContainer.viewIndex + 1;
  280.     var replaceCount = this._countVisibleRowsForItem(aContainer);
  281.  
  282.     // We don't replace the container item itself so we decrease the
  283.     // replaceCount by 1. We don't do so though if there is no visible item
  284.     // for the container. This happens when aContainer is the root node and
  285.     // showRoot is not set.
  286.     if (aContainer.viewIndex != -1)
  287.       replaceCount-=1;
  288.  
  289.     // Persist selection state
  290.     var previouslySelectedNodes = [];
  291.     var selection = this.selection;
  292.     var rc = selection.getRangeCount();
  293.     for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
  294.       var min = { }, max = { };
  295.       selection.getRangeAt(rangeIndex, min, max);
  296.       var lastIndex = Math.min(max.value, startReplacement + replaceCount -1);
  297.       if (min.value < startReplacement || min.value > lastIndex)
  298.         continue;
  299.  
  300.       for (var nodeIndex = min.value; nodeIndex <= lastIndex; nodeIndex++)
  301.         previouslySelectedNodes.push(
  302.           { node: this._visibleElements[nodeIndex].node, oldIndex: nodeIndex });
  303.     }
  304.  
  305.     // Mark the removes as invisible
  306.     for (var i = 0; i < replaceCount; i++)
  307.       this._visibleElements[startReplacement + i].node.viewIndex = -1;
  308.  
  309.     // Building the new list will set the new elements' visible indices.
  310.     var newElements = [];
  311.     var toOpenElements = [];
  312.     this._buildVisibleSection(aContainer, newElements, toOpenElements, startReplacement);
  313.  
  314.     // actually update the visible list
  315.     this._visibleElements =
  316.       this._visibleElements.slice(0, startReplacement).concat(newElements)
  317.           .concat(this._visibleElements.slice(startReplacement + replaceCount,
  318.                                               this._visibleElements.length));
  319.  
  320.     // If the new area has a different size, we'll have to renumber the
  321.     // elements following the area.
  322.     if (replaceCount != newElements.length) {
  323.       for (i = startReplacement + newElements.length;
  324.            i < this._visibleElements.length; i ++) {
  325.         this._visibleElements[i].node.viewIndex = i;
  326.       }
  327.     }
  328.  
  329.     // now update the number of elements
  330.     selection.selectEventsSuppressed = true;
  331.     this._tree.beginUpdateBatch();
  332.  
  333.     if (replaceCount)
  334.       this._tree.rowCountChanged(startReplacement, -replaceCount);
  335.     if (newElements.length)
  336.       this._tree.rowCountChanged(startReplacement, newElements.length);
  337.  
  338.     if (!this._flatList) {
  339.       // now, open any containers that were persisted
  340.       for (var i = 0; i < toOpenElements.length; i++) {
  341.         var item = toOpenElements[i];
  342.         var parent = item.parent;
  343.         // avoid recursively opening containers
  344.         while (parent) {
  345.           if (parent.uri == item.uri)
  346.             break;
  347.           parent = parent.parent;
  348.         }
  349.         // if we don't have a parent, we made it all the way to the root
  350.         // and didn't find a match, so we can open our item
  351.         if (!parent && !item.containerOpen)
  352.           item.containerOpen = true;
  353.       }
  354.     }
  355.  
  356.     this._tree.endUpdateBatch();
  357.  
  358.     // restore selection
  359.     if (previouslySelectedNodes.length > 0) {
  360.       for (var i = 0; i < previouslySelectedNodes.length; i++) {
  361.         var nodeInfo = previouslySelectedNodes[i];
  362.         var index = nodeInfo.node.viewIndex;
  363.  
  364.         // if the same node was used (happens on sorting-changes),
  365.         // just use viewIndex
  366.         if (index == -1) { // otherwise, try to find an equal node
  367.           var itemId = PlacesUtils.getConcreteItemId(nodeInfo.node);
  368.           if (itemId != 1) { // bookmark-nodes in queries case
  369.             for (var j = 0; j < newElements.length && index == -1; j++) {
  370.               if (PlacesUtils.getConcreteItemId(newElements[j]) == itemId)
  371.                 index = newElements[j].viewIndex;
  372.             }
  373.           }
  374.           else { // history nodes
  375.             var uri = nodeInfo.node.uri;
  376.             if (uri) {
  377.               for (var j = 0; j < newElements.length && index == -1; j++) {
  378.                 if (newElements[j].uri == uri)
  379.                   index = newElements[j].viewIndex;
  380.               }
  381.             }
  382.           }
  383.         }
  384.         if (index != -1)
  385.           selection.rangedSelect(index, index, true);
  386.       }
  387.  
  388.       // if only one node was previously selected and there's no selection now,
  389.       // select the node at its old-viewIndex, if any
  390.       if (previouslySelectedNodes.length == 1 &&
  391.           selection.getRangeCount() == 0 &&
  392.           this._visibleElements.length > previouslySelectedNodes[0].oldIndex) {
  393.         selection.rangedSelect(previouslySelectedNodes[0].oldIndex,
  394.                                previouslySelectedNodes[0].oldIndex, true);
  395.       }
  396.     }
  397.     selection.selectEventsSuppressed = false;
  398.   },
  399.  
  400.   _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
  401.     var timeInMilliseconds = aTime / 1000; // PRTime is in microseconds
  402.     var timeObj = new Date(timeInMilliseconds);
  403.  
  404.     // Check if it is today and only display the time.  Only bother
  405.     // checking for today if it's within the last 24 hours, since
  406.     // computing midnight is not really cheap. Sometimes we may get dates
  407.     // in the future, so always show those.
  408.     var ago = new Date(Date.now() - timeInMilliseconds);
  409.     var dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
  410.     if (ago > -10000 && ago < (1000 * 24 * 60 * 60)) {
  411.       var midnight = new Date(timeInMilliseconds);
  412.       midnight.setHours(0);
  413.       midnight.setMinutes(0);
  414.       midnight.setSeconds(0);
  415.       midnight.setMilliseconds(0);
  416.  
  417.       if (timeInMilliseconds > midnight.getTime())
  418.         dateFormat = Ci.nsIScriptableDateFormat.dateFormatNone;
  419.     }
  420.  
  421.     return (this._dateService.FormatDateTime("", dateFormat,
  422.       Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
  423.       timeObj.getFullYear(), timeObj.getMonth() + 1,
  424.       timeObj.getDate(), timeObj.getHours(),
  425.       timeObj.getMinutes(), timeObj.getSeconds()));
  426.   },
  427.  
  428.   COLUMN_TYPE_UNKNOWN: 0,
  429.   COLUMN_TYPE_TITLE: 1,
  430.   COLUMN_TYPE_URI: 2,
  431.   COLUMN_TYPE_DATE: 3,
  432.   COLUMN_TYPE_VISITCOUNT: 4,
  433.   COLUMN_TYPE_KEYWORD: 5,
  434.   COLUMN_TYPE_DESCRIPTION: 6,
  435.   COLUMN_TYPE_DATEADDED: 7,
  436.   COLUMN_TYPE_LASTMODIFIED: 8,
  437.   COLUMN_TYPE_TAGS: 9,
  438.  
  439.   _getColumnType: function PTV__getColumnType(aColumn) {
  440.     var columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
  441.  
  442.     switch (columnType) {
  443.       case "title":
  444.         return this.COLUMN_TYPE_TITLE;
  445.       case "url":
  446.         return this.COLUMN_TYPE_URI;
  447.       case "date":
  448.         return this.COLUMN_TYPE_DATE;
  449.       case "visitCount":
  450.         return this.COLUMN_TYPE_VISITCOUNT;
  451.       case "keyword":
  452.         return this.COLUMN_TYPE_KEYWORD;
  453.       case "description":
  454.         return this.COLUMN_TYPE_DESCRIPTION;
  455.       case "dateAdded":
  456.         return this.COLUMN_TYPE_DATEADDED;
  457.       case "lastModified":
  458.         return this.COLUMN_TYPE_LASTMODIFIED;
  459.       case "tags":
  460.         return this.COLUMN_TYPE_TAGS;
  461.     }
  462.     return this.COLUMN_TYPE_UNKNOWN;
  463.   },
  464.  
  465.   _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) {
  466.     switch (aSortType) {
  467.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING:
  468.         return [this.COLUMN_TYPE_TITLE, false];
  469.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
  470.         return [this.COLUMN_TYPE_TITLE, true];
  471.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
  472.         return [this.COLUMN_TYPE_DATE, false];
  473.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
  474.         return [this.COLUMN_TYPE_DATE, true];
  475.       case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
  476.         return [this.COLUMN_TYPE_URI, false];
  477.       case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
  478.         return [this.COLUMN_TYPE_URI, true];
  479.       case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
  480.         return [this.COLUMN_TYPE_VISITCOUNT, false];
  481.       case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
  482.         return [this.COLUMN_TYPE_VISITCOUNT, true];
  483.       case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_ASCENDING:
  484.         return [this.COLUMN_TYPE_KEYWORD, false];
  485.       case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_DESCENDING:
  486.         return [this.COLUMN_TYPE_KEYWORD, true];
  487.       case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING:
  488.         if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
  489.           return [this.COLUMN_TYPE_DESCRIPTION, false];
  490.         break;
  491.       case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
  492.         if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
  493.           return [this.COLUMN_TYPE_DESCRIPTION, true];
  494.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
  495.         return [this.COLUMN_TYPE_DATEADDED, false];
  496.       case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING:
  497.         return [this.COLUMN_TYPE_DATEADDED, true];
  498.       case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING:
  499.         return [this.COLUMN_TYPE_LASTMODIFIED, false];
  500.       case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING:
  501.         return [this.COLUMN_TYPE_LASTMODIFIED, true];
  502.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING:
  503.         return [this.COLUMN_TYPE_TAGS, false];
  504.       case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING:
  505.         return [this.COLUMN_TYPE_TAGS, true];
  506.     }
  507.     return [this.COLUMN_TYPE_UNKNOWN, false];
  508.   },
  509.  
  510.   // nsINavHistoryResultViewer
  511.   itemInserted: function PTV_itemInserted(aParent, aItem, aNewIndex) {
  512.     if (!this._tree)
  513.       return;
  514.     if (!this._result)
  515.       throw Cr.NS_ERROR_UNEXPECTED;
  516.  
  517.     if (PlacesUtils.nodeIsSeparator(aItem) &&
  518.         this._result.sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
  519.       aItem.viewIndex = -1;
  520.       return;
  521.     }
  522.  
  523.     // update parent when inserting the first item because twisty may
  524.     // have changed
  525.     if (aParent.childCount == 1)
  526.       this.itemChanged(aParent);
  527.  
  528.     // compute the new view index of the item
  529.     var newViewIndex = -1;
  530.     if (aNewIndex == 0) {
  531.       // item is the first thing in our child list, it takes our index +1. Note
  532.       // that this computation still works if the parent is an invisible root
  533.       // node, because root_index + 1 = -1 + 1 = 0
  534.       newViewIndex = aParent.viewIndex + 1;
  535.     }
  536.     else {
  537.       // Here, we try to find the next visible element in the child list so we
  538.       // can set the new visible index to be right before that. Note that we
  539.       // have to search DOWN instead of up, because some siblings could have
  540.       // children themselves that would be in the way.
  541.       for (var i = aNewIndex + 1; i < aParent.childCount; i ++) {
  542.         var viewIndex = aParent.getChild(i).viewIndex;
  543.         if (viewIndex >= 0) {
  544.           // the view indices of subsequent children have not been shifted so
  545.           // the next item will have what should be our index
  546.           newViewIndex = viewIndex;
  547.           break;
  548.         }
  549.       }
  550.       if (newViewIndex < 0) {
  551.         // At the end of the child list without finding a visible sibling: This
  552.         // is a little harder because we don't know how many rows the last item
  553.         // in our list takes up (it could be a container with many children).
  554.         var prevChild = aParent.getChild(aNewIndex - 1);
  555.         newViewIndex = prevChild.viewIndex + this._countVisibleRowsForItem(prevChild);
  556.       }
  557.     }
  558.  
  559.     aItem.viewIndex = newViewIndex;
  560.     this._visibleElements.splice(newViewIndex, 0, 
  561.                                  { node: aItem, properties: null });
  562.     for (var i = newViewIndex + 1;
  563.          i < this._visibleElements.length; i ++) {
  564.       this._visibleElements[i].node.viewIndex = i;
  565.     }
  566.     this._tree.rowCountChanged(newViewIndex, 1);
  567.  
  568.     // Need to redraw the rows around this one because session boundaries
  569.     // may have changed. For example, if we add a page to a session, the
  570.     // previous page will need to be redrawn because it's session border
  571.     // will disappear.
  572.     if (this._showSessions) {
  573.       if (newViewIndex > 0)
  574.         this._tree.invalidateRange(newViewIndex - 1, newViewIndex - 1);
  575.       if (newViewIndex < this._visibleElements.length -1)
  576.         this._tree.invalidateRange(newViewIndex + 1, newViewIndex + 1);
  577.     }
  578.  
  579.     if (PlacesUtils.nodeIsContainer(aItem) && asContainer(aItem).containerOpen)
  580.       this._refreshVisibleSection(aItem);
  581.   },
  582.  
  583.   // this is used in itemRemoved and itemMoved to fix viewIndex values
  584.   // throw if the item has an invalid viewIndex
  585.   _fixViewIndexOnRemove: function PTV_fixViewIndexOnRemove(aItem, aParent) {
  586.     var oldViewIndex = aItem.viewIndex;
  587.     // this may have been a container, in which case it has a lot of rows
  588.     var count = this._countVisibleRowsForItem(aItem);
  589.  
  590.     if (oldViewIndex > this._visibleElements.length)
  591.       throw("Trying to remove an item with an invalid viewIndex");
  592.  
  593.     this._visibleElements.splice(oldViewIndex, count);
  594.     for (var i = oldViewIndex; i < this._visibleElements.length; i++)
  595.       this._visibleElements[i].node.viewIndex = i;
  596.  
  597.     this._tree.rowCountChanged(oldViewIndex, -count);
  598.  
  599.     // redraw parent because twisty may have changed
  600.     if (!aParent.hasChildren)
  601.       this.itemChanged(aParent);
  602.  
  603.     return;
  604.   },
  605.  
  606.   /**
  607.    * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being
  608.    * removed but the node it is collapsed with is not being removed (this then
  609.    * just swap out the removee with it's collapsing partner). The only time
  610.    * when we really remove things is when deleting URIs, which will apply to
  611.    * all collapsees. This function is called sometimes when resorting items.
  612.    * However, we won't do this when sorted by date because dates will never
  613.    * change for visits, and date sorting is the only time things are collapsed.
  614.    */
  615.   itemRemoved: function PTV_itemRemoved(aParent, aItem, aOldIndex) {
  616.     NS_ASSERT(this._result, "Got a notification but have no result!");
  617.     if (!this._tree)
  618.       return; // nothing to do
  619.  
  620.     var oldViewIndex = aItem.viewIndex;
  621.     if (oldViewIndex < 0)
  622.       return; // item was already invisible, nothing to do
  623.  
  624.     // if the item was exclusively selected, the node next to it will be
  625.     // selected
  626.     var selectNext = false;
  627.     var selection = this.selection;
  628.     if (selection.getRangeCount() == 1) {
  629.       var min = { }, max = { };
  630.       selection.getRangeAt(0, min, max);
  631.       if (min.value == max.value &&
  632.           this.nodeForTreeIndex(min.value) == aItem)
  633.         selectNext = true;
  634.     }
  635.  
  636.     // remove the item and fix viewIndex values
  637.     this._fixViewIndexOnRemove(aItem, aParent);
  638.  
  639.     // restore selection if the item was exclusively selected
  640.     if (!selectNext)
  641.       return;
  642.     // restore selection
  643.     if (this._visibleElements.length > oldViewIndex)
  644.       selection.rangedSelect(oldViewIndex, oldViewIndex, true);    
  645.     else if (this._visibleElements.length > 0) {
  646.       // if we removed the last child, we select the new last child if exists
  647.       selection.rangedSelect(this._visibleElements.length - 1,
  648.                              this._visibleElements.length - 1, true);
  649.     }
  650.   },
  651.  
  652.   /**
  653.    * Be careful, aOldIndex and aNewIndex specify the index in the
  654.    * corresponding parent nodes, not the visible indexes.
  655.    */
  656.   itemMoved:
  657.   function PTV_itemMoved(aItem, aOldParent, aOldIndex, aNewParent, aNewIndex) {
  658.     NS_ASSERT(this._result, "Got a notification but have no result!");
  659.     if (!this._tree)
  660.       return; // nothing to do
  661.  
  662.     var oldViewIndex = aItem.viewIndex;
  663.     if (oldViewIndex < 0)
  664.       return; // item was already invisible, nothing to do
  665.  
  666.     // this may have been a container, in which case it has a lot of rows
  667.     var count = this._countVisibleRowsForItem(aItem);
  668.  
  669.     // Persist selection state
  670.     var nodesToSelect = [];
  671.     var selection = this.selection;
  672.     var rc = selection.getRangeCount();
  673.     for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
  674.       var min = { }, max = { };
  675.       selection.getRangeAt(rangeIndex, min, max);
  676.       var lastIndex = Math.min(max.value, oldViewIndex + count -1);
  677.       if (min.value < oldViewIndex || min.value > lastIndex)
  678.         continue;
  679.  
  680.       for (var nodeIndex = min.value; nodeIndex <= lastIndex; nodeIndex++)
  681.         nodesToSelect.push(this._visibleElements[nodeIndex].node);
  682.     }
  683.     if (nodesToSelect.length > 0)
  684.       selection.selectEventsSuppressed = true;
  685.  
  686.     // remove item from the old position
  687.     this._fixViewIndexOnRemove(aItem, aOldParent);
  688.  
  689.     // insert the item into the new position
  690.     this.itemInserted(aNewParent, aItem, aNewIndex);
  691.  
  692.     // restore selection
  693.     if (nodesToSelect.length > 0) {
  694.       for (var i = 0; i < nodesToSelect.length; i++) {
  695.         var node = nodesToSelect[i];
  696.         var index = node.viewIndex;
  697.         selection.rangedSelect(index, index, true);
  698.       }
  699.       selection.selectEventsSuppressed = false;
  700.     }
  701.   },
  702.  
  703.   /**
  704.    * Be careful, the parameter 'aIndex' here specifies the index in the parent
  705.    * node of the item, not the visible index.
  706.    *
  707.    * This is called from the result when the item is replaced, but this object
  708.    * calls this function internally also when duplicate collapsing changes. In
  709.    * this case, aIndex will be 0, so we should be careful not to use the value.
  710.    */
  711.   itemReplaced:
  712.   function PTV_itemReplaced(aParent, aOldItem, aNewItem, aIndexDoNotUse) {
  713.     if (!this._tree)
  714.       return;
  715.  
  716.     var viewIndex = aOldItem.viewIndex;
  717.     aNewItem.viewIndex = viewIndex;
  718.     if (viewIndex >= 0 &&
  719.         viewIndex < this._visibleElements.length) {
  720.       this._visibleElements[viewIndex].node = aNewItem;
  721.       this._visibleElements[viewIndex].properties = null;
  722.     }
  723.     aOldItem.viewIndex = -1;
  724.     this._tree.invalidateRow(viewIndex);
  725.   },
  726.  
  727.   itemChanged: function PTV_itemChanged(aItem) {
  728.     NS_ASSERT(this._result, "Got a notification but have no result!");
  729.     var viewIndex = aItem.viewIndex;
  730.     if (this._tree && viewIndex >= 0)
  731.       this._tree.invalidateRow(viewIndex);
  732.   },
  733.  
  734.   containerOpened: function PTV_containerOpened(aItem) {
  735.     this.invalidateContainer(aItem);
  736.   },
  737.  
  738.   containerClosed: function PTV_containerClosed(aItem) {
  739.     this.invalidateContainer(aItem);
  740.   },
  741.  
  742.   invalidateContainer: function PTV_invalidateContainer(aItem) {
  743.     NS_ASSERT(this._result, "Got a notification but have no result!");
  744.     if (!this._tree)
  745.       return; // nothing to do, container is not visible
  746.     var viewIndex = aItem.viewIndex;
  747.     if (viewIndex >= this._visibleElements.length) {
  748.       // be paranoid about visible indices since others can change it
  749.       throw Cr.NS_ERROR_UNEXPECTED;
  750.     }
  751.     this._refreshVisibleSection(aItem);
  752.   },
  753.  
  754.   invalidateAll: function PTV_invalidateAll() {
  755.     NS_ASSERT(this._result, "Got message but don't have a result!");
  756.     if (!this._tree)
  757.       return;
  758.  
  759.     var oldRowCount = this._visibleElements.length;
  760.  
  761.     // update flat list to new contents
  762.     this._buildVisibleList();
  763.   },
  764.  
  765.   sortingChanged: function PTV__sortingChanged(aSortingMode) {
  766.     if (!this._tree || !this._result)
  767.       return;
  768.  
  769.     // depending on the sort mode, certain commands may be disabled
  770.     window.updateCommands("sort");
  771.  
  772.     var columns = this._tree.columns;
  773.  
  774.     // clear old sorting indicator
  775.     var sortedColumn = columns.getSortedColumn();
  776.     if (sortedColumn)
  777.       sortedColumn.element.removeAttribute("sortDirection");
  778.  
  779.     // set new sorting indicator by looking through all columns for ours
  780.     if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE)
  781.       return;
  782.     var [desiredColumn, desiredIsDescending] =
  783.       this._sortTypeToColumnType(aSortingMode);
  784.     var colCount = columns.count;
  785.     for (var i = 0; i < colCount; i ++) {
  786.       var column = columns.getColumnAt(i);
  787.       if (this._getColumnType(column) == desiredColumn) {
  788.         // found our desired one, set
  789.         if (desiredIsDescending)
  790.           column.element.setAttribute("sortDirection", "descending");
  791.         else
  792.           column.element.setAttribute("sortDirection", "ascending");
  793.         break;
  794.       }
  795.     }
  796.   },
  797.  
  798.   get result() {
  799.     return this._result;
  800.   },
  801.  
  802.   set result(val) {
  803.     // some methods (e.g. getURLsFromContainer) temporarily null out the
  804.     // viewer when they do temporary changes to the view, this does _not_
  805.     // call setResult(null), but then, we're called again with the result
  806.     // object which is already set for this viewer. At that point,
  807.     // we should do nothing.
  808.     if (this._result != val) {
  809.       this._result = val;
  810.       this._finishInit();
  811.     }
  812.     return val;
  813.   },
  814.  
  815.   nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) {
  816.     if (aIndex > this._visibleElements.length)
  817.       throw Cr.NS_ERROR_INVALID_ARG;
  818.  
  819.     return this._visibleElements[aIndex].node;
  820.   },
  821.  
  822.   treeIndexForNode: function PTV_treeNodeForIndex(aNode) {
  823.     var viewIndex = aNode.viewIndex;
  824.     if (viewIndex < 0)
  825.       return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
  826.  
  827.     NS_ASSERT(this._visibleElements[viewIndex].node == aNode,
  828.               "Node's visible index and array out of sync");
  829.     return viewIndex;
  830.   },
  831.  
  832.   _getResourceForNode: function PTV_getResourceForNode(aNode)
  833.   {
  834.     var uri = aNode.uri;
  835.     NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
  836.     return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
  837.   },
  838.  
  839.   // nsITreeView
  840.   get rowCount() {
  841.     return this._visibleElements.length;
  842.   },
  843.  
  844.   get selection() {
  845.     return this._selection;
  846.   },
  847.  
  848.   set selection(val) {
  849.     return this._selection = val;
  850.   },
  851.  
  852.   getRowProperties: function PTV_getRowProperties(aRow, aProperties) {
  853.     this._ensureValidRow(aRow);
  854.  
  855.     // Handle properties for session information.
  856.     if (!this._showSessions)
  857.       return;
  858.  
  859.     var status = this._getRowSessionStatus(aRow);
  860.     switch (status) {
  861.       case this.SESSION_STATUS_NONE:
  862.         break;
  863.       case this.SESSION_STATUS_START:
  864.         aProperties.AppendElement(this._getAtomFor("session-start"));
  865.         break;
  866.       case this.SESSION_STATUS_CONTINUE:
  867.         aProperties.AppendElement(this._getAtomFor("session-continue"));
  868.         break
  869.     }
  870.   },
  871.  
  872.   getCellProperties: function PTV_getCellProperties(aRow, aColumn, aProperties) {
  873.     this._ensureValidRow(aRow);
  874.  
  875.     // for anonid-trees, we need to add the column-type manually
  876.     var columnType = aColumn.element.getAttribute("anonid");
  877.     if (columnType)
  878.       aProperties.AppendElement(this._getAtomFor(columnType));
  879.     else
  880.       var columnType = aColumn.id;
  881.  
  882.     if (columnType != "title")
  883.       return;
  884.  
  885.     var node = this._visibleElements[aRow].node;
  886.     var properties = this._visibleElements[aRow].properties;
  887.  
  888.     if (!properties) {
  889.       properties = new Array();
  890.       var nodeType = node.type;
  891.       if (PlacesUtils.containerTypes.indexOf(nodeType) != -1) {
  892.         var itemId = node.itemId;
  893.         if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
  894.           properties.push(this._getAtomFor("query"));
  895.           if (PlacesUtils.nodeIsTagQuery(node))
  896.             properties.push(this._getAtomFor("tagContainer"));
  897.           else if (PlacesUtils.nodeIsDay(node))
  898.             properties.push(this._getAtomFor("dayContainer"));
  899.           else if (PlacesUtils.nodeIsHost(node))
  900.             properties.push(this._getAtomFor("hostContainer"));
  901.         }
  902.         else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
  903.                  nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
  904.           if (PlacesUtils.annotations.itemHasAnnotation(itemId,
  905.                                                         LMANNO_FEEDURI))
  906.             properties.push(this._getAtomFor("livemark"));
  907.         }
  908.  
  909.         if (itemId != -1) {
  910.           var oqAnno;
  911.           try {
  912.             oqAnno = PlacesUtils.annotations.getItemAnnotation(itemId, ORGANIZER_QUERY_ANNO);
  913.             properties.push(this._getAtomFor("OrganizerQuery_" + oqAnno));
  914.           }
  915.           catch (ex) { /* not a special query */ }
  916.         }
  917.       }
  918.       else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
  919.         properties.push(this._getAtomFor("separator"));
  920.       else if (itemId != -1) { // bookmark nodes
  921.         if (PlacesUtils.nodeIsLivemarkContainer(node.parent))
  922.           properties.push(this._getAtomFor("livemarkItem"));
  923.       }
  924.  
  925.       this._visibleElements[aRow].properties = properties;
  926.     }
  927.     for (var i = 0; i < properties.length; i++)
  928.       aProperties.AppendElement(properties[i]);
  929.   },
  930.  
  931.   getColumnProperties: function(aColumn, aProperties) { },
  932.  
  933.   isContainer: function PTV_isContainer(aRow) {
  934.     this._ensureValidRow(aRow);
  935.  
  936.     var node = this._visibleElements[aRow].node;
  937.     if (PlacesUtils.nodeIsContainer(node)) {
  938.       // the root node is always expandable
  939.       if (!node.parent)
  940.         return true;
  941.  
  942.       // treat non-expandable childless queries as non-containers
  943.       if (PlacesUtils.nodeIsQuery(node)) {
  944.         var parent = node.parent;
  945.         if((PlacesUtils.nodeIsQuery(parent) ||
  946.             PlacesUtils.nodeIsFolder(parent)) &&
  947.            !node.hasChildren)
  948.           return asQuery(parent).queryOptions.expandQueries;
  949.       }
  950.       return true;
  951.     }
  952.     return false;
  953.   },
  954.  
  955.   isContainerOpen: function PTV_isContainerOpen(aRow) {
  956.     if (this._flatList)
  957.       return false;
  958.  
  959.     this._ensureValidRow(aRow);
  960.     if (!PlacesUtils.nodeIsContainer(this._visibleElements[aRow].node))
  961.       throw Cr.NS_ERROR_INVALID_ARG;
  962.  
  963.     return this._visibleElements[aRow].node.containerOpen;
  964.   },
  965.  
  966.   isContainerEmpty: function PTV_isContainerEmpty(aRow) {
  967.     if (this._flatList)
  968.       return true;
  969.  
  970.     this._ensureValidRow(aRow);
  971.  
  972.     if (!PlacesUtils.nodeIsContainer(this._visibleElements[aRow].node))
  973.       throw Cr.NS_ERROR_INVALID_ARG;
  974.  
  975.     return !this._visibleElements[aRow].node.hasChildren;
  976.   },
  977.  
  978.   isSeparator: function PTV_isSeparator(aRow) {
  979.     this._ensureValidRow(aRow);
  980.     return PlacesUtils.nodeIsSeparator(this._visibleElements[aRow].node);
  981.   },
  982.  
  983.   isSorted: function PTV_isSorted() {
  984.     return this._result.sortingMode !=
  985.            Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_NONE;
  986.   },
  987.  
  988.   canDrop: function PTV_canDrop(aRow, aOrientation) {
  989.     if (!this._result)
  990.       throw Cr.NS_ERROR_UNEXPECTED;
  991.  
  992.     var node = aRow != -1 ? this.nodeForTreeIndex(aRow) : this._result.root;
  993.  
  994.     if (aOrientation == Ci.nsITreeView.DROP_ON) {
  995.       // The user cannot drop an item into itself or a read-only container
  996.       var dragService =  Cc["@mozilla.org/widget/dragservice;1"].
  997.                          getService(Ci.nsIDragService);
  998.       var dragSession = dragService.getCurrentSession();
  999.       var elt = dragSession.sourceNode.parentNode;
  1000.       if (elt.localName == "tree" && elt.view == this &&
  1001.           this.selection.isSelected(aRow))
  1002.         return false;
  1003.     }
  1004.   
  1005.     var ip = this._getInsertionPoint(aRow, aOrientation);
  1006.     return ip && PlacesControllerDragHelper.canDrop(ip);
  1007.   },
  1008.  
  1009.   // XXXmano: these two are copied over from tree.xml, to fix this we need to
  1010.   // either add a helper to PlacesUtils or keep it here and add insertionPoint
  1011.   // to the view interface.
  1012.   _disallowInsertion: function PTV__disallowInsertion(aContainer) {
  1013.     // allow dropping into Tag containers
  1014.     if (PlacesUtils.nodeIsTagQuery(aContainer))
  1015.       return false;
  1016.     // Disallow insertion of items under readonly folders
  1017.     return (!PlacesUtils.nodeIsFolder(aContainer) ||
  1018.             PlacesUtils.nodeIsReadOnly(aContainer));
  1019.   },
  1020.  
  1021.   _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) {
  1022.     var container = this._result.root;
  1023.     var dropNearItemId = -1;
  1024.     // When there's no selection, assume the container is the container
  1025.     // the view is populated from (i.e. the result's itemId).
  1026.     if (index != -1) {
  1027.       var lastSelected = this.nodeForTreeIndex(index);
  1028.       if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
  1029.         // If the last selected item is an open container, append _into_
  1030.         // it, rather than insert adjacent to it. 
  1031.         container = lastSelected;
  1032.         index = -1;
  1033.       }
  1034.       else if (!this._disallowInsertion(lastSelected) &&
  1035.                lastSelected.containerOpen &&
  1036.                orientation == Ci.nsITreeView.DROP_AFTER &&
  1037.                lastSelected.hasChildren) {
  1038.         // If the last selected item is an open container and the user is
  1039.         // trying to drag into it as a first item, really insert into it.
  1040.         container = lastSelected;
  1041.         orientation = Ci.nsITreeView.DROP_BEFORE;
  1042.         index = 0;
  1043.       }
  1044.       else {
  1045.         // Use the last-selected node's container unless the root node
  1046.         // is selected, in which case we use the root node itself as the
  1047.         // insertion point.
  1048.         container = lastSelected.parent || container;
  1049.  
  1050.         // avoid the potentially expensive call to getIndexOfNode() 
  1051.         // if we know this container doesn't allow insertion
  1052.         if (this._disallowInsertion(container))
  1053.           return null;
  1054.  
  1055.         var queryOptions = asQuery(this._result.root).queryOptions;
  1056.         if (queryOptions.sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
  1057.           // If we are within a sorted view, insert at the end
  1058.           index = -1;
  1059.         }
  1060.         else if (queryOptions.excludeItems ||
  1061.                  queryOptions.excludeQueries ||
  1062.                  queryOptions.excludeReadOnlyFolders) {
  1063.           // Some item may be invisible, insert near last selected one.
  1064.           // We don't replace index here to avoid requests to the db,
  1065.           // instead it will be calculated later by the controller.
  1066.           index = -1;
  1067.           dropNearItemId = lastSelected.itemId;
  1068.         }
  1069.         else {
  1070.           var lsi = PlacesUtils.getIndexOfNode(lastSelected);
  1071.           index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
  1072.         }
  1073.       }
  1074.     }
  1075.  
  1076.     if (this._disallowInsertion(container))
  1077.       return null;
  1078.  
  1079.     return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
  1080.                               index, orientation,
  1081.                               PlacesUtils.nodeIsTagQuery(container),
  1082.                               dropNearItemId);
  1083.   },
  1084.  
  1085.   drop: function PTV_drop(aRow, aOrientation) {
  1086.     // We are responsible for translating the |index| and |orientation| 
  1087.     // parameters into a container id and index within the container, 
  1088.     // since this information is specific to the tree view.
  1089.     var ip = this._getInsertionPoint(aRow, aOrientation);
  1090.     if (!ip)
  1091.       throw Cr.NS_ERROR_NOT_AVAILABLE;
  1092.     PlacesControllerDragHelper.onDrop(ip);
  1093.   },
  1094.  
  1095.   getParentIndex: function PTV_getParentIndex(aRow) {
  1096.     this._ensureValidRow(aRow);
  1097.     var parent = this._visibleElements[aRow].node.parent;
  1098.     if (!parent || parent.viewIndex < 0)
  1099.       return -1;
  1100.  
  1101.     return parent.viewIndex;
  1102.   },
  1103.  
  1104.   hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) {
  1105.     this._ensureValidRow(aRow);
  1106.     if (aRow == this._visibleElements.length -1) {
  1107.       // this is the last thing in the list -> no next sibling
  1108.       return false;
  1109.     }
  1110.  
  1111.     var thisLevel = this._visibleElements[aRow].node.indentLevel;
  1112.     for (var i = aAfterIndex + 1; i < this._visibleElements.length; ++i) {
  1113.       var nextLevel = this._visibleElements[i].node.indentLevel;
  1114.       if (nextLevel == thisLevel)
  1115.         return true;
  1116.       if (nextLevel < thisLevel)
  1117.         break;
  1118.     }
  1119.     return false;
  1120.   },
  1121.  
  1122.   getLevel: function PTV_getLevel(aRow) {
  1123.     this._ensureValidRow(aRow);
  1124.  
  1125.     // Level is 0 for items at the root level, 1 for its children and so on.
  1126.     // If we don't show the result's root node, the level is simply the node's
  1127.     // indentLevel; if we do, it is the node's indentLevel increased by 1.
  1128.     // That is because nsNavHistoryResult uses -1 as the indent level for the
  1129.     // root node regardless of our internal showRoot state.
  1130.     if (this._showRoot)
  1131.       return this._visibleElements[aRow].node.indentLevel + 1;
  1132.  
  1133.     return this._visibleElements[aRow].node.indentLevel;
  1134.   },
  1135.  
  1136.   getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
  1137.     this._ensureValidRow(aRow);
  1138.  
  1139.     // only the title column has an image
  1140.     if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
  1141.       return "";
  1142.  
  1143.     var node = this._visibleElements[aRow].node;
  1144.     var icon = node.icon;
  1145.     if (icon)
  1146.       return icon.spec;
  1147.     return "";
  1148.   },
  1149.  
  1150.   getProgressMode: function(aRow, aColumn) { },
  1151.   getCellValue: function(aRow, aColumn) { },
  1152.  
  1153.   getCellText: function PTV_getCellText(aRow, aColumn) {
  1154.     this._ensureValidRow(aRow);
  1155.  
  1156.     var node = this._visibleElements[aRow].node;
  1157.     var columnType = this._getColumnType(aColumn);
  1158.     switch (columnType) {
  1159.       case this.COLUMN_TYPE_TITLE:
  1160.         // normally, this is just the title, but we don't want empty items in
  1161.         // the tree view so return a special string if the title is empty.
  1162.         // Do it here so that callers can still get at the 0 length title
  1163.         // if they go through the "result" API.
  1164.         if (PlacesUtils.nodeIsSeparator(node))
  1165.           return "";
  1166.         return PlacesUIUtils.getBestTitle(node);
  1167.       case this.COLUMN_TYPE_TAGS:
  1168.         return node.tags;
  1169.       case this.COLUMN_TYPE_URI:
  1170.         if (PlacesUtils.nodeIsURI(node))
  1171.           return node.uri;
  1172.         return "";
  1173.       case this.COLUMN_TYPE_DATE:
  1174.         if (node.time == 0 || !PlacesUtils.nodeIsURI(node)) {
  1175.           // hosts and days shouldn't have a value for the date column.
  1176.           // Actually, you could argue this point, but looking at the
  1177.           // results, seeing the most recently visited date is not what
  1178.           // I expect, and gives me no information I know how to use.
  1179.           // Only show this for URI-based items.
  1180.           return "";
  1181.         }
  1182.         if (this._getRowSessionStatus(aRow) != this.SESSION_STATUS_CONTINUE)
  1183.           return this._convertPRTimeToString(node.time);
  1184.         return "";
  1185.       case this.COLUMN_TYPE_VISITCOUNT:
  1186.         return node.accessCount;
  1187.       case this.COLUMN_TYPE_KEYWORD:
  1188.         if (PlacesUtils.nodeIsBookmark(node))
  1189.           return PlacesUtils.bookmarks.getKeywordForBookmark(node.itemId);
  1190.         return "";
  1191.       case this.COLUMN_TYPE_DESCRIPTION:
  1192.         const annos = PlacesUtils.annotations;
  1193.         if (annos.itemHasAnnotation(node.itemId, DESCRIPTION_ANNO))
  1194.           return annos.getItemAnnotation(node.itemId, DESCRIPTION_ANNO)
  1195.         return "";
  1196.       case this.COLUMN_TYPE_DATEADDED:
  1197.         if (node.dateAdded)
  1198.           return this._convertPRTimeToString(node.dateAdded);
  1199.         return "";
  1200.       case this.COLUMN_TYPE_LASTMODIFIED:
  1201.         if (node.lastModified)
  1202.           return this._convertPRTimeToString(node.lastModified);
  1203.         return "";
  1204.     }
  1205.     return "";
  1206.   },
  1207.  
  1208.   setTree: function PTV_setTree(aTree) {
  1209.     var hasOldTree = this._tree != null;
  1210.     this._tree = aTree;
  1211.  
  1212.     // do this before detaching from result when there is no tree.
  1213.     // This ensures that the visible indices of the elements in the
  1214.     // result have been set to -1
  1215.     this._finishInit();
  1216.  
  1217.     if (!aTree && hasOldTree && this._result) {
  1218.       // detach from result when we are detaching from the tree.
  1219.       // This breaks the reference cycle between us and the result.
  1220.       this._result.viewer = null;
  1221.     }
  1222.   },
  1223.  
  1224.   toggleOpenState: function PTV_toggleOpenState(aRow) {
  1225.     if (!this._result)
  1226.       throw Cr.NS_ERROR_UNEXPECTED;
  1227.     this._ensureValidRow(aRow);
  1228.  
  1229.     var node = this._visibleElements[aRow].node;
  1230.     if (!PlacesUtils.nodeIsContainer(node))
  1231.       return; // not a container, nothing to do
  1232.  
  1233.     if (this._flatList && this._openContainerCallback) {
  1234.       this._openContainerCallback(node);
  1235.       return;
  1236.     }
  1237.  
  1238.     var resource = this._getResourceForNode(node);
  1239.     if (resource) {
  1240.       const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
  1241.       const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
  1242.  
  1243.       if (node.containerOpen)
  1244.         PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
  1245.       else
  1246.         PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
  1247.     }
  1248.  
  1249.     node.containerOpen = !node.containerOpen;
  1250.   },
  1251.  
  1252.   cycleHeader: function PTV_cycleHeader(aColumn) {
  1253.     if (!this._result)
  1254.       throw Cr.NS_ERROR_UNEXPECTED;
  1255.  
  1256.     // Sometimes you want a tri-state sorting, and sometimes you don't. This
  1257.     // rule allows tri-state sorting when the root node is a folder. This will
  1258.     // catch the most common cases. When you are looking at folders, you want
  1259.     // the third state to reset the sorting to the natural bookmark order. When
  1260.     // you are looking at history, that third state has no meaning so we try
  1261.     // to disallow it.
  1262.     //
  1263.     // The problem occurs when you have a query that results in bookmark
  1264.     // folders. One example of this is the subscriptions view. In these cases,
  1265.     // this rule doesn't allow you to sort those sub-folders by their natural
  1266.     // order.
  1267.     var allowTriState = PlacesUtils.nodeIsFolder(this._result.root);
  1268.  
  1269.     var oldSort = this._result.sortingMode;
  1270.     var oldSortingAnnotation = this._result.sortingAnnotation;
  1271.     var newSort;
  1272.     var newSortingAnnotation = "";
  1273.     const NHQO = Ci.nsINavHistoryQueryOptions;
  1274.     var columnType = this._getColumnType(aColumn);
  1275.     switch (columnType) {
  1276.       case this.COLUMN_TYPE_TITLE:
  1277.         if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING)
  1278.           newSort = NHQO.SORT_BY_TITLE_DESCENDING;
  1279.         else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING)
  1280.           newSort = NHQO.SORT_BY_NONE;
  1281.         else
  1282.           newSort = NHQO.SORT_BY_TITLE_ASCENDING;
  1283.  
  1284.         break;
  1285.       case this.COLUMN_TYPE_URI:
  1286.         if (oldSort == NHQO.SORT_BY_URI_ASCENDING)
  1287.           newSort = NHQO.SORT_BY_URI_DESCENDING;
  1288.         else if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING)
  1289.           newSort = NHQO.SORT_BY_NONE;
  1290.         else
  1291.           newSort = NHQO.SORT_BY_URI_ASCENDING;
  1292.  
  1293.         break;
  1294.       case this.COLUMN_TYPE_DATE:
  1295.         if (oldSort == NHQO.SORT_BY_DATE_ASCENDING)
  1296.           newSort = NHQO.SORT_BY_DATE_DESCENDING;
  1297.         else if (allowTriState &&
  1298.                  oldSort == NHQO.SORT_BY_DATE_DESCENDING)
  1299.           newSort = NHQO.SORT_BY_NONE;
  1300.         else
  1301.           newSort = NHQO.SORT_BY_DATE_ASCENDING;
  1302.  
  1303.         break;
  1304.       case this.COLUMN_TYPE_VISITCOUNT:
  1305.         // visit count default is unusual because we sort by descending
  1306.         // by default because you are most likely to be looking for
  1307.         // highly visited sites when you click it
  1308.         if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
  1309.           newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
  1310.         else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING)
  1311.           newSort = NHQO.SORT_BY_NONE;
  1312.         else
  1313.           newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
  1314.  
  1315.         break;
  1316.       case this.COLUMN_TYPE_KEYWORD:
  1317.         if (oldSort == NHQO.SORT_BY_KEYWORD_ASCENDING)
  1318.           newSort = NHQO.SORT_BY_KEYWORD_DESCENDING;
  1319.         else if (allowTriState && oldSort == NHQO.SORT_BY_KEYWORD_DESCENDING)
  1320.           newSort = NHQO.SORT_BY_NONE;
  1321.         else
  1322.           newSort = NHQO.SORT_BY_KEYWORD_ASCENDING;
  1323.  
  1324.         break;
  1325.       case this.COLUMN_TYPE_DESCRIPTION:
  1326.         if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING &&
  1327.             oldSortingAnnotation == DESCRIPTION_ANNO) {
  1328.           newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING;
  1329.           newSortingAnnotation = DESCRIPTION_ANNO;
  1330.         }
  1331.         else if (allowTriState &&
  1332.                  oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING &&
  1333.                  oldSortingAnnotation == DESCRIPTION_ANNO)
  1334.           newSort = NHQO.SORT_BY_NONE;
  1335.         else {
  1336.           newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING;
  1337.           newSortingAnnotation = DESCRIPTION_ANNO;
  1338.         }
  1339.  
  1340.         break;
  1341.       case this.COLUMN_TYPE_DATEADDED:
  1342.         if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING)
  1343.           newSort = NHQO.SORT_BY_DATEADDED_DESCENDING;
  1344.         else if (allowTriState &&
  1345.                  oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING)
  1346.           newSort = NHQO.SORT_BY_NONE;
  1347.         else
  1348.           newSort = NHQO.SORT_BY_DATEADDED_ASCENDING;
  1349.  
  1350.         break;
  1351.       case this.COLUMN_TYPE_LASTMODIFIED:
  1352.         if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING)
  1353.           newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING;
  1354.         else if (allowTriState &&
  1355.                  oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING)
  1356.           newSort = NHQO.SORT_BY_NONE;
  1357.         else
  1358.           newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
  1359.  
  1360.         break;
  1361.       case this.COLUMN_TYPE_TAGS:
  1362.         if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING)
  1363.           newSort = NHQO.SORT_BY_TAGS_DESCENDING;
  1364.         else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING)
  1365.           newSort = NHQO.SORT_BY_NONE;
  1366.         else
  1367.           newSort = NHQO.SORT_BY_TAGS_ASCENDING;
  1368.  
  1369.         break;
  1370.       default:
  1371.         throw Cr.NS_ERROR_INVALID_ARG;
  1372.     }
  1373.     this._result.sortingAnnotation = newSortingAnnotation;
  1374.     this._result.sortingMode = newSort;
  1375.   },
  1376.  
  1377.   isEditable: function PTV_isEditable(aRow, aColumn) {
  1378.     // At this point we only support editing the title field.
  1379.     if (aColumn.index != 0)
  1380.       return false;
  1381.  
  1382.     var node = this.nodeForTreeIndex(aRow);
  1383.     if (!PlacesUtils.nodeIsReadOnly(node) &&
  1384.         (PlacesUtils.nodeIsFolder(node) ||
  1385.          (PlacesUtils.nodeIsBookmark(node) &&
  1386.           !PlacesUtils.nodeIsLivemarkItem(node))))
  1387.       return true;
  1388.  
  1389.     return false;
  1390.   },
  1391.  
  1392.   setCellText: function PTV_setCellText(aRow, aColumn, aText) {
  1393.     // we may only get here if the cell is editable
  1394.     var node = this.nodeForTreeIndex(aRow);
  1395.     if (node.title != aText) {
  1396.       var txn = PlacesUIUtils.ptm.editItemTitle(node.itemId, aText);
  1397.       PlacesUIUtils.ptm.doTransaction(txn);
  1398.     }
  1399.   },
  1400.  
  1401.   selectionChanged: function() { },
  1402.   cycleCell: function PTV_cycleCell(aRow, aColumn) { },
  1403.   isSelectable: function(aRow, aColumn) { return false; },
  1404.   performAction: function(aAction) { },
  1405.   performActionOnRow: function(aAction, aRow) { },
  1406.   performActionOnCell: function(aAction, aRow, aColumn) { }
  1407. };
  1408.  
  1409. function PlacesTreeView(aShowRoot, aFlatList, aOnOpenFlatContainer) {
  1410.   if (aShowRoot && aFlatList)
  1411.     throw("Flat-list mode is not supported when show-root is set");
  1412.  
  1413.   this._tree = null;
  1414.   this._result = null;
  1415.   this._showSessions = false;
  1416.   this._selection = null;
  1417.   this._visibleElements = [];
  1418.   this._showRoot = aShowRoot;
  1419.   this._flatList = aFlatList;
  1420.   this._openContainerCallback = aOnOpenFlatContainer;
  1421. }
  1422.