browser/components/places/content/toolbar.xml
author Marco Bonardo <mak77@bonardo.net>
Wed, 24 Sep 2008 20:29:52 +0200
changeset 19645 920a4326d1087b174c2fa2b9a8358e12c697022c
parent 19426 45e42cafab3faabad2b0eff11aca11262bf294f1
child 19695 65db1514e59c9e8b26c8bc962b2f3cb7135fcc75
permissions -rwxr-xr-x
Bug 420449 - Enable shift + drag for folders in bookmarks toolbar on Linux, r=mano

<?xml version="1.0"?>

# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Places Toolbar View.
#
# The Initial Developer of the Original Code is Google Inc.
# Portions created by the Initial Developer are Copyright (C) 2005-2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Annie Sullivan <annie.sullivan@gmail.com>
#   Ben Goodger <beng@google.com>
#   Myk Melez <myk@mozilla.org>
#   Marco Bonardo <mak77@bonardo.net>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK ***** 


<!DOCTYPE bindings [
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
%browserDTD;
]>

<bindings id="placesToolbarBindings"
          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-bar">
    <resources>
      <stylesheet src="chrome://browser/skin/places/places.css"/>
    </resources>

    <content>
      <xul:vbox>
        <xul:hbox class="toolbar-drop-indicator-bar">
          <xul:hbox class="toolbar-drop-indicator"/>
        </xul:hbox>
        <xul:hbox flex="1">
          <xul:hbox class="bookmarks-toolbar-items places-toolbar-items" flex="1">
            <children/>
          </xul:hbox>
          <xul:toolbarbutton class="bookmark-item bookmarks-toolbar-customize"
                             mousethrough="never"
                             label="&bookmarksToolbarItem.label;"/>
        </xul:hbox>
      </xul:vbox>
      <xul:hbox mousethrough="always"
                flex="1"
                pack="end">
        <xul:toolbarbutton type="menu"
                           class="chevron"
                           mousethrough="never"
                           collapsed="true"
                           onpopupshowing="chevronPopupShowing(event);">
          <xul:menupopup anonid="chevronPopup"
#ifndef XP_MACOSX
                         context="placesContext"
#endif
          />
        </xul:toolbarbutton>
      </xul:hbox>
    </content>

    <implementation>
      <constructor><![CDATA[
        this._init();
      ]]></constructor>

      <destructor><![CDATA[
        if (this._result) {
          this._result.viewer = null;
          this._result = null;
        }
      ]]></destructor>

      <property name="controller"
                readonly="true"
                onget="return this._controller;"/>

      <method name="_init">
        <body><![CDATA[
        this._controller = new PlacesController(this);
        this.controllers.appendController(this._controller);

        var t = this;
        window.addEventListener("resize",
                                function f(e) { t.updateChevron(e); },
                                false);

        if (this.hasAttribute("place")) {
          // Do the initial build. 
          this.place = this.place;
        }
        ]]></body>
      </method>

      <field name="_dropIndicatorBar">document.getAnonymousElementByAttribute(this, "class", "toolbar-drop-indicator-bar")</field>
      <field name="_chevron">document.getAnonymousElementByAttribute(this, "class", "chevron")</field>

      <field name="_openedMenuButton">null</field>

      <field name="_result">null</field>

      <!-- nsIPlacesView -->
      <method name="getResult">
        <body><![CDATA[
          return this._result;
        ]]></body>
      </method>

      <!-- nsIPlacesView -->
      <method name="getResultNode">
        <body><![CDATA[
          return this._result.root;
        ]]></body>
      </method>

      <method name="_rebuild">
        <body><![CDATA[
          // Clear out references to existing nodes, since we'll be deleting and re-adding.
          if (this._DNDObserver._overFolder.node)
            this._DNDObserver._clearOverFolder();
          this._openedMenuButton = null;

          while (this.hasChildNodes())
            this.removeChild(this.firstChild);

          var rootNode = this._result.root;
          var cc = rootNode.childCount;
          for (var i = 0; i < cc; ++i)
            this.insertNewItem(rootNode.getChild(i), null);

          var chevronPopup = this._chevron.lastChild;
          if (chevronPopup.hasAttribute("type")) {
            // Otherwise we'll set it when the chevron is enabled (see updateChevron)
            chevronPopup.place = this.place;
          }

          while (chevronPopup.hasChildNodes())
            chevronPopup.removeChild(chevronPopup.lastChild);

          // This needs to be in a timeout to make sure our boxObject has time
          // to get its proper size
          var t = this;
          setTimeout(function() { t.updateChevron(); }, 0);
        ]]></body>
      </method>

      <method name="insertNewItem">
        <parameter name="aChild"/>
        <parameter name="aBefore"/>
        <body><![CDATA[
          var type = aChild.type;
          var button;
          if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
            button = document.createElement("toolbarseparator");
          else {
            button = document.createElement("toolbarbutton");
            button.className = "bookmark-item";
            button.setAttribute("label", aChild.title);
            var iconURI = aChild.icon;
            var iconURISpec = "";
            if (iconURI) {
              iconURISpec = iconURI.spec;
              button.setAttribute("image", iconURISpec);
            }

            if (PlacesUtils.containerTypes.indexOf(type) != -1) {
              button.setAttribute("type", "menu");
              button.setAttribute("container", "true");

              if (PlacesUtils.nodeIsQuery(aChild)) {
                button.setAttribute("query", "true");
                if (PlacesUtils.nodeIsTagQuery(aChild))
                  button.setAttribute("tagContainer", "true");
              }
              else if (PlacesUtils.nodeIsLivemarkContainer(aChild))
                button.setAttribute("livemark", "true");

              var popup = document.createElement("menupopup");
              popup.setAttribute("placespopup", "true");
              button.appendChild(popup);
              popup._result = this._result;
              popup._resultNode = asContainer(aChild);
#ifndef XP_MACOSX
              popup.setAttribute("context", "placesContext");
#endif
              this._containerNodesMap.push({ resultNode: aChild,
                                             domNode: popup });
            }
          }

          button.node = aChild;
          button.node.viewIndex = 0;
          if (aBefore)
            this.insertBefore(button, aBefore);
          else
            this.appendChild(button);
        ]]></body>
      </method>

      <method name="removeItem">
        <parameter name="child"/>
        <body><![CDATA[
          if (PlacesUtils.nodeIsContainer(child.node)) {
            for (var i=0; i < this._containerNodesMap.length; i++) {
              if (this._containerNodesMap[i].resultNode == child.node) {
                this._containerNodesMap.splice(i, 1);
                break;
              }
            }
          }

          // if document.popupNode pointed to this child, null it out,
          // otherwise controller's command-updating may rely on the removed
          // item still being "selected".
          if (document.popupNode == child)
            document.popupNode = null;
          child.parentNode.removeChild(child);
        ]]></body>
      </method>

      <method name="chevronPopupShowing">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var popup = aEvent.target;
          if (popup != this._chevron.firstChild)
            return;

          for (var i = 0; i < popup.childNodes.length; i++)
            popup.childNodes[i].hidden = !this.childNodes[i].collapsed;
        ]]></body>
      </method>

      <method name="getElementWidth">
        <parameter name="element"/>
        <body><![CDATA[
          var style = document.defaultView.getComputedStyle(element, "");
          var leftMargin = style.getPropertyValue("margin-left");
          leftMargin = leftMargin ? Math.round(parseFloat(leftMargin)) : 0;
          var rightMargin = style.getPropertyValue("margin-right");
          rightMargin = rightMargin ? Math.round(parseFloat(rightMargin)) : 0;
          return element.boxObject.width + leftMargin + rightMargin;
        ]]></body>
      </method>

      <method name="updateChevron">
        <parameter name="event"/>
        <body><![CDATA[
          // Ignore events that aren't on the document or the window
          // (html document, tooltips, etc)
          // Do not ignore content window resizes, because they may
          // be the result of the toolbar being shown/hidden
          if (event && event.target != document && event.target != window &&
              event.target != content)
            return;

          if (this.childNodes.length == 0) {
            this._chevron.collapsed = true;
            return;
          }

          var spaceLeft = this.boxObject.width;
          this._chevron.collapsed = false;
          var chevronWidth = this._chevron.boxObject.width;
          var overflowed = false;
          for (var i = 0; i < this.childNodes.length; i++) {
            var child = this.childNodes[i];
            child.collapsed = false;
            spaceLeft -= this.getElementWidth(child);
            var spaceNeeded = (i == this.childNodes.length - 1) ? 0 : chevronWidth;
            if (spaceLeft < spaceNeeded) {
              overflowed = true;
              child.collapsed = true;
            }
          }
          if (!(this._chevron.collapsed = !overflowed)) {
            // Attach the popup binding to the chevron popup
            var popup = this._chevron.firstChild;
            if (!popup.hasAttribute("type")) {
              popup.setAttribute("place", this.place);
              popup.setAttribute("type", "places");
            }
          }

          // We rebuild the chevron on popupShowing, so if it is open
          // we must force a rebuild
          if (this._chevron.open) {
            var popup = this._chevron.firstChild;
            for (var i = 0; i < popup.childNodes.length; i++)
              popup.childNodes[i].hidden = !this.childNodes[i].collapsed;
          }
        ]]></body>
      </method>

      <!-- nsIPlacesView -->
      <property name="place">
        <getter><![CDATA[
          return this.getAttribute("place");
        ]]></getter>
        <setter><![CDATA[ 
          this.setAttribute("place", val);

          var history = PlacesUtils.history;
          var queries = { }, options = { };
          history.queryStringToQueries(val, queries, { }, options);
          if (!queries.value.length) 
            queries.value = [history.getNewQuery()];
          try {
            var result =
              history.executeQueries(queries.value, queries.value.length,
                                     options.value);
            result.viewer = this._viewer;
          }
          catch(ex) {
            // Invalid query, or had no results.
            // This is valid, eg: user deletes their bookmarks toolbar folder. 
          }
          return val;
        ]]></setter>
      </property>

      <!-- nsIPlacesView -->
      <property name="hasSelection">
        <getter><![CDATA[ 
          return this.selectedNode != null;
        ]]></getter>
      </property>

      <!-- nsIPlacesView -->
      <method name="getSelectionNodes">
        <body><![CDATA[
          var selectedNode = this.selectedNode;
          return selectedNode ? [selectedNode] : [];
        ]]></body>
      </method>

      <!-- nsIPlacesView -->
      <method name="getRemovableSelectionRanges">
        <body><![CDATA[
          return [this.getSelectionNodes()];
        ]]></body>
      </method>

      <!-- nsIPlacesView -->
      <method name="getDragableSelection">
        <body><![CDATA[
          return [this._draggedNode];
        ]]></body>
      </method>

      <!-- nsIPlacesView -->
      <property name="selectedNode">
        <getter><![CDATA[
          if (this._contextMenuShown) {
            var popupNode = document.popupNode;
            if (popupNode == this)
              return this.getResultNode();

            return popupNode.node || popupNode.parentNode._resultNode || null;
          }
          return null;
        ]]></getter>
      </property>

      <!-- nsIPlacesView -->
      <property name="insertionPoint">
        <getter><![CDATA[
          // By default, the insertion point is at the top level, at the end. 
          var index = PlacesUtils.bookmarks.DEFAULT_INDEX;
          var container = this._result.root;
          var orientation = Ci.nsITreeView.DROP_BEFORE;
          var isTag = false;

          var selectedNode = this.selectedNode;
          if (selectedNode) {
            var popupNode = document.popupNode;
            if (!popupNode.node) {
              // If a static menuitem is selected the insertion point
              // is inside the folder, at the end.
              container = selectedNode;
              orientation = Ci.nsITreeView.DROP_ON;
            }
            else {
              // In all other cases the insertion point is before that node.
              container = selectedNode.parent;
              index = PlacesUtils.getIndexOfNode(selectedNode);
              isTag = PlacesUtils.nodeIsTagQuery(selectedNode.parent);
            }
          }

          if (PlacesControllerDragHelper.disallowInsertion(container))
            return null;

          return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
                                    index, orientation, isTag);
        ]]></getter>
      </property>

      <!-- nsIPlacesView -->
      <method name="selectAll">
        <body><![CDATA[ 
          // Nothing
        ]]></body>
      </method>

      <method name="selectItems">
        <body><![CDATA[
          // Nothing
        ]]></body>
      </method>

      <!-- nsINavHistoryResultViewer -->
      <field name="_viewer"><![CDATA[({
        _self: this,

        _getPopupForContainer:
        function PMV__getPopupForContainer(aNode) {
          if (this._self._resultNode == aNode)
            return this._self;

          for (var i=0; i < this._self._containerNodesMap.length; i++) {
            if (this._self._containerNodesMap[i].resultNode == aNode)
              return this._self._containerNodesMap[i].domNode;
          }
          throw("Container view not found");
        },

        get result() {
          return this._self._result;
        },

        set result(val) {
          // some methods (e.g. getURLsFromContainer) temporarily null out the
          // viewer when they do temporary changes to the view, this does _not_
          // call setResult(null), but then, we're called again with the result
          // object which is already set for this viewer. At that point,
          // we should do nothing.
          if (this._self._result != val) {
            this._self._containerNodesMap = [];
            this._self._result = val;
            if (val) // this calls _rebuild through invalidateContainer
              val.root.containerOpen = true;
          }
          return val;
        },

        itemInserted: function TV_V_itemInserted(aParentNode, aNode, aIndex) {
          // don't insert new items into the toolbar
          // if the parent is not the root 
          if (aParentNode == this._self.getResultNode()) {
            var children = this._self.childNodes;
            this._self.insertNewItem(aNode,
              aIndex < children.length ? children[aIndex] : null);
            this._self.updateChevron();
          }
          else {
            var popup = this._getPopupForContainer(aParentNode);
            if (!popup._built)
              return;

            var before = popup.childNodes[aIndex] || null;
            this._self.insertNewItemToPopup(aNode, popup, before);
            if (popup._emptyMenuItem)
              popup._emptyMenuItem.hidden = true;
          }
        },

        itemRemoved: function TV_V_itemRemoved(aParentNode, aNode, aIndex) {
          if (aParentNode == this._self.getResultNode()) {
            var children = this._self.childNodes;
            for (var i = 0; i < children.length; i++) {
              if (children[i].node == aNode) {
                this._self.removeItem(children[i]);
                this._self.updateChevron();
                return;
              }
            }
          }
          else {
            var popup = this._getPopupForContainer(aParentNode);
            if (!popup._built)
              return;

            var children = popup.childNodes;
            for (var i = 0; i < children.length; i++) {
              if (children[i].node == aNode) {
                this._self.removeItem(children[i]);
                if (!popup.hasChildNodes() ||
                    (popup.childNodes.length == 1 &&
                     popup.firstChild == popup._emptyMenuItem)) {
                  this._self._showEmptyMenuItem(popup);
                }
                if (popup._endMarker != -1)
                  popup._endMarker--;
                return;
              }
            }
          }
        },

        itemMoved:
        function TV_V_itemMoved(aItem, aOldParent, aOldIndex, aNewParent,
                                aNewIndex) {
          // This cannot actually happen yet (see IDL)
          if (aNewParent != aOldParent)
            return;

          if (aNewParent == this._self.getResultNode()) {
            var children = this._self.childNodes;
            var chevronPopup = this._self._chevron.firstChild;
            for (var i = 0; i < children.length; i++) {
              var button = children[i];
              if (button.node == aItem) {
                this._self.removeChild(button);
                this._self.insertBefore(button, children[aNewIndex]);
                if (chevronPopup) {
                  // Maintain chevron in sync
                  menuitem = chevronPopup.childNodes[i];
                  chevronPopup.removeChild(menuitem);
                  chevronPopup.insertBefore(menuitem,
                                            chevronPopup.childNodes[aNewIndex]);
                }
                this._self.updateChevron();
                return;
              }
            }
          }
          var popup = this._getPopupForContainer(aNewParent);
          var children = popup.childNodes;
          for (var i = 0; i < children.length; i++) {
            var menuItem = children[i];
            if (menuItem.node == aItem) {
              popup.removeChild(menuItem);
              popup.insertBefore(menuItem, children[aNewIndex]);
              return;
            }
          }
        },

        itemChanged: function TV_V_itemChanged(aNode) {
          // this check can be removed once we fix bug #382397
          var parentNode = aNode.parent;
          if (!parentNode)
            return;

          var element;
          var onToolbar = false;
          if (parentNode == this._self.getResultNode()) {
            onToolbar = true;
            var children = this._self.childNodes;
            for (var i = 0; i < children.length; i++) {
              if (children[i].node == aNode) {
                element = children[i];
                break;
              }
            }
            // Don't replace title on toolbarbuttons
            var title = aNode.title;
          }
          else {
            var popup = this._getPopupForContainer(parentNode);
            if (!popup._built)
              return;

            var children = popup.childNodes;
            for (var i = 0; i < children.length; i++) {
              if (children[i].node == aNode) {
                element = children[i];
                break;
              }
            }
            var title = PlacesUIUtils.getBestTitle(aNode);
          }

          if (PlacesUtils.nodeIsSeparator(aNode)) {
            // nothing to do when a separator changes
            return;
          }

          var iconURI = aNode.icon;
          if (iconURI) {
            var spec = iconURI.spec;
            if (element.getAttribute("image") != spec)
              element.setAttribute("image", spec);
          }
          else
            element.removeAttribute("image");

          if (element.getAttribute("label") != title) {
            element.setAttribute("label", title);
            if (onToolbar)
              this._self.updateChevron();
          }

          if (!element.hasAttribute("livemark") &&
              PlacesUtils.nodeIsLivemarkContainer(aNode))
            element.setAttribute("livemark", "true");
        },

        itemReplaced:
        function TV_V_itemReplaced(aParentNode, aOldNode, aNewNode, aIndex) {
          if (aParentNode == this._self.getResultNode()) {
            var children = this._self.childNodes;
            for (var i = 0; i < children.length; i++) {
              if (children[i].node == aOldNode) {
                var next = children[i].nextSibling;
                this._self.removeItem(children[i]);
                this._self.insertNewItem(aNewNode, next);
                this._self.updateChevron();
                return;
              }
            }
          }
          else
            this._forwardToChildView(aParentNode, "itemReplaced", arguments);
        },

        containerOpened: function TV_V_containerOpened(aNode) {
          this.invalidateContainer(aNode);
        },

        containerClosed: function TV_V_containerClosed(aNode) {
          this.invalidateContainer(aNode);
        },

        invalidateContainer: function TV_V_invalidateContainer(aContainer) {
          if (aContainer == this._self.getResultNode()) {
            this._self._containerNodesMap.splice(0);
            this._self._rebuild();
            return;
          }

          function isChildOf(node, container) {
            var parent = node.parent;
            while (parent) {
              if (parent == container)
                return true;
              parent = parent.parent;
            }
            return false;
          }

          var popupToRebuild = null;
          for (var i=0; i < this._self._containerNodesMap.length; i++) {
            var node = this._self._containerNodesMap[i].resultNode;
            
            if (node == aContainer)
              popupToRebuild = this._self._containerNodesMap[i].domNode;
            if (isChildOf(node, aContainer)) {
              this._self._containerNodesMap.splice(i,1);
              i--;
            }
          }

          if (popupToRebuild) {
            popupToRebuild._built = false;

            // if the menupopup is open we should live-update it
            if (popupToRebuild.parentNode.open)
              this._self._rebuildPopup(popupToRebuild);
          }
        },

        invalidateAll: function TV_V_invalidateAll() {
          this._self._containerNodesMap.splice(0);
          this._self._rebuild();
        },

        sortingChanged: function TV_V_sortingChanged(aSortingMode) {
        }
      })]]></field>

      <field name="_DNDObserver"><![CDATA[({
        // Inside the _DNDObserver object's functions, this points to 
        // the _DNDObserver object.  _self points to the toolbar xbl object.
        _self: this,

        // Menu buttons should be opened when the mouse drags over them, and closed
        // when the mouse drags off.  The overFolder object manages opening and closing
        // of folders when the mouse hovers.
        _overFolder: {node: null,
                      openTimer: null,
                      hoverTime: 350,
                      closeTimer: null},

        // timer for turning of indicator bar, to get rid of flicker
        _ibTimer: null, 
 
        _setTimer: function TBV_DO_setTimer(time) {
          var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
          timer.initWithCallback(this, time, timer.TYPE_ONE_SHOT);
          return timer;
        },

        // Function to process all timer notifications.
        notify: function TBV_DO_notify(timer) {
          // Timer to turn off indicator bar.
          if (timer == this._ibTimer) {
            ib = this._self._dropIndicatorBar.removeAttribute('dragging');
            this._ibTimer = null;
          }

          // Timer to open a menubutton that's being dragged over.
          if (timer == this._overFolder.openTimer) {
            // Set the autoopen attribute on the folder's menupopup so that
            // the menu will automatically close when the mouse drags off of it.
            this._overFolder.node.lastChild.setAttribute("autoopened", "true");
            this._overFolder.node.open = true;
            this._overFolder.openTimer = null;
          }

          // Timer to close a menubutton that's been dragged off of.
          if (timer == this._overFolder.closeTimer) {
            // Only close the menubutton if the drag session isn't currently over
            // it or one of its children.  (The autoopened attribute will let the menu
            // know to close later if the menu is still being dragged over.)
            var currentNode = PlacesControllerDragHelper.currentDropTarget;
            var inHierarchy = false;
            while (currentNode) {
              if (currentNode == this._self) {
                inHierarchy = true;
                break;
              }
              currentNode = currentNode.parentNode;
            }
            // The _clearOverFolder() function will close the menu for _overFolder.node.
            // So null it out if we don't want to close it.
            if (inHierarchy)
              this._overFolder.node = null;
            
            // Clear out the folder and all associated timers.
            this._clearOverFolder();
          }
        },

        // The mouse is no longer dragging over the stored menubutton.
        // Close the menubutton, clear out drag styles, and clear all
        // timers for opening/closing it.
        _clearOverFolder: function TBV_DO_clearOverFolder() {
          if (this._overFolder.node && this._overFolder.node.lastChild) {
            if (!this._overFolder.node.lastChild.hasAttribute("dragover")) {
              this._overFolder.node.lastChild.hidePopup();
            }
            this._overFolder.node.removeAttribute("dragover");
            this._overFolder.node = null;
          }
          if (this._overFolder.openTimer) {
            this._overFolder.openTimer.cancel();
            this._overFolder.openTimer = null;
          }
          if (this._overFolder.closeTimer) {
            this._overFolder.closeTimer.cancel();
            this._overFolder.closeTimer = null;
          }
        },

        // This function returns information about where to drop when
        // dragging over this menu--insertion point, child index to drop
        // before, and folder to drop into.
        _getDropPoint: function TBV_DO_getDropPoint(event) {
          // Can't drop if the toolbar isn't a folder.
          var result = this._self.getResult();
          if (!PlacesUtils.nodeIsFolder(result.root))
            return null;

          var isRTL = document.defaultView
                              .getComputedStyle(this._self.parentNode, "")
                              .direction == "rtl";

          var dropPoint = { ip: null, beforeIndex: null, folderNode: null };
          // Loop through all the nodes to see which one this should
          // get dropped in/next to
          for (var i = 0; i < this._self.childNodes.length; i++) {
            var xulNode = this._self.childNodes[i];
            if (PlacesUtils.nodeIsFolder(xulNode.node) &&
                !PlacesUtils.nodeIsReadOnly(xulNode.node)) {
              // This is a folder. If the mouse is in the left 25% of the
              // node (or 25% of the right, in RTL UI), drop before the folder.
              // If it's in the middle 50%, drop into the folder. If it's past
              // that, drop after.
              if ((isRTL && event.clientX > xulNode.boxObject.x +
                                            (xulNode.boxObject.width * 0.75)) ||
                  (!isRTL && event.clientX < xulNode.boxObject.x + 
                                             (xulNode.boxObject.width * 0.25))) {
                // Drop to the left of this folder.
                dropPoint.ip =
                  new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
                                     i, -1);
                dropPoint.beforeIndex = i;
                return dropPoint;
              }
              else if ((isRTL && event.clientX > xulNode.boxObject.x + 
                                                 (xulNode.boxObject.width * 0.25)) ||
                       (!isRTL && event.clientX < xulNode.boxObject.x +
                                                  (xulNode.boxObject.width * 0.75))) {
                // Drop inside this folder.
                dropPoint.ip =
                  new InsertionPoint(PlacesUtils.getConcreteItemId(xulNode.node),
                                     -1, 1,
                                     PlacesUtils.nodeIsTagQuery(xulNode.node));
                dropPoint.beforeIndex = i;
                dropPoint.folderNode = xulNode;
                return dropPoint;
              }
            }
            else {
              // This is a non-folder node. If the mouse is left (or right, in
              // RTL UI) of the middle, drop before the folder.  Otehrwise,
              // we'll drop after
              if ((isRTL && event.clientX > xulNode.boxObject.x + (xulNode.boxObject.width / 2)) ||
                  (!isRTL && event.clientX < xulNode.boxObject.x + (xulNode.boxObject.width / 2))) {
                // Drop before this bookmark.
                dropPoint.ip =
	                new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
	                                   i, -1);
                dropPoint.beforeIndex = i;
                return dropPoint;
              }
            }
          }
          // Should drop after the last node.
          dropPoint.ip =
        	  new InsertionPoint(PlacesUtils.getConcreteItemId(result.root),
	                             -1, 1);
          dropPoint.beforeIndex = -1;
          return dropPoint;
        },

        onDragStart: function TBV_DO_onDragStart(aEvent, aXferData, aDragAction) {
          var draggedXulNode = aEvent.target;
          // sub menus have their own d&d handlers
          if (draggedXulNode.parentNode != this._self)
            return false;

          if (draggedXulNode.localName == "toolbarbutton" &&
              draggedXulNode.getAttribute("type") == "menu") {
#ifdef XP_WIN
            // Support folder dragging on the personal toolbar when the user
            // holds the "alt" or "shift" key while dragging.
            // Ctrl+drag is Copy
            if (!aEvent.shiftKey && !aEvent.altKey && !aEvent.ctrlKey)
              return false;
#else
            // Support folder dragging on the personal toolbar when the user
            // holds the "shift" key while dragging
            // Ctrl+drag is Copy
            if (!aEvent.shiftKey && !aEvent.ctrlKey)
              return false;
#endif
            draggedXulNode.firstChild.hidePopup();
          }

          // activate the view and cache the dragged node
          this._self._draggedNode = draggedXulNode.node;
          this._self.focus();

          this._self._controller.setDataTransfer(aEvent);
          return true;
        },

        canDrop: function TBV_DO_canDrop(aEvent, aDragSession) {
          // Cache the dataTransfer
          PlacesControllerDragHelper.currentDataTransfer = aEvent.dataTransfer;

          var ip = this._self.insertionPoint;
          return ip && PlacesControllerDragHelper.canDrop(ip);
        },

        onDragOver: function TBV_DO_onDragOver(event, flavor, session) {
          PlacesControllerDragHelper.currentDropTarget = event.target;
          var dropPoint = this._getDropPoint(event);

          var ib = this._self._dropIndicatorBar;
          if (this._ibTimer) {
            this._ibTimer.cancel();
            this._ibTimer = null;
          }

          if (dropPoint.folderNode ||
              event.originalTarget == this._self._chevron) {
            // Dropping over a menubutton or chevron button
            // set styles and timer to open relative menupopup
            var overNode = dropPoint.folderNode || this._self._chevron;
            if (this._overFolder.node != overNode) {
              this._clearOverFolder();
              this._overFolder.node = overNode;
              this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
            }
            if (!this._overFolder.node.hasAttribute("dragover"))
              this._overFolder.node.setAttribute("dragover", "true");

            ib.removeAttribute("dragging");
          }
          else {
            // Dragging over a normal toolbarbutton,
            // show indicator bar and move it to the appropriate drop point.
            if (!ib.hasAttribute("dragging"))
              ib.setAttribute("dragging", "true");
            var ind = ib.firstChild;
            var halfInd = ind.boxObject.width / 2;
            var direction = document.defaultView.getComputedStyle(this._self.parentNode, "").direction;
            if (direction == "ltr") {
              halfInd = Math.ceil(halfInd);
              if (!this._self.childNodes.length)
                ind.style.marginLeft = 0 - this._self.boxObject.x - halfInd + 'px'
              else if (dropPoint.beforeIndex == -1)
                ind.style.marginLeft = this._self.lastChild.boxObject.x + 
                                       this._self.lastChild.boxObject.width - this._self.boxObject.x - halfInd + 'px';
              else
                ind.style.marginLeft = this._self.childNodes[dropPoint.beforeIndex].boxObject.x -
                                       this._self.boxObject.x - halfInd + 'px';
            }
            else {
              halfInd = Math.floor(halfInd);
              if (this._self.childNodes.length == 0)
                ind.style.marginRight = this._self.boxObject.width + 'px';
              else if (dropPoint.beforeIndex == -1) {
                ind.style.marginRight = this._self.boxObject.width -
                                        (this._self.childNodes[this._self.childNodes.length - 1].boxObject.x +
                                        halfInd) +'px';
              }
              else {
                ind.style.marginRight = this._self.boxObject.width -
                                        (this._self.childNodes[dropPoint.beforeIndex].boxObject.x +
                                        this._self.childNodes[dropPoint.beforeIndex].boxObject.width -
                                        this._self.boxObject.x + halfInd) + 'px';
              }
            }
            // Clear out old folder information
            this._clearOverFolder();
          }
        },

        onDrop: function TBV_DO_onDrop(event, dropData, session) {
          // Cache the dataTransfer
          PlacesControllerDragHelper.currentDataTransfer = event.dataTransfer;

          var dropPoint = this._getDropPoint(event);
          if (!dropPoint)
            return;
          PlacesControllerDragHelper.onDrop(dropPoint.ip);
        },

        onDragExit: function TBV_DO_onDragExit(event, session) {
          PlacesControllerDragHelper.currentDropTarget = null;
          PlacesControllerDragHelper.currentDataTransfer = null;

          // Set timer to turn off indicator bar (if we turn it off
          // here, dragenter might be called immediately after, creating
          // flicker.)
          if (this._ibTimer)
            this._ibTimer.cancel();
          this._ibTimer = this._setTimer(10);
          // Close any folder being hovered over
          if (this._overFolder.node)
            this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);

          this._self._draggedNode = null;
        },

        getSupportedFlavours: function TBV_DO_getSupportedFlavours() {
          return PlacesControllerDragHelper.flavourSet;
        }
      })]]></field>

      <method name="checkForMenuEvent">
        <parameter name="event"/>
        <parameter name="action"/>
        <body><![CDATA[
          // It seems that even if the menu drag/drop event
          // handlers set their phase to capturing, toolbarbutton
          // menu events come to the toolbar first, and don't bubble.
          // So if this is a menu/menuitem, try to send the event to its
          // xbl handler.
          if (event.target.localName.indexOf("menu") == 0) {
            var parent = event.target.parentNode;
            // XULDocument has no getAttribute() function, so check for it before calling.
            while (parent && parent.getAttribute) {
              if (parent.getAttribute("type") == "places") {
                nsDragAndDrop[action](event, parent._DNDObserver);
                return true;
              }
              parent = parent.parentNode;
            }
          }
          return false;
        ]]></body>
      </method>

      <property name="selType" onget="return 'single';"/>

      <method name="buildContextMenu">
        <parameter name="aPopup"/>
        <body><![CDATA[
          this._contextMenuShown = true;
          this.focus();
          // The above call to focus activates the controller, but it may not
          // always fire a consumable event for commandUpdater, so we force a
          // command update.
          window.updateCommands("focus");
          var show = this.controller.buildContextMenu(aPopup);
          if (show) {
            // disable the Delete command if the selection isn't explicit
            if (document.popupNode && document.popupNode.localName == "menupopup")
              document.getElementById("cmd_delete").setAttribute("disabled", "true");
            return true;
          }
          return false;
        ]]></body>
      </method>

      <method name="destroyContextMenu">
        <parameter name="aPopup"/>
        <body><![CDATA[
          this._contextMenuShown = false;
          if (window.content)
            window.content.focus();
        ]]></body>
      </method>

      <method name="_showEmptyMenuItem">
        <parameter name="aPopup"/>
        <body><![CDATA[
          if (aPopup._emptyMenuItem) {
            aPopup._emptyMenuItem.hidden = false;
            return;
          }

          var label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
          aPopup._emptyMenuItem = document.createElement("menuitem");
          aPopup._emptyMenuItem.setAttribute("label", label);
          aPopup._emptyMenuItem.setAttribute("disabled", true);
          aPopup.appendChild(aPopup._emptyMenuItem);
        ]]></body>
      </method>

      <method name="insertNewItemToPopup">
        <parameter name="aChild"/>
        <parameter name="aParentPopup"/>
        <parameter name="aBefore"/>
        <body><![CDATA[
          var element =
            PlacesUIUtils.createMenuItemForNode(aChild, this._containerNodesMap);

          if (aBefore)
            aParentPopup.insertBefore(element, aBefore);
          else {
            // Add the new element to the menu.  If there is static content at
            // the end of the menu, add the element before that.  Otherwise,
            // just add to the end.
            if (aParentPopup._endMarker != -1) {
              aParentPopup.insertBefore(element,
                                        aParentPopup.childNodes[aParentPopup._endMarker]);
            }
            else
              aParentPopup.appendChild(element);
          }

          if (aParentPopup._endMarker != -1)
            aParentPopup._endMarker++;
        ]]></body>
      </method>

      <method name="_containerPopupShowing">
        <parameter name="aPopup"/>
        <body><![CDATA[
          if (!aPopup._built)
            this._rebuildPopup(aPopup);
        ]]></body>
      </method>

      <method name="_rebuildPopup">
        <parameter name="aPopup"/>
        <body><![CDATA[
          PlacesUIUtils.cleanPlacesPopup(aPopup);

          var resultNode = aPopup._resultNode;
          if (!resultNode.containerOpen)
            resultNode.containerOpen = true;

          var cc = resultNode.childCount;
          if (cc > 0) {
            if (aPopup._emptyMenuItem)
              aPopup._emptyMenuItem.hidden = true;

            for (var i = 0; i < cc; ++i) {
              var child = resultNode.getChild(i);
              this.insertNewItemToPopup(child, aPopup, null);
            }
          }
          else {
            // This menu is empty.  If there is no static content, add
            // an element to show it is empty.
            if (aPopup._startMarker == -1 && aPopup._endMarker == -1)
              this._showEmptyMenuItem(aPopup);
          }
          aPopup._built = true;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="mouseover"><![CDATA[
        var button = event.target;
        if (button.parentNode == this && button.node &&
            PlacesUtils.nodeIsURI(button.node))
          window.XULBrowserWindow.setOverLink(event.target.node.uri, null);
      ]]></handler>
      <handler event="mouseout"><![CDATA[
        window.XULBrowserWindow.setOverLink("", null);
      ]]></handler>
      <handler event="draggesture"><![CDATA[
        if (event.target.localName == "toolbarbutton" ||
            event.target.localName == "toolbarseparator")
          nsDragAndDrop.startDrag(event, this._DNDObserver);
      ]]></handler>
      <handler event="dragover"><![CDATA[
        if (!this.checkForMenuEvent(event, "dragOver"))
          nsDragAndDrop.dragOver(event, this._DNDObserver);
      ]]></handler>
      <handler event="dragdrop"><![CDATA[
        if (!this.checkForMenuEvent(event, "drop"))
          nsDragAndDrop.drop(event, this._DNDObserver);
      ]]></handler>
      <handler event="dragexit"><![CDATA[
        if (!this.checkForMenuEvent(event, "dragExit"))
          nsDragAndDrop.dragExit(event, this._DNDObserver);
      ]]></handler>
      <handler event="popupshowing" phase="capturing"><![CDATA[
      // Don't show the popup if we are dragging a container.
      if (this._draggingContainer) {
          this._draggingContainer = false;
#ifdef MOZ_WIDGET_GTK2
          // Allow drag and drop of folders in Linux.
          // We must prevent popupshowing event from firing when shift is pressed.
          event.preventDefault();
          return false;
#endif
        }

        var popup = event.originalTarget;

        // Avoid handling popupshowing of inner views
        if (popup._resultNode && PlacesUIUtils.getViewForNode(popup) == this)
          this._containerPopupShowing(popup);

        var parent = popup.parentNode;
        if (parent.localName == "toolbarbutton" &&
            !PlacesControllerDragHelper.getSession())
          this._openedMenuButton = parent;
      ]]></handler>
      <handler event="popuphidden"><![CDATA[
        var popup = event.originalTarget;

        // Avoid handling popupshowing of inner views
        if (popup._resultNode && PlacesUIUtils.getViewForNode(popup) == this) {
          // UI performance: folder queries are cheap, keep the resultnode open
          // so we don't rebuild its contents whenever the popup is reopened.
          if (!PlacesUtils.nodeIsFolder(popup._resultNode))
            popup._resultNode.containerOpen = false;
        }

        var parent = popup.parentNode;
        if (parent.localName == "toolbarbutton" &&
            !PlacesControllerDragHelper.getSession())
          this._openedMenuButton = null;
      ]]></handler>

      <handler event="mousedown"><![CDATA[
        // Allow drag and drop of folders in Linux.
        // We must prevent popupshowing event from firing when shift is pressed.
        var target = event.originalTarget;
        if (event.button == 0 && event.shiftKey &&
            target.localName == "toolbarbutton" && target.type == "menu")
          this._draggingContainer = true;
      ]]></handler>

      <handler event="mousemove"><![CDATA[
        if (this._openedMenuButton == null ||
            PlacesControllerDragHelper.getSession())
          return;

        var target = event.originalTarget;
        if (this._openedMenuButton != target &&
            target.localName == "toolbarbutton" &&
            target.type == "menu") {
          this._openedMenuButton.open = false;
          target.open = true;
        }
      ]]></handler>
    </handlers>
  </binding>

</bindings>