<?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>
#   Asaf Romano <mano@mozilla.com>
#
# 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:toolbarbutton class="bookmark-item bookmarks-toolbar-customize"
                           mousethrough="never"
                           label="&bookmarksToolbarItem.label;"/>
        <xul:hbox flex="1">
          <xul:hbox align="center">
            <xul:image class="toolbar-drop-indicator"
                       mousethrough="always"
                       collapsed="true"/>
          </xul:hbox>
          <xul:scrollbox orient="horizontal"
                         class="bookmarks-toolbar-items"
                         flex="1">
            <children/>
          </xul:scrollbox>
          <xul:toolbarbutton type="menu"
                             class="chevron"
                             mousethrough="never"
                             collapsed="true"
                             tooltiptext="&bookmarksToolbarChevron.tooltip;"
                             onpopupshowing="_onChevronPopupShowing(event);">
            <xul:menupopup anonid="chevronPopup"
                           popupsinherittooltip="true"
                           xbl:inherits="tooltip"
#ifndef XP_MACOSX
                           context="placesContext"
#endif
            />
          </xul:toolbarbutton>
        </xul:hbox>
    </content>

    <implementation implements="nsIAccessibleProvider, nsITimerCallback, nsIDOMEventListener">
      <constructor><![CDATA[
        this._init();
      ]]></constructor>

      <destructor><![CDATA[
        this._scrollbox.removeEventListener("overflow", this, false);
        this._scrollbox.removeEventListener("underflow", this, false);
        window.removeEventListener("resize", this, false);

        if (this._result) {
          this._resultNode.containerOpen = false;
          this._resultNode = null;
          this._result.viewer = null;
          this._result = null;
        }
      ]]></destructor>

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

      <method name="_init">
        <body><![CDATA[
          // XBL bug is in the middle...
          // When toolbar customization is opened, this binding is attached
          // again, as a result of adding the item under the wrapper.  However,
          // the binding isn't detached from the "original" hbox element due
          // to bug 83635.
          //
          // Then, when the customization dialog is closed, the binding is
          // attached the third time, as a result of adding our element back to
          // the toolbar.
          //
          // So, We'll just continue using the original binding, which was
          // never removed, and avoid using the new bindings.  This means that
          // this workaround will work just until bug 83635 is fixed.
          //
          // However, when the binding is "reconstructed", we do need to add
          // back the event listeners and the places controller.
          //
          // Note: we could avoid part of this mess by moving the "Bookmark
          // Toolbar Items" placeholder out of this binding.

          // We also need to avoid initializing _result and _resultNode and
          // _controller as XBL fields.  Otherwise, they'll be unset when the
          // "extra" bindings are applied.

          this._scrollbox.addEventListener("overflow", this, false);
          this._scrollbox.addEventListener("underflow", this, false);
          window.addEventListener("resize", this, false);

          if (this._result === undefined) {
            this._result = null;
            this._resultNode = null;
            if (this.hasAttribute("place")) {
              // Do the initial build.
              this.place = this.place;
            }
          }

          // Attach the places controller.
          if (!this._controller) 
            this._controller = new PlacesController(this);

          this.controllers.appendController(this._controller);
        ]]></body>
      </method>

      <field name="_scrollbox">
        document.getAnonymousElementByAttribute(this, "class",
                                                "bookmarks-toolbar-items")
      </field>
      <field name="_dropIndicator">
        document.getAnonymousElementByAttribute(this, "class",
                                                "toolbar-drop-indicator")
      </field>
      <field name="_chevron">
        document.getAnonymousElementByAttribute(this, "class", "chevron")
      </field>
      <field name="_chevronPopup">
        document.getAnonymousElementByAttribute(this, "anonid", "chevronPopup")
      </field>

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

      <field name="_isRTL">
        document.defaultView.getComputedStyle(this.parentNode, "")
                            .direction == "rtl"
      </field>

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

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

      <method name="_rebuild">
        <body><![CDATA[
          // Clear out references to existing nodes, since they will be removed
          // and re-added.
          if (this._overFolder.node)
            this._clearOverFolder();
          this._openedMenuButton = null;

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

          let cc = this._resultNode.childCount;
          for (let i = 0; i < cc; ++i)
            this.insertNewItem(this._resultNode.getChild(i), null);

          if (this._chevronPopup.hasAttribute("type")) {
            // Chevron has already been initialized, but since we are forcing
            // a rebuild of the toolbar, it has to be rebuilt.
            // Otherwise, it will be initialized when the toolbar overflows.
            this._chevronPopup.place = this.place;
          }
        ]]></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 icon = aChild.icon;
            if (icon)
              button.setAttribute("image", icon);

            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._resultNode = asContainer(aChild);
#ifndef XP_MACOSX
              popup.setAttribute("context", "placesContext");
#endif
            }
            else if (PlacesUtils.nodeIsURI(aChild))
              button.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
          }

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

      <method name="removeItem">
        <parameter name="child"/>
        <body><![CDATA[
          // 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="_updateChevronPopupNodesVisibility">
        <body><![CDATA[
          for (let i = 0; i < this._chevronPopup.childNodes.length; i++) {
            this._chevronPopup.childNodes[i].hidden =
              this.childNodes[i].style.visibility != "hidden";
          }
        ]]></body>
      </method>

      <method name="_onChevronPopupShowing">
        <parameter name="aEvent"/>
        <body><![CDATA[
          // Handle popupshowing only for the chevron popup, not for
          // nested ones.
          if (aEvent.target != this._chevronPopup)
            return;

          this._updateChevronPopupNodesVisibility();
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          // Both overflow/underflow and resize events should not be handled
          // for descendant nodes.
          if (aEvent.target != aEvent.currentTarget)
            return;

          switch (aEvent.type) {
            case "resize":
              // This handler updates nodes visibility in both the toolbar
              // and the chevron popup when a window resize does not change
              // the overflow status of the toolbar.
              break;

            case "overflow":
              // Ignore purely vertical overflows.
              if (aEvent.detail == 0)
                return;

              // Attach the popup binding to the chevron popup if it has not yet
              // been initialized.
              if (!this._chevronPopup.hasAttribute("type")) {
                this._chevronPopup.setAttribute("place", this.place);
                this._chevronPopup.setAttribute("type", "places");
              }
              this._chevron.collapsed = false;
              break;

            case "underflow":
              // Ignore purely vertical underflows.
              if (aEvent.detail == 0)
                return;

              this._chevron.collapsed = true;
              break;
          }

          this.updateChevron();
        ]]></body>
      </method>

      <method name="updateChevron">
        <body><![CDATA[
          // If the chevron is collapsed there's nothing to update.
          if (this._chevron.collapsed)
            return;

          // XXX (bug 508816) Scrollbox does not handle correctly RTL mode.
          // This workarounds the issue scrolling the box to the right.
          if (this._isRTL)
            this._scrollbox.scrollLeft = this._scrollbox.scrollWidth;

          // Update the chevron on a timer.  This will avoid repeated work when
          // lot of changes happen in a small timeframe.
          if (this._updateChevronTimer)
            this._updateChevronTimer.cancel();
          this._updateChevronTimer = this._setTimer(100);
        ]]></body>
      </method>

      <method name="_updateChevronTimerCallback">
        <body><![CDATA[
          var scrollRect = this._scrollbox.getBoundingClientRect();
          var childOverflowed = false;
          for (let i = 0; i < this.childNodes.length; i++) {
            let child = this.childNodes[i];
            // Once a child overflows, all the next ones will.
            if (!childOverflowed) {
              let childRect = child.getBoundingClientRect();
              childOverflowed = this._isRTL ? (childRect.left < scrollRect.left)
                                            : (childRect.right > scrollRect.right);
            }
            child.style.visibility = childOverflowed ? "hidden" : "visible";
          }

          // We rebuild the chevron on popupShowing, so if it is open
          // we must update it.
          if (this._chevron.open)
            this._updateChevronPopupNodesVisibility();
        ]]></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 his 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[
          // On static content the current selectedNode would be the selection's
          // parent node. We don't want to allow removing a node when the
          // selection is not explicit.
          if (document.popupNode &&
              (document.popupNode == "menupopup" || !document.popupNode.node))
            return [];

          return [this.getSelectionNodes()];
        ]]></body>
      </method>

      <!-- nsIPlacesView -->
      <method name="getDraggableSelection">
        <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._resultNode;
          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,

        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) {
            if (this._self._result)
              this._self._resultNode.containerOpen = false;

            this._self._result = val;
            if (val) {
              this._self._resultNode = val.root;
              this._self._resultNode._DOMElement = this._self;
              // This calls _rebuild through invalidateContainer.
              this._self._resultNode.containerOpen = true;
            }
            else
              this._self._resultNode = null;
          }
          return val;
        },

        nodeInserted: function TV_V_nodeInserted(aParentNode, aNode, aIndex) {
          let parentElt = aParentNode._DOMElement;
          NS_ASSERT(parentElt, "parent node must have _DOMElement set");

          if (parentElt == this._self) {
            // Node is on the toolbar.
            let children = this._self.childNodes;
            this._self.insertNewItem(aNode,
              aIndex < children.length ? children[aIndex] : null);
            this._self.updateChevron();
          }
          else if (parentElt._built) {
            // Node is within a built menu.
            let popup = parentElt.firstChild;
            let before = popup.childNodes[aIndex] || null;
            this._self.insertNewItemToPopup(aNode, popup, before);
            if (popup._emptyMenuItem)
              popup._emptyMenuItem.hidden = true;
          }
        },

        nodeRemoved: function TV_V_nodeRemoved(aParentNode, aNode, aIndex) {
          let parentElt = aParentNode._DOMElement;
          let nodeElt = aNode._DOMElement;

          NS_ASSERT(parentElt, "parent node must have _DOMElement set");
          NS_ASSERT(nodeElt, "node must have _DOMElement set");

          if (parentElt == this._self) {
            // Node is on the toolbar.
            this._self.removeChild(nodeElt);
            this._self.updateChevron();
          }
          else if (parentElt._built) {
            // Node is within a built menu.
            var popup = parentElt.firstChild;
            popup.removeChild(nodeElt);
            if (!popup.hasChildNodes() ||
                (popup.childNodes.length == 1 &&
                popup.firstChild == popup._emptyMenuItem))
              this._self._showEmptyMenuItem(popup);

            if (popup._endMarker != -1)
              popup._endMarker--;
          }
        },

        nodeMoved:
        function TV_V_nodeMoved(aNode,
                                aOldParent, aOldIndex,
                                aNewParent, aNewIndex) {
          // Note: the current implementation of moveItem does not actually
          // use this notification when the item in question is moved from one
          // folder to another.  Instead, it calls nodeRemoved and nodeInserted
          // for the two folders.  Thus, we can assume aOldParent == aNewParent.

          let nodeElt = aNode._DOMElement;
          NS_ASSERT(nodeElt, "node must have _DOMElement set");

          // If our root node is a folder, it might be moved. There's nothing
          // we need to do in that case.
          if (nodeElt == this._self)
            return;

          let parentElt = aNewParent._DOMElement;
          NS_ASSERT(parentElt, "parent node must have _DOMElement set");

          if (parentElt == this._self) {
            // Container is on the toolbar.

            // Move the node.
            this._self.removeChild(nodeElt);
            this._self.insertBefore(nodeElt, this._self.childNodes[aNewIndex]);

            // If the chevron popup is open, keep it in sync.
            if (this._self._chevron.open) {
              let chevronPopup = this._self._chevronPopup;
              let menuitem = chevronPopup.childNodes[aOldIndex];
              chevronPopup.removeChild(menuitem);
              chevronPopup.insertBefore(menuitem,
                                        chevronPopup.childNodes[aNewIndex]);
            }
            this._self.updateChevron();
          }
          else if (parentElt._built) {
            // Container is within a built menu.

            // parentElt is the <menu> element for the container,
            // we need the <menupopup>.
            var popup = parentElt.firstChild;

            // Move the node.
            popup.removeChild(nodeElt);
            popup.insertBefore(nodeElt, popup.childNodes[aNewIndex]);
          }
        },

        nodeTitleChanged: function TV_V_nodeTitleChanged(aNode, aNewTitle) {
          let nodeElt = aNode._DOMElement;
          NS_ASSERT(nodeElt, "node must have _DOMElement set");

          // There's no UI representation for the root node, thus there's
          // nothing to be done when the title changes.
          if (nodeElt == this._self)
            return;

          if (nodeElt.parentNode == this._self) {
            // Node is on the toolbar
            nodeElt.label = aNewTitle;
            this._self.updateChevron();
          }
          else {
            // Node is within a built menu.
            nodeElt.label = aNewTitle || PlacesUIUtils.getBestTitle(aNode);
          }
        },

        nodeURIChanged: function TV_V_nodeURIChanged(aNode, aURIString) {
          let nodeElt = aNode._DOMElement;
          NS_ASSERT(nodeElt, "node must have _DOMElement set");

          nodeElt.setAttribute("scheme",
                               PlacesUIUtils.guessUrlSchemeForUI(aURIString));
        },

        nodeIconChanged: function TV_V_nodeIconChanged(aNode) {
          let nodeElt = aNode._DOMElement;
          NS_ASSERT(nodeElt, "node must have _DOMElement set");

          // There's no UI representation for the root node, thus there's
          // nothing to be done when the icon changes.
          if (nodeElt == this._self)
            return;

          let icon = aNode.icon;
          if (icon) {
            if (nodeElt.getAttribute("image") != icon)
              nodeElt.setAttribute("image", icon);
          }
          else
            nodeElt.removeAttribute("image");
        },

        nodeAnnotationChanged:
        function TV_V_nodeAnnotationChanged(aNode, aAnno) {
          // Ensure the changed annotation is a livemark one.
          if (/^livemark\//.test(aAnno) &&
              PlacesUtils.nodeIsLivemarkContainer(aNode)) {
            let nodeElt = aNode._DOMElement;
            NS_ASSERT(nodeElt, "node must have _DOMElement set");

            if (!nodeElt.hasAttribute("livemark"))
              nodeElt.setAttribute("livemark", "true");

            // Add or remove the livemark status menuitem.
            PlacesUIUtils.ensureLivemarkStatusMenuItem(nodeElt.firstChild);
          }
        },

        nodeHistoryDetailsChanged: function() { },
        nodeTagsChanged: function() { },
        nodeDateAddedChanged: function() { },
        nodeLastModifiedChanged: function() { },
        nodeKeywordChanged: function() { },

        nodeReplaced:
        function TV_V_nodeReplaced(aParentNode, aOldNode, aNewNode, aIndex) {
          let nodeElt = aOldNode._DOMElement;
          NS_ASSERT(nodeElt, "node must have _DOMElement set");

          // No worries: If nodeElt is the last item (i.e. no nextSibling),
          // insertNewItem/insertNewItemToPopup will insert the new element as
          // the last item.
          let next = nodeElt.nextSibling;

          let parentElt = aParentNode._DOMElement;
          NS_ASSERT(parentElt, "parent node must have _DOMElement set");
          if (parentElt == this._self) {
            // Node is on the toolbar.
            this._self.removeItem(nodeElt);
            this._self.insertNewItem(aNewNode, next);
            this._self.updateChevron();
          }
          else if (parentElt._built) {
            // Node is within a built menu.
            let popup = parentElt.firstChild;
            popup.removeItem(nodeElt);
            this._self.insertNewItemToPopup(aNewNode, popup, next);
          }
        },

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

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

        invalidateContainer: function TV_V_invalidateContainer(aContainer) {
          let containerNodeElt = aContainer._DOMElement;
          NS_ASSERT(containerNodeElt, "node must have _DOMElement set");

          if (containerNodeElt == this._self) {
            // Container is the toolbar itself.
            this._self._rebuild();
          }
          else if (containerNodeElt._built) {
            // Container is a built menu.
            containerNodeElt._built = false;
            // If the menupopup is open we should live-update it.
            if (containerNodeElt.open)
              this._self._rebuildPopup(containerNodeElt.firstChild);
          }
        },

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

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

      <method name="buildContextMenu">
        <parameter name="aPopup"/>
        <body><![CDATA[
          this._contextMenuShown = true;
          window.updateCommands("places");
          return this.controller.buildContextMenu(aPopup);
        ]]></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);

          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) {
              let lastNode = aParentPopup.childNodes[aParentPopup._endMarker];
              aParentPopup.insertBefore(element, lastNode);
            }
            else
              aParentPopup.appendChild(element);
          }

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

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

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

          // If this is a livemark container check if the status menuitem has
          // to be added or removed.
          if (PlacesUtils.nodeIsLivemarkContainer(aPopup._resultNode))
            PlacesUIUtils.ensureLivemarkStatusMenuItem(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 (let 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.parentNode._built = true;
        ]]></body>
      </method>

      <field name="_overFolder"><![CDATA[
       (
         // Menu buttons should be opened when the mouse drags over them, and
         // closed when the mouse drags off. This object manages opening and
         // closing of folders when the mouse hovers.
         { node: null, openTimer: null, hoverTime: 350, closeTimer: null }
       );
      ]]></field>

      <method name="_clearOverFolder">
        <body><![CDATA[
          // 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.
          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;
          }
        ]]></body>
      </method>

      <method name="_getDropPoint">
        <parameter name="aEvent"/>
        <body><![CDATA[
          // This function returns information about where to drop when
          // dragging over the toolbar.
          // The returned object has 3 properties:
          // - ip: the insertion point for the bookmarks service.
          // - beforeIndex: child index to drop before, for the drop indicator.
          // - folderNode: the folder to drop into, if applicable.
          var result = this.getResult();
          if (!PlacesUtils.nodeIsFolder(this._resultNode))
            return null;

          var dropPoint = { ip: null, beforeIndex: null, folderNode: null };
          var xulNode = aEvent.target;
          if (xulNode.node) {
            let nodeRect = xulNode.getBoundingClientRect();
            let nodeIndex = Array.indexOf(this.childNodes, xulNode);
            if (PlacesUtils.nodeIsFolder(xulNode.node) &&
                !PlacesUtils.nodeIsReadOnly(xulNode.node)) {
              // This is a folder.
              // If we are in the middle of it, drop inside it.
              // Otherwise, drop before it, with regards to RTL mode.
              let threshold = nodeRect.width * 0.25;
              if (this._isRTL ? (aEvent.clientX > nodeRect.right - threshold)
                              : (aEvent.clientX < nodeRect.left + threshold)) {
                // Drop before this folder.
                dropPoint.ip =
                  new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
                                     nodeIndex, Ci.nsITreeView.DROP_BEFORE);
                dropPoint.beforeIndex = nodeIndex;
              }
              else if (this._isRTL ? (aEvent.clientX > nodeRect.left + threshold)
                                   : (aEvent.clientX < nodeRect.right - threshold)) {
                // Drop inside this folder.
                dropPoint.ip =
                  new InsertionPoint(PlacesUtils.getConcreteItemId(xulNode.node),
                                     -1, Ci.nsITreeView.DROP_ON,
                                     PlacesUtils.nodeIsTagQuery(xulNode.node));
                dropPoint.beforeIndex = nodeIndex;
                dropPoint.folderNode = xulNode;
              }
              else {
                // Drop after this folder.
                let beforeIndex =
                  (nodeIndex == this.childNodes.length - 1) ? -1 : nodeIndex + 1;
                dropPoint.ip =
                  new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
                                     beforeIndex, Ci.nsITreeView.DROP_BEFORE);
                dropPoint.beforeIndex = beforeIndex;
              }
            }
            else {
              // This is a non-folder node or a read-only folder.
              // Drop before it with regards to RTL mode.
              let threshold = nodeRect.width * 0.5;
              if (this._isRTL ? (aEvent.clientX > nodeRect.left + threshold)
                              : (aEvent.clientX < nodeRect.left + threshold)) {
                // Drop before this bookmark.
                dropPoint.ip =
	                new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
	                                   nodeIndex, Ci.nsITreeView.DROP_BEFORE);
                dropPoint.beforeIndex = nodeIndex;
              }
              else {
                // Drop after this bookmark.
                let beforeIndex =
                  nodeIndex == this.childNodes.length - 1 ? -1 : nodeIndex + 1;
                dropPoint.ip =
                  new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
                                     beforeIndex, Ci.nsITreeView.DROP_BEFORE);
                dropPoint.beforeIndex = beforeIndex;
              }
            }
          }
          else {
            // We are most likely dragging on the empty area of the
            // toolbar, we should drop after the last node.
            dropPoint.ip =
              new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
                                 -1, Ci.nsITreeView.DROP_BEFORE);
            dropPoint.beforeIndex = -1;
          }

          return dropPoint;
        ]]></body>
      </method>

      <method name="_setTimer">
        <parameter name="aTime"/>
        <body><![CDATA[
          var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
          timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
          return timer;
        ]]></body>
      </method>

      <!-- nsITimerCallback -->
      <method name="notify">
        <parameter name="aTimer"/>
        <body><![CDATA[
          // Function to process all timer notifications.

          if (aTimer == this._updateChevronTimer) {
            this._updateChevronTimer = null;
            this._updateChevronTimerCallback();
          }

          // * Timer to turn off indicator bar.
          else if (aTimer == this._ibTimer) {
            this._dropIndicator.collapsed = true;
            this._ibTimer = null;
          }

          // * Timer to open a menubutton that's being dragged over.
          else if (aTimer == 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.
          else if (aTimer == this._overFolder.closeTimer) {
            // Close the menubutton if we are not dragging 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) {
                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();
          }
        ]]></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="dragstart"><![CDATA[
        // sub menus have their own d&d handlers
        var draggedDOMNode = event.target;
        if (draggedDOMNode.parentNode != this || !draggedDOMNode.node)
          return;

        if (draggedDOMNode.localName == "toolbarbutton" &&
            draggedDOMNode.getAttribute("type") == "menu") {
          // If the drag gesture on a container is toward down we open instead
          // of dragging.
          if (this._mouseDownTimer) {
            this._mouseDownTimer.cancel();
            this._mouseDownTimer = null;
          }
          var translateY = this._cachedMouseMoveEvent.clientY - event.clientY;
          var translateX = this._cachedMouseMoveEvent.clientX - event.clientX;
          if ((translateY) >= Math.abs(translateX/2)) {
            // Don't start the drag
            event.preventDefault();
            // Open the menu
            draggedDOMNode.open = true;
            return;
          }
          // if the menu is open, close it
          if (draggedDOMNode.open) {
            draggedDOMNode.firstChild.hidePopup();
            draggedDOMNode.open = false;
          }
        }

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

        this._controller.setDataTransfer(event);
        event.stopPropagation();
      ]]></handler>

      <handler event="dragover"><![CDATA[
        // Cache the dataTransfer
        var dt = PlacesControllerDragHelper.currentDataTransfer =
                 event.dataTransfer;

        var ip = this.insertionPoint;
        if (!ip || !PlacesControllerDragHelper.canDrop(ip)) {
          this._dropIndicator.collapsed = true;
          PlacesControllerDragHelper.currentDataTransfer = null;
          return;
        }

        PlacesControllerDragHelper.currentDropTarget = event.target;
        var dropPoint = this._getDropPoint(event);

        if (this._ibTimer) {
          this._ibTimer.cancel();
          this._ibTimer = null;
        }

        if (dropPoint.folderNode ||
            event.originalTarget == this._chevron) {
          // Dropping over a menubutton or chevron button
          // set styles and timer to open relative menupopup
          var overNode = dropPoint.folderNode || this._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");

          this._dropIndicator.collapsed = true;
        }
        else {
          // Dragging over a normal toolbarbutton,
          // show indicator bar and move it to the appropriate drop point.
          let ind = this._dropIndicator;
          let halfInd = ind.clientWidth / 2;
          let translateX;
          if (this._isRTL) {
            halfInd = Math.ceil(halfInd);
            translateX = 0 - this._scrollbox.getBoundingClientRect().right -
                             halfInd;
            if (this.firstChild) {
              if (dropPoint.beforeIndex == -1)
                translateX += this.lastChild.getBoundingClientRect().left;
              else {
                translateX += this.childNodes[dropPoint.beforeIndex]
                                .getBoundingClientRect().right;
              }
            }
          }
          else {
            halfInd = Math.floor(halfInd);
            translateX = 0 - this._scrollbox.getBoundingClientRect().left +
                         halfInd;
            if (this.firstChild) {
              if (dropPoint.beforeIndex == -1)
                translateX += this.lastChild.getBoundingClientRect().right;
              else {
                translateX += this.childNodes[dropPoint.beforeIndex]
                                  .getBoundingClientRect().left;
              }
            }
          }

          ind.style.MozTransform = "translate(" + Math.round(translateX) + "px)";
          ind.style.MozMarginStart = (-ind.clientWidth) + "px";
          ind.collapsed = false;

          // Clear out old folder information
          this._clearOverFolder();
        }

        dt.effectAllowed = "all";
        event.preventDefault();
        event.stopPropagation();
      ]]></handler>

      <handler event="drop"><![CDATA[
        PlacesControllerDragHelper.currentDataTransfer = event.dataTransfer;
        PlacesControllerDragHelper.currentDropTarget = event.target;

        var ip = this.insertionPoint;
        if (!ip || !PlacesControllerDragHelper.canDrop(ip))
          return;

        var dropPoint = this._getDropPoint(event);
        if (!dropPoint)
          return;

        PlacesControllerDragHelper.onDrop(dropPoint.ip);
        event.stopPropagation();
      ]]></handler>

      <handler event="dragleave"><![CDATA[
        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);

        // If we hovered over a folder, close it now
        if (this._overFolder.node)
            this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
      ]]></handler>

      <handler event="dragend"><![CDATA[
        this._draggedNode = null;
      ]]></handler>

      <handler event="popupshowing" phase="capturing"><![CDATA[
        if (!this._allowPopupShowing) {
          this._allowPopupShowing = true;
          event.preventDefault();
          return;
        }

        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")
          this._openedMenuButton = parent;
      ]]></handler>

      <handler event="popuphidden"><![CDATA[
        var popup = event.originalTarget;

        // Avoid handling popuphidden 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") {
          this._openedMenuButton = null;
          // Clear the dragover attribute if present, if we are dragging into a
          // folder in the hierachy of current opened popup we don't clear
          // this attribute on clearOverFolder.  See Notify for closeTimer.
          if (parent.hasAttribute("dragover"))
            parent.removeAttribute("dragover");
        }
      ]]></handler>

#ifdef XP_UNIX
#ifndef XP_MACOSX
      <handler event="mousedown"><![CDATA[
        var target = event.target;
        if (event.button == 0 &&
            target.localName == "toolbarbutton" &&
            target.getAttribute("type") == "menu") {
          this._allowPopupShowing = false;
          // On Linux we can open the popup only after a delay.
          // Indeed as soon as the menupopup opens we are unable to start a
          // drag event.  See bug 500081 for details.
          this._mouseDownTimer = Cc["@mozilla.org/timer;1"]
                                   .createInstance(Ci.nsITimer);
          var callback = {
            _self: this,
		        _target: target,
		        notify: function(timer) {
              this._target.open = true;
              this._self._mouseDownTimer = null;
            }
          };

          this._mouseDownTimer.initWithCallback(callback, 300,
                                                Ci.nsITimer.TYPE_ONE_SHOT);
        }
      ]]></handler>
#endif
#endif
      <handler event="mouseup"><![CDATA[
        if (event.button != 0)
          return;

        if (this._mouseDownTimer) {
          // On a click (down/up) we should open the menu popup
          this._mouseDownTimer.cancel();
          this._mouseDownTimer = null;
          event.target.open = true;
        }
      ]]></handler>

      <handler event="mousemove"><![CDATA[
        // Used in dragStart to prevent dragging folders when dragging down
        this._cachedMouseMoveEvent = event;

        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>
