summaryrefslogtreecommitdiffstats
path: root/browser/components/places/content/tree.xml
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/places/content/tree.xml')
-rw-r--r--browser/components/places/content/tree.xml801
1 files changed, 801 insertions, 0 deletions
diff --git a/browser/components/places/content/tree.xml b/browser/components/places/content/tree.xml
new file mode 100644
index 000000000..338ee81f6
--- /dev/null
+++ b/browser/components/places/content/tree.xml
@@ -0,0 +1,801 @@
+<?xml version="1.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/. -->
+
+<bindings id="placesTreeBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
+ <implementation>
+ <constructor><![CDATA[
+ // Force an initial build.
+ if (this.place)
+ this.place = this.place;
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ // Break the treeviewer->result->treeviewer cycle.
+ // Note: unsetting the result's viewer also unsets
+ // the viewer's reference to our treeBoxObject.
+ var result = this.result;
+ if (result) {
+ result.root.containerOpen = false;
+ }
+
+ // Unregister the controllber before unlinking the view, otherwise it
+ // may still try to update commands on a view with a null result.
+ if (this._controller) {
+ this._controller.terminate();
+ this.controllers.removeController(this._controller);
+ }
+
+ this.view = null;
+ ]]></destructor>
+
+ <property name="controller"
+ readonly="true"
+ onget="return this._controller"/>
+
+ <!-- overriding -->
+ <property name="view">
+ <getter><![CDATA[
+ try {
+ return this.treeBoxObject.view.wrappedJSObject || null;
+ }
+ catch (e) {
+ return null;
+ }
+ ]]></getter>
+ <setter><![CDATA[
+ return this.treeBoxObject.view = val;
+ ]]></setter>
+ </property>
+
+ <property name="associatedElement"
+ readonly="true"
+ onget="return this"/>
+
+ <method name="applyFilter">
+ <parameter name="filterString"/>
+ <parameter name="folderRestrict"/>
+ <parameter name="includeHidden"/>
+ <body><![CDATA[
+ // preserve grouping
+ var queryNode = PlacesUtils.asQuery(this.result.root);
+ var options = queryNode.queryOptions.clone();
+
+ // Make sure we're getting uri results.
+ // We do not yet support searching into grouped queries or into
+ // tag containers, so we must fall to the default case.
+ if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
+ options.resultType == options.RESULTS_AS_TAG_QUERY ||
+ options.resultType == options.RESULTS_AS_TAG_CONTENTS)
+ options.resultType = options.RESULTS_AS_URI;
+
+ var query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+
+ if (folderRestrict) {
+ query.setFolders(folderRestrict, folderRestrict.length);
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ }
+
+ options.includeHidden = !!includeHidden;
+
+ this.load([query], options);
+ ]]></body>
+ </method>
+
+ <method name="load">
+ <parameter name="queries"/>
+ <parameter name="options"/>
+ <body><![CDATA[
+ let result = PlacesUtils.history
+ .executeQueries(queries, queries.length,
+ options);
+ let callback;
+ if (this.flatList) {
+ let onOpenFlatContainer = this.onOpenFlatContainer;
+ if (onOpenFlatContainer)
+ callback = new Function("aContainer", onOpenFlatContainer);
+ }
+
+ if (!this._controller) {
+ this._controller = new PlacesController(this);
+ this.controllers.appendController(this._controller);
+ }
+
+ let treeView = new PlacesTreeView(this.flatList, callback, this._controller);
+
+ // Observer removal is done within the view itself. When the tree
+ // goes away, treeboxobject calls view.setTree(null), which then
+ // calls removeObserver.
+ result.addObserver(treeView, false);
+ this.view = treeView;
+
+ if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) {
+ treeView.selection.select(0);
+ }
+
+ this._cachedInsertionPoint = undefined;
+ ]]></body>
+ </method>
+
+ <property name="flatList">
+ <getter><![CDATA[
+ return this.getAttribute("flatList") == "true";
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.flatList != val) {
+ this.setAttribute("flatList", val);
+ // reload with the last place set
+ if (this.place)
+ this.place = this.place;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="onOpenFlatContainer">
+ <getter><![CDATA[
+ return this.getAttribute("onopenflatcontainer");
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.onOpenFlatContainer != val) {
+ this.setAttribute("onopenflatcontainer", val);
+ // reload with the last place set
+ if (this.place)
+ this.place = this.place;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <!--
+ Causes a particular node represented by the specified placeURI to be
+ selected in the tree. All containers above the node in the hierarchy
+ will be opened, so that the node is visible.
+ -->
+ <method name="selectPlaceURI">
+ <parameter name="placeURI"/>
+ <body><![CDATA[
+ // Do nothing if a node matching the given uri is already selected
+ if (this.hasSelection && this.selectedNode.uri == placeURI)
+ return;
+
+ function findNode(container, placeURI, nodesURIChecked) {
+ var containerURI = container.uri;
+ if (containerURI == placeURI)
+ return container;
+ if (nodesURIChecked.includes(containerURI))
+ return null;
+
+ // never check the contents of the same query
+ nodesURIChecked.push(containerURI);
+
+ var wasOpen = container.containerOpen;
+ if (!wasOpen)
+ container.containerOpen = true;
+ for (var i = 0; i < container.childCount; ++i) {
+ var child = container.getChild(i);
+ var childURI = child.uri;
+ if (childURI == placeURI)
+ return child;
+ else if (PlacesUtils.nodeIsContainer(child)) {
+ var nested = findNode(PlacesUtils.asContainer(child), placeURI, nodesURIChecked);
+ if (nested)
+ return nested;
+ }
+ }
+
+ if (!wasOpen)
+ container.containerOpen = false;
+
+ return null;
+ }
+
+ var container = this.result.root;
+ NS_ASSERT(container, "No result, cannot select place URI!");
+ if (!container)
+ return;
+
+ var child = findNode(container, placeURI, []);
+ if (child)
+ this.selectNode(child);
+ else {
+ // If the specified child could not be located, clear the selection
+ var selection = this.view.selection;
+ selection.clearSelection();
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ Causes a particular node to be selected in the tree, resulting in all
+ containers above the node in the hierarchy to be opened, so that the
+ node is visible.
+ -->
+ <method name="selectNode">
+ <parameter name="node"/>
+ <body><![CDATA[
+ var view = this.view;
+
+ var parent = node.parent;
+ if (parent && !parent.containerOpen) {
+ // Build a list of all of the nodes that are the parent of this one
+ // in the result.
+ var parents = [];
+ var root = this.result.root;
+ while (parent && parent != root) {
+ parents.push(parent);
+ parent = parent.parent;
+ }
+
+ // Walk the list backwards (opening from the root of the hierarchy)
+ // opening each folder as we go.
+ for (var i = parents.length - 1; i >= 0; --i) {
+ let index = view.treeIndexForNode(parents[i]);
+ if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE &&
+ view.isContainer(index) && !view.isContainerOpen(index))
+ view.toggleOpenState(index);
+ }
+ // Select the specified node...
+ }
+
+ let index = view.treeIndexForNode(node);
+ if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
+ return;
+
+ view.selection.select(index);
+ // ... and ensure it's visible, not scrolled off somewhere.
+ this.treeBoxObject.ensureRowIsVisible(index);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <property name="result">
+ <getter><![CDATA[
+ try {
+ return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result;
+ }
+ catch (e) {
+ return null;
+ }
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="place">
+ <getter><![CDATA[
+ return this.getAttribute("place");
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("place", val);
+
+ var queriesRef = { };
+ var queryCountRef = { };
+ var optionsRef = { };
+ PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
+ if (queryCountRef.value == 0)
+ queriesRef.value = [PlacesUtils.history.getNewQuery()];
+ if (!optionsRef.value)
+ optionsRef.value = PlacesUtils.history.getNewQueryOptions();
+
+ this.load(queriesRef.value, optionsRef.value);
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="hasSelection">
+ <getter><![CDATA[
+ return this.view && this.view.selection.count >= 1;
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="selectedNodes">
+ <getter><![CDATA[
+ let nodes = [];
+ if (!this.hasSelection)
+ return nodes;
+
+ let selection = this.view.selection;
+ let rc = selection.getRangeCount();
+ let resultview = this.view;
+ for (let i = 0; i < rc; ++i) {
+ let min = { }, max = { };
+ selection.getRangeAt(i, min, max);
+ for (let j = min.value; j <= max.value; ++j) {
+ nodes.push(resultview.nodeForTreeIndex(j));
+ }
+ }
+ return nodes;
+ ]]></getter>
+ </property>
+
+ <method name="toggleCutNode">
+ <parameter name="aNode"/>
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this.view.toggleCutNode(aNode, aValue);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <property name="removableSelectionRanges">
+ <getter><![CDATA[
+ // This property exists in addition to selectedNodes because it
+ // encodes selection ranges (which only occur in list views) into
+ // the return value. For each removed range, the index at which items
+ // will be re-inserted upon the remove transaction being performed is
+ // the first index of the range, so that the view updates correctly.
+ //
+ // For example, if we remove rows 2,3,4 and 7,8 from a list, when we
+ // undo that operation, if we insert what was at row 3 at row 3 again,
+ // it will show up _after_ the item that was at row 5. So we need to
+ // insert all items at row 2, and the tree view will update correctly.
+ //
+ // Also, this function collapses the selection to remove redundant
+ // data, e.g. when deleting this selection:
+ //
+ // http://www.foo.com/
+ // (-) Some Folder
+ // http://www.bar.com/
+ //
+ // ... returning http://www.bar.com/ as part of the selection is
+ // redundant because it is implied by removing "Some Folder". We
+ // filter out all such redundancies since some partial amount of
+ // the folder's children may be selected.
+ //
+ let nodes = [];
+ if (!this.hasSelection)
+ return nodes;
+
+ var selection = this.view.selection;
+ var rc = selection.getRangeCount();
+ var resultview = this.view;
+ // This list is kept independently of the range selected (i.e. OUTSIDE
+ // the for loop) since the row index of a container is unique for the
+ // entire view, and we could have some really wacky selection and we
+ // don't want to blow up.
+ var containers = { };
+ for (var i = 0; i < rc; ++i) {
+ var range = [];
+ var min = { }, max = { };
+ selection.getRangeAt(i, min, max);
+
+ for (var j = min.value; j <= max.value; ++j) {
+ if (this.view.isContainer(j))
+ containers[j] = true;
+ if (!(this.view.getParentIndex(j) in containers))
+ range.push(resultview.nodeForTreeIndex(j));
+ }
+ nodes.push(range);
+ }
+ return nodes;
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="draggableSelection"
+ onget="return this.selectedNodes"/>
+
+ <!-- nsIPlacesView -->
+ <property name="selectedNode">
+ <getter><![CDATA[
+ var view = this.view;
+ if (!view || view.selection.count != 1)
+ return null;
+
+ var selection = view.selection;
+ var min = { }, max = { };
+ selection.getRangeAt(0, min, max);
+
+ return this.view.nodeForTreeIndex(min.value);
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="insertionPoint">
+ <getter><![CDATA[
+ // invalidated on selection and focus changes
+ if (this._cachedInsertionPoint !== undefined)
+ return this._cachedInsertionPoint;
+
+ // there is no insertion point for history queries
+ // so bail out now and save a lot of work when updating commands
+ var resultNode = this.result.root;
+ if (PlacesUtils.nodeIsQuery(resultNode) &&
+ PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
+ return this._cachedInsertionPoint = null;
+
+ var orientation = Ci.nsITreeView.DROP_BEFORE;
+ // If there is no selection, insert at the end of the container.
+ if (!this.hasSelection) {
+ var index = this.view.rowCount - 1;
+ this._cachedInsertionPoint =
+ this._getInsertionPoint(index, orientation);
+ return this._cachedInsertionPoint;
+ }
+
+ // This is a two-part process. The first part is determining the drop
+ // orientation.
+ // * The default orientation is to drop _before_ the selected item.
+ // * If the selected item is a container, the default orientation
+ // is to drop _into_ that container.
+ //
+ // Warning: It may be tempting to use tree indexes in this code, but
+ // you must not, since the tree is nested and as your tree
+ // index may change when folders before you are opened and
+ // closed. You must convert your tree index to a node, and
+ // then use getChildIndex to find your absolute index in
+ // the parent container instead.
+ //
+ var resultView = this.view;
+ var selection = resultView.selection;
+ var rc = selection.getRangeCount();
+ var min = { }, max = { };
+ selection.getRangeAt(rc - 1, min, max);
+
+ // If the sole selection is a container, and we are not in
+ // a flatlist, insert into it.
+ // Note that this only applies to _single_ selections,
+ // if the last element within a multi-selection is a
+ // container, insert _adjacent_ to the selection.
+ //
+ // If the sole selection is the bookmarks toolbar folder, we insert
+ // into it even if it is not opened
+ if (selection.count == 1 && resultView.isContainer(max.value) &&
+ !this.flatList)
+ orientation = Ci.nsITreeView.DROP_ON;
+
+ this._cachedInsertionPoint =
+ this._getInsertionPoint(max.value, orientation);
+ return this._cachedInsertionPoint;
+ ]]></getter>
+ </property>
+
+ <method name="_getInsertionPoint">
+ <parameter name="index"/>
+ <parameter name="orientation"/>
+ <body><![CDATA[
+ var result = this.result;
+ var resultview = this.view;
+ var container = result.root;
+ var dropNearItemId = -1;
+ NS_ASSERT(container, "null container");
+ // 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) {
+ var lastSelected = resultview.nodeForTreeIndex(index);
+ if (resultview.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 item is an open container and the user is
+ // trying to drag into it as a first item, really insert into it.
+ container = lastSelected;
+ orientation = Ci.nsITreeView.DROP_ON;
+ index = 0;
+ }
+ else {
+ // Use the last-selected node's container.
+ container = lastSelected.parent;
+
+ // See comment in the treeView.js's copy of this method
+ 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;
+
+ var queryOptions = PlacesUtils.asQuery(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 {
+ var 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);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <method name="selectAll">
+ <body><![CDATA[
+ this.view.selection.selectAll();
+ ]]></body>
+ </method>
+
+ <!-- This method will select the first node in the tree that matches
+ each given item id. It will open any folder nodes that it needs
+ to in order to show the selected items.
+ -->
+ <method name="selectItems">
+ <parameter name="aIDs"/>
+ <parameter name="aOpenContainers"/>
+ <body><![CDATA[
+ // Never open containers in flat lists.
+ if (this.flatList)
+ aOpenContainers = false;
+ // By default, we do search and select within containers which were
+ // closed (note that containers in which nodes were not found are
+ // closed).
+ if (aOpenContainers === undefined)
+ aOpenContainers = true;
+
+ var ids = aIDs; // don't manipulate the caller's array
+
+ // Array of nodes found by findNodes which are to be selected
+ var nodes = [];
+
+ // Array of nodes found by findNodes which should be opened
+ var nodesToOpen = [];
+
+ // A set of GUIDs of container-nodes that were previously searched,
+ // and thus shouldn't be searched again. This is empty at the initial
+ // start of the recursion and gets filled in as the recursion
+ // progresses.
+ var checkedGuidsSet = new Set();
+
+ /**
+ * Recursively search through a node's children for items
+ * with the given IDs. When a matching item is found, remove its ID
+ * from the IDs array, and add the found node to the nodes dictionary.
+ *
+ * NOTE: This method will leave open any node that had matching items
+ * in its subtree.
+ */
+ function findNodes(node) {
+ var foundOne = false;
+ // See if node matches an ID we wanted; add to results.
+ // For simple folder queries, check both itemId and the concrete
+ // item id.
+ var index = ids.indexOf(node.itemId);
+ if (index == -1 &&
+ node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
+ index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId);
+
+ if (index != -1) {
+ nodes.push(node);
+ foundOne = true;
+ ids.splice(index, 1);
+ }
+
+ var concreteGuid = PlacesUtils.getConcreteItemGuid(node);
+ if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
+ checkedGuidsSet.has(concreteGuid))
+ return foundOne;
+
+ // Only follow a query if it has been been explicitly opened by the caller.
+ let shouldOpen = aOpenContainers && PlacesUtils.nodeIsFolder(node);
+ PlacesUtils.asContainer(node);
+ if (!node.containerOpen && !shouldOpen)
+ return foundOne;
+
+ checkedGuidsSet.add(concreteGuid);
+
+ // Remember the beginning state so that we can re-close
+ // this node if we don't find any additional results here.
+ var previousOpenness = node.containerOpen;
+ node.containerOpen = true;
+ for (var child = 0; child < node.childCount && ids.length > 0;
+ child++) {
+ var childNode = node.getChild(child);
+ var found = findNodes(childNode);
+ if (!foundOne)
+ foundOne = found;
+ }
+
+ // If we didn't find any additional matches in this node's
+ // subtree, revert the node to its previous openness.
+ if (foundOne)
+ nodesToOpen.unshift(node);
+ node.containerOpen = previousOpenness;
+ return foundOne;
+ }
+
+ // Disable notifications while looking for nodes.
+ let result = this.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true
+ try {
+ findNodes(this.result.root);
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+
+ // For all the nodes we've found, highlight the corresponding
+ // index in the tree.
+ var resultview = this.view;
+ var selection = this.view.selection;
+ selection.selectEventsSuppressed = true;
+ selection.clearSelection();
+ // Open nodes containing found items
+ for (let i = 0; i < nodesToOpen.length; i++) {
+ nodesToOpen[i].containerOpen = true;
+ }
+ for (let i = 0; i < nodes.length; i++) {
+ var index = resultview.treeIndexForNode(nodes[i]);
+ if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
+ continue;
+ selection.rangedSelect(index, index, true);
+ }
+ selection.selectEventsSuppressed = false;
+ ]]></body>
+ </method>
+
+ <field name="_contextMenuShown">false</field>
+
+ <method name="buildContextMenu">
+ <parameter name="aPopup"/>
+ <body><![CDATA[
+ this._contextMenuShown = true;
+ return this.controller.buildContextMenu(aPopup);
+ ]]></body>
+ </method>
+
+ <method name="destroyContextMenu">
+ <parameter name="aPopup"/>
+ this._contextMenuShown = false;
+ <body/>
+ </method>
+
+ <property name="ownerWindow"
+ readonly="true"
+ onget="return window;"/>
+
+ <field name="_active">true</field>
+ <property name="active"
+ onget="return this._active"
+ onset="return this._active = val"/>
+
+ </implementation>
+ <handlers>
+ <handler event="focus"><![CDATA[
+ this._cachedInsertionPoint = undefined;
+
+ // See select handler. We need the sidebar's places commandset to be
+ // updated as well
+ document.commandDispatcher.updateCommands("focus");
+ ]]></handler>
+ <handler event="select"><![CDATA[
+ this._cachedInsertionPoint = undefined;
+
+ // This additional complexity is here for the sidebars
+ var win = window;
+ while (true) {
+ win.document.commandDispatcher.updateCommands("focus");
+ if (win == window.top)
+ break;
+
+ win = win.parent;
+ }
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ if (event.target.localName != "treechildren")
+ return;
+
+ let nodes = this.selectedNodes;
+ for (let i = 0; i < nodes.length; i++) {
+ let node = nodes[i];
+
+ // Disallow dragging the root node of a tree.
+ if (!node.parent) {
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // If this node is child of a readonly container (e.g. a livemark)
+ // or cannot be moved, we must force a copy.
+ if (!PlacesControllerDragHelper.canMoveNode(node)) {
+ event.dataTransfer.effectAllowed = "copyLink";
+ break;
+ }
+ }
+
+ this._controller.setDataTransfer(event);
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ if (event.target.localName != "treechildren")
+ return;
+
+ let cell = this.treeBoxObject.getCellAt(event.clientX, event.clientY);
+ let node = cell.row != -1 ?
+ this.view.nodeForTreeIndex(cell.row) :
+ this.result.root;
+ // cache the dropTarget for the view
+ PlacesControllerDragHelper.currentDropTarget = node;
+
+ // We have to calculate the orientation since view.canDrop will use
+ // it and we want to be consistent with the dropfeedback.
+ let tbo = this.treeBoxObject;
+ let rowHeight = tbo.rowHeight;
+ let eventY = event.clientY - tbo.treeBody.boxObject.y -
+ rowHeight * (cell.row - tbo.getFirstVisibleRow());
+
+ let orientation = Ci.nsITreeView.DROP_BEFORE;
+
+ if (cell.row == -1) {
+ // If the row is not valid we try to insert inside the resultNode.
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+ else if (PlacesUtils.nodeIsContainer(node) &&
+ eventY > rowHeight * 0.75) {
+ // If we are below the 75% of a container the treeview we try
+ // to drop after the node.
+ orientation = Ci.nsITreeView.DROP_AFTER;
+ }
+ else if (PlacesUtils.nodeIsContainer(node) &&
+ eventY > rowHeight * 0.25) {
+ // If we are below the 25% of a container the treeview we try
+ // to drop inside the node.
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+
+ if (!this.view.canDrop(cell.row, orientation, event.dataTransfer))
+ return;
+
+ event.preventDefault();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = null;
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+</bindings>