diff options
Diffstat (limited to 'browser/components/places/content/treeView.js')
-rw-r--r-- | browser/components/places/content/treeView.js | 1728 |
1 files changed, 0 insertions, 1728 deletions
diff --git a/browser/components/places/content/treeView.js b/browser/components/places/content/treeView.js deleted file mode 100644 index 181bb5404..000000000 --- a/browser/components/places/content/treeView.js +++ /dev/null @@ -1,1728 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); - -const PTV_interfaces = [Ci.nsITreeView, - Ci.nsINavHistoryResultObserver, - Ci.nsINavHistoryResultTreeViewer, - Ci.nsISupportsWeakReference]; - -function PlacesTreeView(aFlatList, aOnOpenFlatContainer, aController) { - this._tree = null; - this._result = null; - this._selection = null; - this._rootNode = null; - this._rows = []; - this._flatList = aFlatList; - this._openContainerCallback = aOnOpenFlatContainer; - this._controller = aController; -} - -PlacesTreeView.prototype = { - get wrappedJSObject() { - return this; - }, - - __xulStore: null, - get _xulStore() { - if (!this.__xulStore) { - this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore); - } - return this.__xulStore; - }, - - QueryInterface: XPCOMUtils.generateQI(PTV_interfaces), - - // Bug 761494: - // ---------- - // Some addons use methods from nsINavHistoryResultObserver and - // nsINavHistoryResultTreeViewer, without QIing to these interfaces first. - // That's not a problem when the view is retrieved through the - // <tree>.view getter (which returns the wrappedJSObject of this object), - // it raises an issue when the view retrieved through the treeBoxObject.view - // getter. Thus, to avoid breaking addons, the interfaces are prefetched. - classInfo: XPCOMUtils.generateCI({ interfaces: PTV_interfaces }), - - /** - * This is called once both the result and the tree are set. - */ - _finishInit: function PTV__finishInit() { - let selection = this.selection; - if (selection) - selection.selectEventsSuppressed = true; - - if (!this._rootNode.containerOpen) { - // This triggers containerStateChanged which then builds the visible - // section. - this._rootNode.containerOpen = true; - } - else - this.invalidateContainer(this._rootNode); - - // "Activate" the sorting column and update commands. - this.sortingChanged(this._result.sortingMode); - - if (selection) - selection.selectEventsSuppressed = false; - }, - - /** - * Plain Container: container result nodes which may never include sub - * hierarchies. - * - * When the rows array is constructed, we don't set the children of plain - * containers. Instead, we keep placeholders for these children. We then - * build these children lazily as the tree asks us for information about each - * row. Luckily, the tree doesn't ask about rows outside the visible area. - * - * @see _getNodeForRow and _getRowForNode for the actual magic. - * - * @note It's guaranteed that all containers are listed in the rows - * elements array. It's also guaranteed that separators (if they're not - * filtered, see below) are listed in the visible elements array, because - * bookmark folders are never built lazily, as described above. - * - * @param aContainer - * A container result node. - * - * @return true if aContainer is a plain container, false otherwise. - */ - _isPlainContainer: function PTV__isPlainContainer(aContainer) { - // Livemarks are always plain containers. - if (this._controller.hasCachedLivemarkInfo(aContainer)) - return true; - - // We don't know enough about non-query containers. - if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode)) - return false; - - switch (aContainer.queryOptions.resultType) { - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY: - return false; - } - - // If it's a folder, it's not a plain container. - let nodeType = aContainer.type; - return nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER && - nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT; - }, - - /** - * Gets the row number for a given node. Assumes that the given node is - * visible (i.e. it's not an obsolete node). - * - * @param aNode - * A result node. Do not pass an obsolete node, or any - * node which isn't supposed to be in the tree (e.g. separators in - * sorted trees). - * @param [optional] aForceBuild - * @see _isPlainContainer. - * If true, the row will be computed even if the node still isn't set - * in our rows array. - * @param [optional] aParentRow - * The row of aNode's parent. Ignored for the root node. - * @param [optional] aNodeIndex - * The index of aNode in its parent. Only used if aParentRow is - * set too. - * - * @throws if aNode is invisible. - * @note If aParentRow and aNodeIndex are passed and parent is a plain - * container, this method will just return a calculated row value, without - * making assumptions on existence of the node at that position. - * @return aNode's row if it's in the rows list or if aForceBuild is set, -1 - * otherwise. - */ - _getRowForNode: - function PTV__getRowForNode(aNode, aForceBuild, aParentRow, aNodeIndex) { - if (aNode == this._rootNode) - throw new Error("The root node is never visible"); - - // A node is removed form the view either if it has no parent or if its - // root-ancestor is not the root node (in which case that's the node - // for which nodeRemoved was called). - let ancestors = Array.from(PlacesUtils.nodeAncestors(aNode)); - if (ancestors.length == 0 || - ancestors[ancestors.length - 1] != this._rootNode) { - throw new Error("Removed node passed to _getRowForNode"); - } - - // Ensure that the entire chain is open, otherwise that node is invisible. - for (let ancestor of ancestors) { - if (!ancestor.containerOpen) - throw new Error("Invisible node passed to _getRowForNode"); - } - - // Non-plain containers are initially built with their contents. - let parent = aNode.parent; - let parentIsPlain = this._isPlainContainer(parent); - if (!parentIsPlain) { - if (parent == this._rootNode) - return this._rows.indexOf(aNode); - - return this._rows.indexOf(aNode, aParentRow); - } - - let row = -1; - let useNodeIndex = typeof(aNodeIndex) == "number"; - if (parent == this._rootNode) { - if (aNode instanceof Ci.nsINavHistoryResultNode) { - row = useNodeIndex ? aNodeIndex : this._rootNode.getChildIndex(aNode); - } - } - else if (useNodeIndex && typeof(aParentRow) == "number") { - // If we have both the row of the parent node, and the node's index, we - // can avoid searching the rows array if the parent is a plain container. - row = aParentRow + aNodeIndex + 1; - } - else { - // Look for the node in the nodes array. Start the search at the parent - // row. If the parent row isn't passed, we'll pass undefined to indexOf, - // which is fine. - row = this._rows.indexOf(aNode, aParentRow); - if (row == -1 && aForceBuild) { - let parentRow = typeof(aParentRow) == "number" ? aParentRow - : this._getRowForNode(parent); - row = parentRow + parent.getChildIndex(aNode) + 1; - } - } - - if (row != -1) - this._rows[row] = aNode; - - return row; - }, - - /** - * Given a row, finds and returns the parent details of the associated node. - * - * @param aChildRow - * Row number. - * @return [parentNode, parentRow] - */ - _getParentByChildRow: function PTV__getParentByChildRow(aChildRow) { - let node = this._getNodeForRow(aChildRow); - let parent = (node === null) ? this._rootNode : node.parent; - - // The root node is never visible - if (parent == this._rootNode) - return [this._rootNode, -1]; - - let parentRow = this._rows.lastIndexOf(parent, aChildRow - 1); - return [parent, parentRow]; - }, - - /** - * Gets the node at a given row. - */ - _getNodeForRow: function PTV__getNodeForRow(aRow) { - if (aRow < 0) { - return null; - } - - let node = this._rows[aRow]; - if (node !== undefined) - return node; - - // Find the nearest node. - let rowNode, row; - for (let i = aRow - 1; i >= 0 && rowNode === undefined; i--) { - rowNode = this._rows[i]; - row = i; - } - - // If there's no container prior to the given row, it's a child of - // the root node (remember: all containers are listed in the rows array). - if (!rowNode) - return this._rows[aRow] = this._rootNode.getChild(aRow); - - // Unset elements may exist only in plain containers. Thus, if the nearest - // node is a container, it's the row's parent, otherwise, it's a sibling. - if (rowNode instanceof Ci.nsINavHistoryContainerResultNode) - return this._rows[aRow] = rowNode.getChild(aRow - row - 1); - - let [parent, parentRow] = this._getParentByChildRow(row); - return this._rows[aRow] = parent.getChild(aRow - parentRow - 1); - }, - - /** - * This takes a container and recursively appends our rows array per its - * contents. Assumes that the rows arrays has no rows for the given - * container. - * - * @param [in] aContainer - * A container result node. - * @param [in] aFirstChildRow - * The first row at which nodes may be inserted to the row array. - * In other words, that's aContainer's row + 1. - * @param [out] aToOpen - * An array of containers to open once the build is done. - * - * @return the number of rows which were inserted. - */ - _buildVisibleSection: - function PTV__buildVisibleSection(aContainer, aFirstChildRow, aToOpen) - { - // There's nothing to do if the container is closed. - if (!aContainer.containerOpen) - return 0; - - // Inserting the new elements into the rows array in one shot (by - // Array.prototype.concat) is faster than resizing the array (by splice) on each loop - // iteration. - let cc = aContainer.childCount; - let newElements = new Array(cc); - this._rows = this._rows.splice(0, aFirstChildRow) - .concat(newElements, this._rows); - - if (this._isPlainContainer(aContainer)) - return cc; - - let sortingMode = this._result.sortingMode; - - let rowsInserted = 0; - for (let i = 0; i < cc; i++) { - let curChild = aContainer.getChild(i); - let curChildType = curChild.type; - - let row = aFirstChildRow + rowsInserted; - - // Don't display separators when sorted. - if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { - if (sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) { - // Remove the element for the filtered separator. - // Notice that the rows array was initially resized to include all - // children. - this._rows.splice(row, 1); - continue; - } - } - - this._rows[row] = curChild; - rowsInserted++; - - // Recursively do containers. - if (!this._flatList && - curChild instanceof Ci.nsINavHistoryContainerResultNode && - !this._controller.hasCachedLivemarkInfo(curChild)) { - let uri = curChild.uri; - let isopen = false; - - if (uri) { - let val = this._xulStore.getValue(document.documentURI, uri, "open"); - isopen = (val == "true"); - } - - if (isopen != curChild.containerOpen) - aToOpen.push(curChild); - else if (curChild.containerOpen && curChild.childCount > 0) - rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen); - } - } - - return rowsInserted; - }, - - /** - * This counts how many rows a node takes in the tree. For containers it - * will count the node itself plus any child node following it. - */ - _countVisibleRowsForNodeAtRow: - function PTV__countVisibleRowsForNodeAtRow(aNodeRow) { - let node = this._rows[aNodeRow]; - - // If it's not listed yet, we know that it's a leaf node (instanceof also - // null-checks). - if (!(node instanceof Ci.nsINavHistoryContainerResultNode)) - return 1; - - let outerLevel = node.indentLevel; - for (let i = aNodeRow + 1; i < this._rows.length; i++) { - let rowNode = this._rows[i]; - if (rowNode && rowNode.indentLevel <= outerLevel) - return i - aNodeRow; - } - - // This node plus its children take up the bottom of the list. - return this._rows.length - aNodeRow; - }, - - _getSelectedNodesInRange: - function PTV__getSelectedNodesInRange(aFirstRow, aLastRow) { - let selection = this.selection; - let rc = selection.getRangeCount(); - if (rc == 0) - return []; - - // The visible-area borders are needed for checking whether a - // selected row is also visible. - let firstVisibleRow = this._tree.getFirstVisibleRow(); - let lastVisibleRow = this._tree.getLastVisibleRow(); - - let nodesInfo = []; - for (let rangeIndex = 0; rangeIndex < rc; rangeIndex++) { - let min = { }, max = { }; - selection.getRangeAt(rangeIndex, min, max); - - // If this range does not overlap the replaced chunk, we don't need to - // persist the selection. - if (max.value < aFirstRow || min.value > aLastRow) - continue; - - let firstRow = Math.max(min.value, aFirstRow); - let lastRow = Math.min(max.value, aLastRow); - for (let i = firstRow; i <= lastRow; i++) { - nodesInfo.push({ - node: this._rows[i], - oldRow: i, - wasVisible: i >= firstVisibleRow && i <= lastVisibleRow - }); - } - } - - return nodesInfo; - }, - - /** - * Tries to find an equivalent node for a node which was removed. We first - * look for the original node, in case it was just relocated. Then, if we - * that node was not found, we look for a node that has the same itemId, uri - * and time values. - * - * @param aUpdatedContainer - * An ancestor of the node which was removed. It does not have to be - * its direct parent. - * @param aOldNode - * The node which was removed. - * - * @return the row number of an equivalent node for aOldOne, if one was - * found, -1 otherwise. - */ - _getNewRowForRemovedNode: - function PTV__getNewRowForRemovedNode(aUpdatedContainer, aOldNode) { - let parent = aOldNode.parent; - if (parent) { - // If the node's parent is still set, the node is not obsolete - // and we should just find out its new position. - // However, if any of the node's ancestor is closed, the node is - // invisible. - let ancestors = PlacesUtils.nodeAncestors(aOldNode); - for (let ancestor of ancestors) { - if (!ancestor.containerOpen) - return -1; - } - - return this._getRowForNode(aOldNode, true); - } - - // There's a broken edge case here. - // If a visit appears in two queries, and the second one was - // the old node, we'll select the first one after refresh. There's - // nothing we could do about that, because aOldNode.parent is - // gone by the time invalidateContainer is called. - let newNode = aUpdatedContainer.findNodeByDetails(aOldNode.uri, - aOldNode.time, - aOldNode.itemId, - true); - if (!newNode) - return -1; - - return this._getRowForNode(newNode, true); - }, - - /** - * Restores a given selection state as near as possible to the original - * selection state. - * - * @param aNodesInfo - * The persisted selection state as returned by - * _getSelectedNodesInRange. - * @param aUpdatedContainer - * The container which was updated. - */ - _restoreSelection: - function PTV__restoreSelection(aNodesInfo, aUpdatedContainer) { - if (aNodesInfo.length == 0) - return; - - let selection = this.selection; - - // Attempt to ensure that previously-visible selection will be visible - // if it's re-selected. However, we can only ensure that for one row. - let scrollToRow = -1; - for (let i = 0; i < aNodesInfo.length; i++) { - let nodeInfo = aNodesInfo[i]; - let row = this._getNewRowForRemovedNode(aUpdatedContainer, - nodeInfo.node); - // Select the found node, if any. - if (row != -1) { - selection.rangedSelect(row, row, true); - if (nodeInfo.wasVisible && scrollToRow == -1) - scrollToRow = row; - } - } - - // If only one node was previously selected and there's no selection now, - // select the node at its old row, if any. - if (aNodesInfo.length == 1 && selection.count == 0) { - let row = Math.min(aNodesInfo[0].oldRow, this._rows.length - 1); - if (row != -1) { - selection.rangedSelect(row, row, true); - if (aNodesInfo[0].wasVisible && scrollToRow == -1) - scrollToRow = aNodesInfo[0].oldRow; - } - } - - if (scrollToRow != -1) - this._tree.ensureRowIsVisible(scrollToRow); - }, - - _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) { - const MS_PER_MINUTE = 60000; - const MS_PER_DAY = 86400000; - let timeMs = aTime / 1000; // PRTime is in microseconds - - // Date is calculated starting from midnight, so the modulo with a day are - // milliseconds from today's midnight. - // getTimezoneOffset corrects that based on local time, notice midnight - // can have a different offset during DST-change days. - let dateObj = new Date(); - let now = dateObj.getTime() - dateObj.getTimezoneOffset() * MS_PER_MINUTE; - let midnight = now - (now % MS_PER_DAY); - midnight += new Date(midnight).getTimezoneOffset() * MS_PER_MINUTE; - - let timeObj = new Date(timeMs); - return timeMs >= midnight ? this._todayFormatter.format(timeObj) - : this._dateFormatter.format(timeObj); - }, - - // We use a different formatter for times within the current day, - // so we cache both a "today" formatter and a general date formatter. - __todayFormatter: null, - get _todayFormatter() { - if (!this.__todayFormatter) { - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { hour: 'numeric', minute: 'numeric' }; - this.__todayFormatter = new Intl.DateTimeFormat(locale, dtOptions); - } - return this.__todayFormatter; - }, - - __dateFormatter: null, - get _dateFormatter() { - if (!this.__dateFormatter) { - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { year: 'numeric', month: 'numeric', day: 'numeric', - hour: 'numeric', minute: 'numeric' }; - this.__dateFormatter = new Intl.DateTimeFormat(locale, dtOptions); - } - return this.__dateFormatter; - }, - - COLUMN_TYPE_UNKNOWN: 0, - COLUMN_TYPE_TITLE: 1, - COLUMN_TYPE_URI: 2, - COLUMN_TYPE_DATE: 3, - COLUMN_TYPE_VISITCOUNT: 4, - COLUMN_TYPE_DESCRIPTION: 5, - COLUMN_TYPE_DATEADDED: 6, - COLUMN_TYPE_LASTMODIFIED: 7, - COLUMN_TYPE_TAGS: 8, - - _getColumnType: function PTV__getColumnType(aColumn) { - let 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; - case "description": - return this.COLUMN_TYPE_DESCRIPTION; - case "dateAdded": - return this.COLUMN_TYPE_DATEADDED; - case "lastModified": - return this.COLUMN_TYPE_LASTMODIFIED; - case "tags": - return this.COLUMN_TYPE_TAGS; - } - return this.COLUMN_TYPE_UNKNOWN; - }, - - _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) { - switch (aSortType) { - case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING: - return [this.COLUMN_TYPE_TITLE, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING: - return [this.COLUMN_TYPE_TITLE, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING: - return [this.COLUMN_TYPE_DATE, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING: - return [this.COLUMN_TYPE_DATE, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING: - return [this.COLUMN_TYPE_URI, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING: - return [this.COLUMN_TYPE_URI, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING: - return [this.COLUMN_TYPE_VISITCOUNT, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING: - return [this.COLUMN_TYPE_VISITCOUNT, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING: - if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) - return [this.COLUMN_TYPE_DESCRIPTION, false]; - break; - case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING: - if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) - return [this.COLUMN_TYPE_DESCRIPTION, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING: - return [this.COLUMN_TYPE_DATEADDED, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING: - return [this.COLUMN_TYPE_DATEADDED, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING: - return [this.COLUMN_TYPE_LASTMODIFIED, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING: - return [this.COLUMN_TYPE_LASTMODIFIED, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING: - return [this.COLUMN_TYPE_TAGS, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING: - return [this.COLUMN_TYPE_TAGS, true]; - } - return [this.COLUMN_TYPE_UNKNOWN, false]; - }, - - // nsINavHistoryResultObserver - nodeInserted: function PTV_nodeInserted(aParentNode, aNode, aNewIndex) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // Bail out for hidden separators. - if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted()) - return; - - let parentRow; - if (aParentNode != this._rootNode) { - parentRow = this._getRowForNode(aParentNode); - - // Update parent when inserting the first item, since twisty has changed. - if (aParentNode.childCount == 1) - this._tree.invalidateRow(parentRow); - } - - // Compute the new row number of the node. - let row = -1; - let cc = aParentNode.childCount; - if (aNewIndex == 0 || this._isPlainContainer(aParentNode) || cc == 0) { - // We don't need to worry about sub hierarchies of the parent node - // if it's a plain container, or if the new node is its first child. - if (aParentNode == this._rootNode) - row = aNewIndex; - else - row = parentRow + aNewIndex + 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. - let separatorsAreHidden = PlacesUtils.nodeIsSeparator(aNode) && - this.isSorted(); - for (let i = aNewIndex + 1; i < cc; i++) { - let node = aParentNode.getChild(i); - if (!separatorsAreHidden || PlacesUtils.nodeIsSeparator(node)) { - // The children have not been shifted so the next item will have what - // should be our index. - row = this._getRowForNode(node, false, parentRow, i); - break; - } - } - if (row < 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). - let prevChild = aParentNode.getChild(aNewIndex - 1); - let prevIndex = this._getRowForNode(prevChild, false, parentRow, - aNewIndex - 1); - row = prevIndex + this._countVisibleRowsForNodeAtRow(prevIndex); - } - } - - this._rows.splice(row, 0, aNode); - this._tree.rowCountChanged(row, 1); - - if (PlacesUtils.nodeIsContainer(aNode) && - PlacesUtils.asContainer(aNode).containerOpen) { - this.invalidateContainer(aNode); - } - }, - - /** - * 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. - */ - nodeRemoved: function PTV_nodeRemoved(aParentNode, aNode, aOldIndex) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // XXX bug 517701: We don't know what to do when the root node is removed. - if (aNode == this._rootNode) - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - - // Bail out for hidden separators. - if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted()) - return; - - let parentRow = aParentNode == this._rootNode ? - undefined : this._getRowForNode(aParentNode, true); - let oldRow = this._getRowForNode(aNode, true, parentRow, aOldIndex); - if (oldRow < 0) - throw Cr.NS_ERROR_UNEXPECTED; - - // If the node was exclusively selected, the node next to it will be - // selected. - let selectNext = false; - let selection = this.selection; - if (selection.getRangeCount() == 1) { - let min = { }, max = { }; - selection.getRangeAt(0, min, max); - if (min.value == max.value && - this.nodeForTreeIndex(min.value) == aNode) - selectNext = true; - } - - // Remove the node and its children, if any. - let count = this._countVisibleRowsForNodeAtRow(oldRow); - this._rows.splice(oldRow, count); - this._tree.rowCountChanged(oldRow, -count); - - // Redraw the parent if its twisty state has changed. - if (aParentNode != this._rootNode && !aParentNode.hasChildren) { - let parentRow = oldRow - 1; - this._tree.invalidateRow(parentRow); - } - - // Restore selection if the node was exclusively selected. - if (!selectNext) - return; - - // Restore selection. - let rowToSelect = Math.min(oldRow, this._rows.length - 1); - if (rowToSelect != -1) - this.selection.rangedSelect(rowToSelect, rowToSelect, true); - }, - - nodeMoved: - function PTV_nodeMoved(aNode, aOldParent, aOldIndex, aNewParent, aNewIndex) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // Bail out for hidden separators. - if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted()) - return; - - // Note that at this point the node has already been moved by the backend, - // so we must give hints to _getRowForNode to get the old row position. - let oldParentRow = aOldParent == this._rootNode ? - undefined : this._getRowForNode(aOldParent, true); - let oldRow = this._getRowForNode(aNode, true, oldParentRow, aOldIndex); - if (oldRow < 0) - throw Cr.NS_ERROR_UNEXPECTED; - - // If this node is a container it could take up more than one row. - let count = this._countVisibleRowsForNodeAtRow(oldRow); - - // Persist selection state. - let nodesToReselect = - this._getSelectedNodesInRange(oldRow, oldRow + count); - if (nodesToReselect.length > 0) - this.selection.selectEventsSuppressed = true; - - // Redraw the parent if its twisty state has changed. - if (aOldParent != this._rootNode && !aOldParent.hasChildren) { - let parentRow = oldRow - 1; - this._tree.invalidateRow(parentRow); - } - - // Remove node and its children, if any, from the old position. - this._rows.splice(oldRow, count); - this._tree.rowCountChanged(oldRow, -count); - - // Insert the node into the new position. - this.nodeInserted(aNewParent, aNode, aNewIndex); - - // Restore selection. - if (nodesToReselect.length > 0) { - this._restoreSelection(nodesToReselect, aNewParent); - this.selection.selectEventsSuppressed = false; - } - }, - - _invalidateCellValue: function PTV__invalidateCellValue(aNode, - aColumnType) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // Nothing to do for the root node. - if (aNode == this._rootNode) - return; - - let row = this._getRowForNode(aNode); - if (row == -1) - return; - - let column = this._findColumnByType(aColumnType); - if (column && !column.element.hidden) - this._tree.invalidateCell(row, column); - - // Last modified time is altered for almost all node changes. - if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) { - let lastModifiedColumn = - this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED); - if (lastModifiedColumn && !lastModifiedColumn.hidden) - this._tree.invalidateCell(row, lastModifiedColumn); - } - }, - - _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) { - PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }) - .then(aLivemark => { - let placesNode = aNode; - // Need to check containerOpen since getLivemark is async. - if (!placesNode.containerOpen) - return; - - let children = aLivemark.getNodesForContainer(placesNode); - for (let i = 0; i < children.length; i++) { - let child = children[i]; - this.nodeInserted(placesNode, child, i); - } - }, Components.utils.reportError); - }, - - nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - }, - - nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI); - }, - - nodeIconChanged: function PTV_nodeIconChanged(aNode) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - }, - - nodeHistoryDetailsChanged: - function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate, - aUpdatedVisitCount) { - if (aNode.parent && this._controller.hasCachedLivemarkInfo(aNode.parent)) { - // Find the node in the parent. - let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent); - for (let i = parentRow; i < this._rows.length; i++) { - let child = this.nodeForTreeIndex(i); - if (child.uri == aNode.uri) { - this._cellProperties.delete(child); - this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE); - break; - } - } - return; - } - - this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE); - this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT); - }, - - nodeTagsChanged: function PTV_nodeTagsChanged(aNode) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS); - }, - - nodeKeywordChanged(aNode, aNewKeyword) {}, - - nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) { - if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION); - } - else if (aAnno == PlacesUtils.LMANNO_FEEDURI) { - PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }) - .then(aLivemark => { - this._controller.cacheLivemarkInfo(aNode, aLivemark); - let properties = this._cellProperties.get(aNode); - this._cellProperties.set(aNode, properties += " livemark"); - // The livemark attribute is set as a cell property on the title cell. - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - }, Components.utils.reportError); - } - }, - - nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED); - }, - - nodeLastModifiedChanged: - function PTV_nodeLastModifiedChanged(aNode, aNewValue) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED); - }, - - containerStateChanged: - function PTV_containerStateChanged(aNode, aOldState, aNewState) { - this.invalidateContainer(aNode); - - if (PlacesUtils.nodeIsFolder(aNode) || - (this._flatList && aNode == this._rootNode)) { - let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions; - if (queryOptions.excludeItems) { - return; - } - if (aNode.itemId != -1) { // run when there's a valid node id - PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }) - .then(aLivemark => { - let shouldInvalidate = - !this._controller.hasCachedLivemarkInfo(aNode); - this._controller.cacheLivemarkInfo(aNode, aLivemark); - if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) { - aLivemark.registerForUpdates(aNode, this); - // Prioritize the current livemark. - aLivemark.reload(); - PlacesUtils.livemarks.reloadLivemarks(); - if (shouldInvalidate) - this.invalidateContainer(aNode); - } - else { - aLivemark.unregisterForUpdates(aNode); - } - }, () => undefined); - } - } - }, - - invalidateContainer: function PTV_invalidateContainer(aContainer) { - NS_ASSERT(this._result, "Need to have a result to update"); - if (!this._tree) - return; - - let startReplacement, replaceCount; - if (aContainer == this._rootNode) { - startReplacement = 0; - replaceCount = this._rows.length; - - // If the root node is now closed, the tree is empty. - if (!this._rootNode.containerOpen) { - this._rows = []; - if (replaceCount) - this._tree.rowCountChanged(startReplacement, -replaceCount); - - return; - } - } - else { - // Update the twisty state. - let row = this._getRowForNode(aContainer); - this._tree.invalidateRow(row); - - // We don't replace the container node itself, so we should decrease the - // replaceCount by 1. - startReplacement = row + 1; - replaceCount = this._countVisibleRowsForNodeAtRow(row) - 1; - } - - // Persist selection state. - let nodesToReselect = - this._getSelectedNodesInRange(startReplacement, - startReplacement + replaceCount); - - // Now update the number of elements. - this.selection.selectEventsSuppressed = true; - - // First remove the old elements - this._rows.splice(startReplacement, replaceCount); - - // If the container is now closed, we're done. - if (!aContainer.containerOpen) { - let oldSelectionCount = this.selection.count; - if (replaceCount) - this._tree.rowCountChanged(startReplacement, -replaceCount); - - // Select the row next to the closed container if any of its - // children were selected, and nothing else is selected. - if (nodesToReselect.length > 0 && - nodesToReselect.length == oldSelectionCount) { - this.selection.rangedSelect(startReplacement, startReplacement, true); - this._tree.ensureRowIsVisible(startReplacement); - } - - this.selection.selectEventsSuppressed = false; - return; - } - - // Otherwise, start a batch first. - this._tree.beginUpdateBatch(); - if (replaceCount) - this._tree.rowCountChanged(startReplacement, -replaceCount); - - let toOpenElements = []; - let elementsAddedCount = this._buildVisibleSection(aContainer, - startReplacement, - toOpenElements); - if (elementsAddedCount) - this._tree.rowCountChanged(startReplacement, elementsAddedCount); - - if (!this._flatList) { - // Now, open any containers that were persisted. - for (let i = 0; i < toOpenElements.length; i++) { - let item = toOpenElements[i]; - let 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; - } - } - - if (this._controller.hasCachedLivemarkInfo(aContainer)) { - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - if (!queryOptions.excludeItems) { - this._populateLivemarkContainer(aContainer); - } - } - - this._tree.endUpdateBatch(); - - // Restore selection. - this._restoreSelection(nodesToReselect, aContainer); - this.selection.selectEventsSuppressed = false; - }, - - _columns: [], - _findColumnByType: function PTV__findColumnByType(aColumnType) { - if (this._columns[aColumnType]) - return this._columns[aColumnType]; - - let columns = this._tree.columns; - let colCount = columns.count; - for (let i = 0; i < colCount; i++) { - let column = columns.getColumnAt(i); - let columnType = this._getColumnType(column); - this._columns[columnType] = column; - if (columnType == aColumnType) - return column; - } - - // That's completely valid. Most of our trees actually include just the - // title column. - return null; - }, - - sortingChanged: function PTV__sortingChanged(aSortingMode) { - if (!this._tree || !this._result) - return; - - // Depending on the sort mode, certain commands may be disabled. - window.updateCommands("sort"); - - let columns = this._tree.columns; - - // Clear old sorting indicator. - let sortedColumn = columns.getSortedColumn(); - if (sortedColumn) - sortedColumn.element.removeAttribute("sortDirection"); - - // Set new sorting indicator by looking through all columns for ours. - if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) - return; - - let [desiredColumn, desiredIsDescending] = - this._sortTypeToColumnType(aSortingMode); - let column = this._findColumnByType(desiredColumn); - if (column) { - let sortDir = desiredIsDescending ? "descending" : "ascending"; - column.element.setAttribute("sortDirection", sortDir); - } - }, - - _inBatchMode: false, - batching: function PTV__batching(aToggleMode) { - if (this._inBatchMode != aToggleMode) { - this._inBatchMode = this.selection.selectEventsSuppressed = aToggleMode; - if (this._inBatchMode) { - this._tree.beginUpdateBatch(); - } - else { - this._tree.endUpdateBatch(); - } - } - }, - - get result() { - return this._result; - }, - set result(val) { - if (this._result) { - this._result.removeObserver(this); - this._rootNode.containerOpen = false; - } - - if (val) { - this._result = val; - this._rootNode = this._result.root; - this._cellProperties = new Map(); - this._cuttingNodes = new Set(); - } - else if (this._result) { - delete this._result; - delete this._rootNode; - delete this._cellProperties; - delete this._cuttingNodes; - } - - // If the tree is not set yet, setTree will call finishInit. - if (this._tree && val) - this._finishInit(); - - return val; - }, - - nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) { - if (aIndex > this._rows.length) - throw Cr.NS_ERROR_INVALID_ARG; - - return this._getNodeForRow(aIndex); - }, - - treeIndexForNode: function PTV_treeNodeForIndex(aNode) { - // The API allows passing invisible nodes. - try { - return this._getRowForNode(aNode, true); - } - catch (ex) { } - - return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE; - }, - - // nsITreeView - get rowCount() { - return this._rows.length; - }, - get selection() { - return this._selection; - }, - set selection(val) { - this._selection = val; - }, - - getRowProperties: function() { return ""; }, - - getCellProperties: - function PTV_getCellProperties(aRow, aColumn) { - // for anonid-trees, we need to add the column-type manually - var props = ""; - let columnType = aColumn.element.getAttribute("anonid"); - if (columnType) - props += columnType; - else - columnType = aColumn.id; - - // Set the "ltr" property on url cells - if (columnType == "url") - props += " ltr"; - - if (columnType != "title") - return props; - - let node = this._getNodeForRow(aRow); - - if (this._cuttingNodes.has(node)) { - props += " cutting"; - } - - let properties = this._cellProperties.get(node); - if (properties === undefined) { - properties = ""; - let itemId = node.itemId; - let nodeType = node.type; - if (PlacesUtils.containerTypes.includes(nodeType)) { - if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) { - properties += " query"; - if (PlacesUtils.nodeIsTagQuery(node)) - properties += " tagContainer"; - else if (PlacesUtils.nodeIsDay(node)) - properties += " dayContainer"; - else if (PlacesUtils.nodeIsHost(node)) - properties += " hostContainer"; - } - else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER || - nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) { - if (this._controller.hasCachedLivemarkInfo(node)) { - properties += " livemark"; - } - else { - PlacesUtils.livemarks.getLivemark({ id: node.itemId }) - .then(aLivemark => { - this._controller.cacheLivemarkInfo(node, aLivemark); - let props = this._cellProperties.get(node); - this._cellProperties.set(node, props += " livemark"); - // The livemark attribute is set as a cell property on the title cell. - this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE); - }, () => undefined); - } - } - - if (itemId != -1) { - let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId); - if (queryName) - properties += " OrganizerQuery_" + queryName; - } - } - else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) - properties += " separator"; - else if (PlacesUtils.nodeIsURI(node)) { - properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri); - - if (this._controller.hasCachedLivemarkInfo(node.parent)) { - properties += " livemarkItem"; - if (node.accessCount) { - properties += " visited"; - } - } - } - - this._cellProperties.set(node, properties); - } - - return props + " " + properties; - }, - - getColumnProperties: function(aColumn) { return ""; }, - - isContainer: function PTV_isContainer(aRow) { - // Only leaf nodes aren't listed in the rows array. - let node = this._rows[aRow]; - if (node === undefined) - return false; - - if (PlacesUtils.nodeIsContainer(node)) { - // 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)) { - let parent = node.parent; - if ((PlacesUtils.nodeIsQuery(parent) || - PlacesUtils.nodeIsFolder(parent)) && - !PlacesUtils.asQuery(node).hasChildren) - return PlacesUtils.asQuery(parent).queryOptions.expandQueries; - } - return true; - } - return false; - }, - - isContainerOpen: function PTV_isContainerOpen(aRow) { - if (this._flatList) - return false; - - // All containers are listed in the rows array. - return this._rows[aRow].containerOpen; - }, - - isContainerEmpty: function PTV_isContainerEmpty(aRow) { - if (this._flatList) - return true; - - let node = this._rows[aRow]; - if (this._controller.hasCachedLivemarkInfo(node)) { - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - return queryOptions.excludeItems; - } - - // All containers are listed in the rows array. - return !node.hasChildren; - }, - - isSeparator: function PTV_isSeparator(aRow) { - // All separators are listed in the rows array. - let node = this._rows[aRow]; - return node && PlacesUtils.nodeIsSeparator(node); - }, - - isSorted: function PTV_isSorted() { - return this._result.sortingMode != - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; - }, - - canDrop: function PTV_canDrop(aRow, aOrientation, aDataTransfer) { - if (!this._result) - throw Cr.NS_ERROR_UNEXPECTED; - - // Drop position into a sorted treeview would be wrong. - if (this.isSorted()) - return false; - - let ip = this._getInsertionPoint(aRow, aOrientation); - return ip && PlacesControllerDragHelper.canDrop(ip, aDataTransfer); - }, - - _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) { - let container = this._result.root; - let dropNearItemId = -1; - // When there's no selection, assume the container is the container - // the view is populated from (i.e. the result's itemId). - if (index != -1) { - let lastSelected = this.nodeForTreeIndex(index); - if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) { - // If the last selected item is an open container, append _into_ - // it, rather than insert adjacent to it. - container = lastSelected; - index = -1; - } - else if (lastSelected.containerOpen && - orientation == Ci.nsITreeView.DROP_AFTER && - lastSelected.hasChildren) { - // If the last selected node is an open container and the user is - // trying to drag into it as a first node, really insert into it. - container = lastSelected; - orientation = Ci.nsITreeView.DROP_ON; - index = 0; - } - else { - // Use the last-selected node's container. - container = lastSelected.parent; - - // During its Drag & Drop operation, the tree code closes-and-opens - // containers very often (part of the XUL "spring-loaded folders" - // implementation). And in certain cases, we may reach a closed - // container here. However, we can simply bail out when this happens, - // because we would then be back here in less than a millisecond, when - // the container had been reopened. - if (!container || !container.containerOpen) - return null; - - // Avoid the potentially expensive call to getChildIndex - // if we know this container doesn't allow insertion. - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - if (queryOptions.sortingMode != - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) { - // If we are within a sorted view, insert at the end. - index = -1; - } - else if (queryOptions.excludeItems || - queryOptions.excludeQueries || - queryOptions.excludeReadOnlyFolders) { - // Some item may be invisible, insert near last selected one. - // We don't replace index here to avoid requests to the db, - // instead it will be calculated later by the controller. - index = -1; - dropNearItemId = lastSelected.itemId; - } - else { - let lsi = container.getChildIndex(lastSelected); - index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1; - } - } - } - - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - // TODO (Bug 1160193): properly support dropping on a tag root. - let tagName = null; - if (PlacesUtils.nodeIsTagQuery(container)) { - tagName = container.title; - if (!tagName) - return null; - } - - return new InsertionPoint(PlacesUtils.getConcreteItemId(container), - index, orientation, - tagName, - dropNearItemId); - }, - - drop: function PTV_drop(aRow, aOrientation, aDataTransfer) { - // We are responsible for translating the |index| and |orientation| - // parameters into a container id and index within the container, - // since this information is specific to the tree view. - let ip = this._getInsertionPoint(aRow, aOrientation); - if (ip) { - PlacesControllerDragHelper.onDrop(ip, aDataTransfer) - .then(null, Components.utils.reportError); - } - - PlacesControllerDragHelper.currentDropTarget = null; - }, - - getParentIndex: function PTV_getParentIndex(aRow) { - let [, parentRow] = this._getParentByChildRow(aRow); - return parentRow; - }, - - hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) { - if (aRow == this._rows.length - 1) { - // The last row has no sibling. - return false; - } - - let node = this._rows[aRow]; - if (node === undefined || this._isPlainContainer(node.parent)) { - // The node is a child of a plain container. - // If the next row is either unset or has the same parent, - // it's a sibling. - let nextNode = this._rows[aRow + 1]; - return (nextNode == undefined || nextNode.parent == node.parent); - } - - let thisLevel = node.indentLevel; - for (let i = aAfterIndex + 1; i < this._rows.length; ++i) { - let rowNode = this._getNodeForRow(i); - let nextLevel = rowNode.indentLevel; - if (nextLevel == thisLevel) - return true; - if (nextLevel < thisLevel) - break; - } - - return false; - }, - - getLevel: function(aRow) { - return this._getNodeForRow(aRow).indentLevel; - }, - - getImageSrc: function PTV_getImageSrc(aRow, aColumn) { - // Only the title column has an image. - if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE) - return ""; - - let node = this._getNodeForRow(aRow); - return node.icon; - }, - - getProgressMode: function(aRow, aColumn) { }, - getCellValue: function(aRow, aColumn) { }, - - getCellText: function PTV_getCellText(aRow, aColumn) { - let node = this._getNodeForRow(aRow); - switch (this._getColumnType(aColumn)) { - 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. - if (PlacesUtils.nodeIsSeparator(node)) - return ""; - return PlacesUIUtils.getBestTitle(node, true); - 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: - let nodeTime = node.time; - if (nodeTime == 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 ""; - } - - return this._convertPRTimeToString(nodeTime); - case this.COLUMN_TYPE_VISITCOUNT: - return node.accessCount; - case this.COLUMN_TYPE_DESCRIPTION: - if (node.itemId != -1) { - try { - return PlacesUtils.annotations. - getItemAnnotation(node.itemId, PlacesUIUtils.DESCRIPTION_ANNO); - } - catch (ex) { /* has no description */ } - } - return ""; - case this.COLUMN_TYPE_DATEADDED: - if (node.dateAdded) - return this._convertPRTimeToString(node.dateAdded); - return ""; - case this.COLUMN_TYPE_LASTMODIFIED: - if (node.lastModified) - return this._convertPRTimeToString(node.lastModified); - return ""; - } - return ""; - }, - - setTree: function PTV_setTree(aTree) { - // If we are replacing the tree during a batch, there is a concrete risk - // that the treeView goes out of sync, thus it's safer to end the batch now. - // This is a no-op if we are not batching. - this.batching(false); - - let hasOldTree = this._tree != null; - this._tree = aTree; - - if (this._result) { - if (hasOldTree) { - // detach from result when we are detaching from the tree. - // This breaks the reference cycle between us and the result. - if (!aTree) { - this._result.removeObserver(this); - this._rootNode.containerOpen = false; - } - } - if (aTree) - this._finishInit(); - } - }, - - toggleOpenState: function PTV_toggleOpenState(aRow) { - if (!this._result) - throw Cr.NS_ERROR_UNEXPECTED; - - let node = this._rows[aRow]; - if (this._flatList && this._openContainerCallback) { - this._openContainerCallback(node); - return; - } - - // Persist containers open status, but never persist livemarks. - if (!this._controller.hasCachedLivemarkInfo(node)) { - let uri = node.uri; - - if (uri) { - let docURI = document.documentURI; - - if (node.containerOpen) { - this._xulStore.removeValue(docURI, uri, "open"); - } else { - this._xulStore.setValue(docURI, uri, "open", "true"); - } - } - } - - node.containerOpen = !node.containerOpen; - }, - - cycleHeader: function PTV_cycleHeader(aColumn) { - if (!this._result) - throw Cr.NS_ERROR_UNEXPECTED; - - // Sometimes you want a tri-state sorting, and sometimes you don't. This - // rule allows tri-state sorting when the root node is a folder. This will - // catch the most common cases. When you are looking at folders, you want - // the third state to reset the sorting to the natural bookmark order. When - // you are looking at history, that third state has no meaning so we try - // to disallow it. - // - // The problem occurs when you have a query that results in bookmark - // folders. One example of this is the subscriptions view. In these cases, - // this rule doesn't allow you to sort those sub-folders by their natural - // order. - let allowTriState = PlacesUtils.nodeIsFolder(this._result.root); - - let oldSort = this._result.sortingMode; - let oldSortingAnnotation = this._result.sortingAnnotation; - let newSort; - let newSortingAnnotation = ""; - const NHQO = Ci.nsINavHistoryQueryOptions; - switch (this._getColumnType(aColumn)) { - case this.COLUMN_TYPE_TITLE: - if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING) - newSort = NHQO.SORT_BY_TITLE_DESCENDING; - else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - 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 if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - 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 if (allowTriState && - oldSort == NHQO.SORT_BY_DATE_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - 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 if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING; - - break; - case this.COLUMN_TYPE_DESCRIPTION: - if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING && - oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) { - newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING; - newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO; - } - else if (allowTriState && - oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING && - oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) - newSort = NHQO.SORT_BY_NONE; - else { - newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING; - newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO; - } - - break; - case this.COLUMN_TYPE_DATEADDED: - if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING) - newSort = NHQO.SORT_BY_DATEADDED_DESCENDING; - else if (allowTriState && - oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_DATEADDED_ASCENDING; - - break; - case this.COLUMN_TYPE_LASTMODIFIED: - if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING) - newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING; - else if (allowTriState && - oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING; - - break; - case this.COLUMN_TYPE_TAGS: - if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING) - newSort = NHQO.SORT_BY_TAGS_DESCENDING; - else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_TAGS_ASCENDING; - - break; - default: - throw Cr.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; - - let node = this._rows[aRow]; - if (!node) { - Cu.reportError("isEditable called for an unbuilt row."); - return false; - } - let itemId = node.itemId; - - // Only bookmark-nodes are editable. Fortunately, this checks also takes - // care of livemark children. - if (itemId == -1) - return false; - - // The following items are also not editable, even though they are bookmark - // items. - // * places-roots - // * the left pane special folders and queries (those are place: uri - // bookmarks) - // * separators - // - // Note that concrete itemIds aren't used intentionally. For example, we - // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar, - // except for the one under All Bookmarks. - if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemId)) - return false; - - let parentId = PlacesUtils.getConcreteItemId(node.parent); - if (parentId == PlacesUIUtils.leftPaneFolderId || - parentId == PlacesUIUtils.allBookmarksFolderId) { - // Note that the for the time being this is the check that actually - // blocks renaming places "roots", and not the isRootItem check above. - // That's because places root are only exposed through folder shortcuts - // descendants of the left pane folder. - return false; - } - - return true; - }, - - setCellText: function PTV_setCellText(aRow, aColumn, aText) { - // We may only get here if the cell is editable. - let node = this._rows[aRow]; - if (node.title != aText) { - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditItemTitleTransaction(node.itemId, aText); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - PlacesTransactions.EditTitle({ guid: node.bookmarkGuid, title: aText }) - .transact().catch(Cu.reportError); - } - }, - - toggleCutNode: function PTV_toggleCutNode(aNode, aValue) { - let currentVal = this._cuttingNodes.has(aNode); - if (currentVal != aValue) { - if (aValue) - this._cuttingNodes.add(aNode); - else - this._cuttingNodes.delete(aNode); - - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - } - }, - - selectionChanged: function() { }, - cycleCell: function(aRow, aColumn) { }, - isSelectable: function(aRow, aColumn) { return false; }, - performAction: function(aAction) { }, - performActionOnRow: function(aAction, aRow) { }, - performActionOnCell: function(aAction, aRow, aColumn) { } -}; |