<?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 Menupopup 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>
#   Asaf Romano <mano@mozilla.com>
#   Simon Bünzli <zeniko@gmail.com>
#   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 *****

<bindings id="placesMenuBindings"
          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-popup-base"
           extends="chrome://global/content/bindings/popup.xml#popup">
    <content>
      <xul:hbox flex="1">
        <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
          <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
        </xul:vbox>
        <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical">
          <children/>
        </xul:arrowscrollbox>
      </xul:hbox>
    </content>

    <implementation>

      <field name="_indicatorBar">
        document.getAnonymousElementByAttribute(this, "class",
                                                "menupopup-drop-indicator-bar");
      </field>

      <field name="_scrollBox">
        document.getAnonymousElementByAttribute(this, "class",
                                                "popup-internal-box");
      </field>

      <!-- markers for start and end of valid places items -->
      <field name="_startMarker">-1</field>
      <field name="_endMarker">-1</field>

      <!-- This is the view that manage the popup -->
      <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>

      <method name="onDragOver">
        <parameter name="aEvent"/>
        <parameter name="aFlavour"/>
        <parameter name="aDragSession"/>
        <body><![CDATA[
          PlacesControllerDragHelper.currentDropTarget = aEvent.target;
          // check if we have a valid dropPoint
          var dropPoint = this._getDropPoint(aEvent);
          if (!dropPoint || !dropPoint.ip ||
              !PlacesControllerDragHelper.canDrop(dropPoint.ip)) {
            aEvent.dataTransfer.effectAllowed = "none";
            return;
          }

          // add a dragover attribute to this popup
          this.setAttribute("dragover", "true");

          if (dropPoint.folderNode) {
            // We are dragging over a folder
            // _overFolder should take the care of opening it on a timer
            if (this._overFolder.node &&
                this._overFolder.node != dropPoint.folderNode) {
              // we are dragging over a new folder, let's clear old values
              this._overFolder.clear();
            }
            if (!this._overFolder.node) {
              this._overFolder.node = dropPoint.folderNode;
              // create the timer to open this folder
              this._overFolder.openTimer = this._overFolder
                                               .setTimer(this._overFolder.hoverTime);
            }
            // since we are dropping into a folder set the corresponding style
            dropPoint.folderNode.setAttribute("_moz-menuactive", true);
          }
          else {
            // We are not dragging over a folder
            // Clear out old _overFolder information
            this._overFolder.clear();
          }

          // Autoscroll the popup strip if we drag over the scroll buttons
          var anonid = aEvent.originalTarget.getAttribute('anonid');
          var scrollDir = anonid == "scrollbutton-up" ? -1 :
                          anonid == "scrollbutton-down" ? 1 : 0;
          if (scrollDir != 0) {
            this._scrollBox.scrollByIndex(scrollDir, false);
          }

          // Check if we should hide the drop indicator for this target
          if (!aDragSession.canDrop ||
              !dropPoint || dropPoint.folderNode ||
              this._hideDropIndicator(aEvent, dropPoint)) {
            this._indicatorBar.hidden = true;
            return;
          }

          // We should display the drop indicator relative to the arrowscrollbox
          var sbo = this._scrollBox.scrollBoxObject;
          var newMarginTop = 0;
          if (scrollDir == 0) {
            var node = this.firstChild;
            while (node && aEvent.screenY > node.boxObject.screenY +
                                            node.boxObject.height / 2)
              node = node.nextSibling;
            newMarginTop = node ? node.boxObject.screenY - sbo.screenY :
                                  sbo.height;
          }
          else if (scrollDir == 1)
            newMarginTop = sbo.height;

          // set the new marginTop based on arrowscrollbox
          newMarginTop += sbo.y - this._scrollBox.boxObject.y;
          this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px";
          this._indicatorBar.hidden = false;
          aEvent.stopPropagation();
        ]]></body>
      </method>

      <method name="onDragExit">
        <parameter name="aEvent"/>
        <parameter name="aDragSession"/>
        <body><![CDATA[
          PlacesControllerDragHelper.currentDropTarget = null;
          PlacesControllerDragHelper.currentDataTransfer = null;
          this.removeAttribute("dragover");

          // if we have not moved to a valid new target clear the drop indicator
          // this happens when moving out of the popup
          var target = aEvent.relatedTarget;
          if (!target)
            this._indicatorBar.hidden = true;

          // Close any folder being hovered over
          if (this._overFolder.node) {
            this._overFolder.closeTimer = this._overFolder
                                              .setTimer(this._overFolder.hoverTime);
          }

          // The autoopened attribute is set when this folder was automatically
          // opened after the user dragged over it.  If this attribute is set,
          // auto-close the folder on drag exit.
          // We should also try to close this popup if the drag has started
          // from here, the timer will check if we are dragging over a child.
          if (this.hasAttribute("autoopened") ||
              this.hasAttribute("dragstart")) {
            this._overFolder.closeMenuTimer = this._overFolder
                                                  .setTimer(this._overFolder.hoverTime);
          }

          this._rootView._draggedNode = null;
        ]]></body>
      </method>

      <method name="onDragStart">
        <parameter name="aEvent"/>
        <parameter name="aXferData"/>
        <parameter name="aDragAction"/>
        <body><![CDATA[
          var draggedNode = aEvent.target.node;

          // Force a copy action if parent node is a query or we are dragging a
          // not-removable node
          if (!PlacesControllerDragHelper.canMoveNode(draggedNode))
            aEvent.dataTransfer.effectAllowed = "copyLink";

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

          // Fill the dataTransfer
          this._rootView._controller.setDataTransfer(aEvent);

          this.setAttribute("dragstart", "true");
          aEvent.stopPropagation();
        ]]></body>
      </method>

      <method name="onDrop">
        <parameter name="aEvent"/>
        <parameter name="aDropData"/>
        <parameter name="aSession"/>
        <body><![CDATA[
          // Cache the dataTransfer
          PlacesControllerDragHelper.currentDataTransfer = aEvent.dataTransfer;

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

          PlacesControllerDragHelper.onDrop(dropPoint.ip);
          aEvent.stopPropagation();
        ]]></body>
      </method>

      <!-- This returns the FavourSet accepted by this popup -->
      <method name="getSupportedFlavours">
        <body><![CDATA[
          return PlacesControllerDragHelper.flavourSet;
        ]]></body>
      </method>

      <!-- Check if we should hide the drop indicator for the target -->
      <method name="_hideDropIndicator">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var target = aEvent.target;

          // in some view we have _startMarker and _endMarker, we should not
          // draw the drop indicator outside of them
          var betweenMarkers = true;
          if (this._startMarker != -1 &&
              target.boxObject.y <= this.childNodes[this._startMarker].boxObject.y)
            betweenMarkers = false;
          if (this._endMarker != -1 &&
              target.boxObject.y >= this.childNodes[this._endMarker].boxObject.y)
            betweenMarkers = false;

          // hide the dropmarker if current node is not a places bookmark item
          return !(target && target.node && betweenMarkers &&
                   this.canDrop(aEvent));
        ]]></body>
      </method>

      <!-- This function returns information about where to drop when
           dragging over this popup insertion point -->
      <method name="_getDropPoint">
        <parameter name="aEvent"/>
          <body><![CDATA[
            // Can't drop if the menu isn't a folder
            var resultNode = this._resultNode;

            if (!PlacesUtils.nodeIsFolder(resultNode) ||
                PlacesControllerDragHelper.disallowInsertion(resultNode)) {
              aEvent.dataTransfer.effectAllowed = "none";
              return null;
            }

            var dropPoint = { ip: null, folderNode: null };

            // The node we are dragging over
            var xulNode = aEvent.target;

            // Calculate positions taking care of arrowscrollbox
            var eventY = aEvent.layerY;
            var scrollbox = this._scrollBox;
            var scrollboxOffset = scrollbox.scrollBoxObject.y -
                                  (scrollbox.boxObject.y - this.boxObject.y);
            var nodeY = xulNode.boxObject.y - scrollboxOffset;
            var nodeHeight = xulNode.boxObject.height;

            if (!xulNode.node) {
              // if we are dragging over a non places node drop at the end
              dropPoint.ip = new InsertionPoint(
                                  PlacesUtils.getConcreteItemId(resultNode),
                                  -1,
                                  Ci.nsITreeView.DROP_ON);
              return dropPoint;
            }
            else if ((PlacesUtils.nodeIsFolder(xulNode.node) ||
                      PlacesUtils.nodeIsTagQuery(xulNode.node)) &&
                     !PlacesUtils.nodeIsReadOnly(xulNode.node)) {
              // This is a folder or a tag container.
              if (eventY - nodeY < nodeHeight * 0.20) {
                // If mouse is in the top part of the node, drop above folder.
                dropPoint.ip = new InsertionPoint(
                                    PlacesUtils.getConcreteItemId(resultNode),
                                    -1,
                                    Ci.nsITreeView.DROP_BEFORE,
                                    PlacesUtils.nodeIsTagQuery(xulNode.node),
                                    xulNode.node.itemId);
                return dropPoint;
              }
              else if (eventY - nodeY < nodeHeight * 0.80) {
                // If mouse is in the middle of the node, drop inside folder.
                dropPoint.ip = new InsertionPoint(
                                    PlacesUtils.getConcreteItemId(xulNode.node),
                                    -1,
                                    Ci.nsITreeView.DROP_ON,
                                    PlacesUtils.nodeIsTagQuery(xulNode.node));
                dropPoint.folderNode = xulNode;
                return dropPoint;
              }
            }
            else if (eventY - nodeY <= nodeHeight / 2) {
              // This is a non-folder node or a readonly folder.
              // If the mouse is above the middle, drop above this item.
              dropPoint.ip = new InsertionPoint(
                                  PlacesUtils.getConcreteItemId(resultNode),
                                  -1,
                                  Ci.nsITreeView.DROP_BEFORE,
                                  PlacesUtils.nodeIsTagQuery(xulNode.node),
                                  xulNode.node.itemId);
              return dropPoint;
            }

            // Drop below the item.
            dropPoint.ip = new InsertionPoint(
                                PlacesUtils.getConcreteItemId(resultNode),
                                -1,
                                Ci.nsITreeView.DROP_AFTER,
                                PlacesUtils.nodeIsTagQuery(xulNode.node),
                                xulNode.node.itemId);
            return dropPoint;
        ]]></body>
      </method>

      <method name="canDrop">
        <parameter name="aEvent"/>
        <body><![CDATA[
          // Cache the dataTransfer
          PlacesControllerDragHelper.currentDataTransfer = aEvent.dataTransfer;

          var ip = this._rootView.insertionPoint;
          return ip && PlacesControllerDragHelper.canDrop(ip);
        ]]></body>
      </method>

      <!-- Sub-menus 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. -->
      <field name="_overFolder"><![CDATA[({
        _self: this,
        _folder: {node: null,
                  openTimer: null,
                  hoverTime: 350,
                  closeTimer: null},
        _closeMenuTimer: null,

        get node() {
          return this._folder.node;
        },
        set node(val) {
          return this._folder.node = val;
        },

        get openTimer() {
          return this._folder.openTimer;
        },
        set openTimer(val) {
          return this._folder.openTimer = val;
        },

        get hoverTime() {
          return this._folder.hoverTime;
        },
        set hoverTime(val) {
          return this._folder.hoverTime = val;
        },

        get closeTimer() {
          return this._folder.closeTimer;
        },
        set closeTimer(val) {
          return this._folder.closeTimer = val;
        },

        get closeMenuTimer() {
          return this._closeMenuTimer;
        },
        set closeMenuTimer(val) {
          return this._closeMenuTimer = val;
        },

        setTimer: function OF__setTimer(aTime) {
          var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
          timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
          return timer;
        },

        notify: function OF__notify(aTimer) {
          // Function to process all timer notifications.

          if (aTimer == this._folder.openTimer) {
            // Timer to open a submenu that's being dragged over.
            this._folder.node.lastChild.setAttribute("autoopened", "true");
            this._folder.node.lastChild.showPopup(this._folder.node);
            this._folder.openTimer = null;
          }

          else if (aTimer == this._folder.closeTimer) {
            // Timer to close a submenu that's been dragged off of.
            // Only close the submenu if the mouse isn't being dragged over any
            // of its child menus.
            var draggingOverChild = PlacesControllerDragHelper
                                    .draggingOverChildNode(this._folder.node);
            if (draggingOverChild)
              this._folder.node = null;
            this.clear();

            // Close any parent folders which aren't being dragged over.
            // (This is necessary because of the above code that keeps a folder
            // open while its children are being dragged over.)
            if (!draggingOverChild)
              this.closeParentMenus();
          }

          else if (aTimer == this.closeMenuTimer) {
            // Timer to close this menu after the drag exit.
            var popup = this._self;
            // if we are no more dragging we can leave the menu open to allow
            // for better D&D bookmark organization
            if (PlacesControllerDragHelper.getSession() &&
                !PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
              popup.hidePopup();
              // Close any parent menus that aren't being dragged over;
              // otherwise they'll stay open because they couldn't close
              // while this menu was being dragged over.
              this.closeParentMenus();
            }
            this._closeMenuTimer = null;
          }
        },

        //  Helper function to close all parent menus of this menu,
        //  as long as none of the parent's children are currently being
        //  dragged over.
        closeParentMenus: function OF__closeParentMenus() {
          var popup = this._self;
          var parent = popup.parentNode;
          while (parent) {
            if (parent.nodeName == "menupopup" && parent._resultNode) {
              if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
                break;
              parent.hidePopup();
            }
            parent = parent.parentNode;
          }
        },

        //  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.
        clear: function OF__clear() {
          if (this._folder.node && this._folder.node.lastChild) {
            if (!this._folder.node.lastChild.hasAttribute("dragover"))
              this._folder.node.lastChild.hidePopup();
            // remove menuactive style
            this._folder.node.removeAttribute("_moz-menuactive");
            this._folder.node = null;
          }
          if (this._folder.openTimer) {
            this._folder.openTimer.cancel();
            this._folder.openTimer = null;
          }
          if (this._folder.closeTimer) {
            this._folder.closeTimer.cancel();
            this._folder.closeTimer = null;
          }
        }
      })]]></field>

    </implementation>

    <handlers>
      <handler event="DOMMenuItemActive"><![CDATA[
        var node = event.target;
        if (node.parentNode != this)
          return;

#ifdef XP_MACOSX
        // XXXschonfeld: The following check is a temporary hack
        // until bug 420033 is resolved.
        while (node) {
          if (node.id == "bookmarksMenuPopup" || node.id == "goPopup")
            return;

          node = node.parentNode;
        }
#endif

        if (window.XULBrowserWindow) {
          var nodeItem = event.target.node;
          if (nodeItem && PlacesUtils.nodeIsURI(nodeItem))
            window.XULBrowserWindow.setOverLink(nodeItem.uri, null);
        }
      ]]></handler>
      <handler event="DOMMenuItemInactive"><![CDATA[
        var node = event.target;
        if (node.parentNode != this)
          return;

        if (window.XULBrowserWindow)
          window.XULBrowserWindow.setOverLink("", null);
      ]]></handler>
      <handler event="draggesture" action="if (event.target.node) nsDragAndDrop.startDrag(event, this);"/>
      <handler event="drop" action="nsDragAndDrop.drop(event, this);"/>
      <handler event="dragover" action="nsDragAndDrop.dragOver(event, this);"/>
      <handler event="dragexit" action="nsDragAndDrop.dragExit(event, this);"/>
    </handlers>
  </binding>


  <binding id="places-menupopup"
           extends="chrome://browser/content/places/menu.xml#places-popup-base">
    <implementation>
      <destructor><![CDATA[
        if (this._result) {
          this._resultNode.containerOpen = false;
          this._resultNode = null;
          this._result.viewer = null;
          this._result = null;
        }
      ]]></destructor>

      <field name="_initialized">false</field>
      <method name="_ensureInitialized">
        <body><![CDATA[
          if (this._initialized)
            return;

          this._controller = new PlacesController(this);
          this.controllers.appendController(this._controller);

          // This function should only be called for top-level menus like the bookmarks menu.
          // Submenus get their _result and _resultNode from their parents.
          if (this.hasAttribute("place")) {
            // Do the initial build.
            this.place = this.place;
          }
          this._initialized = true;
        ]]></body>
      </method>

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

      <method name="onPopupShowing">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var popup = aEvent.target;
          var resultNode = popup._resultNode;
          if (!resultNode.containerOpen)
            resultNode.containerOpen = true;
          if (!popup.parentNode._built)
            this._rebuild(popup);
        ]]></body>
      </method>

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

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

      <!-- nsIPlacesView -->
      <method name="getResultNode">
        <body><![CDATA[
          this._ensureInitialized();
          return this._resultNode;
        ]]></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);

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

      <method name="insertNewItem">
        <parameter name="aChild"/>
        <parameter name="aParentPopup"/>
        <parameter name="aBefore"/>
        <body><![CDATA[
          let 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) {
              aParentPopup.insertBefore(element,
                                        aParentPopup.childNodes[aParentPopup._endMarker]);
            }
            else
              aParentPopup.appendChild(element);
          }

          if (aParentPopup._endMarker != -1)
            aParentPopup._endMarker++;

#ifdef XP_MACOSX
          // Bug 529062:
          // We rely on the menu* bindings, but those aren't never applied
          // in native mac menus, for elements which are added to the document
          // through appendChild / insertBefore.
          if (this.parentNode.parentNode.localName != "menubar")
            return;

          function applyBinding(elm) {
            // getPropertyCSSValue is semi-deprecared, but there's no
            // alternative for parsing css_uri values reliably.
            let elmBindingURI = window.getComputedStyle(element, "")
                                      .getPropertyCSSValue("-moz-binding");
            if (elmBindingURI.primitiveType == CSSPrimitiveValue.CSS_URI)
              document.addBinding(elm, elmBindingURI.getStringValue());
          }

          applyBinding(element);
          if (element.localName == "menu")
            applyBinding(element.firstChild);
#endif
        ]]></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="_rebuild">
        <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 cc = aPopup._resultNode.childCount;
          if (cc > 0) {
            if (aPopup._emptyMenuItem)
              aPopup._emptyMenuItem.hidden = true;

            for (var i = 0; i < cc; ++i) {
              var child = aPopup._resultNode.getChild(i);
              this.insertNewItem(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>

      <!-- 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.parentNode._built = false;
            this._self._result = val;
            if (val) {
              this._self._resultNode = val.root;
              this._self._resultNode._DOMElement = this._self.parentNode;
            }
            else
              this._self._resultNode = null;
          }
          return val;
        },

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

          if (!parentElt._built)
            return;

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

          let index = popup._startMarker + 1 + aIndex;
          this._self.insertNewItem(aNode, popup, popup.childNodes[index]);
          if (popup._emptyMenuItem)
            popup._emptyMenuItem.hidden = true;
        },

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

          if (!parentElt._built)
            return;

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

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

          // Figure out if we need to show the "<Empty>" menu-item.
          // TODO Bug 517701: This doesn't seem to handle the case of an empty
          // root (parentElt == this._self.parentNode).
          if (!popup.hasChildNodes() ||
              (popup.childNodes.length == 1 &&
              popup.firstChild == popup._emptyMenuItem))
            this._self._showEmptyMenuItem(popup);

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

        nodeMoved:
        function PMV_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.parentNode)
            return;

          // Move the node.
          let popup = nodeElt.parentNode;
          let index = popup._startMarker + 1 + aNewIndex;
          popup.removeChild(nodeElt);
          popup.insertBefore(nodeElt, popup.childNodes[index]);
        },

        nodeTitleChanged: function PMV__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.parentNode)
            return;

          nodeElt.label = aNewTitle || PlacesUIUtils.getBestTitle(aNode);
        },

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

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

        nodeIconChanged: function PMV_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.parentNode)
            return;

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

        nodeAnnotationChanged:
        function PMV_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 PMV_nodeReplaced(aParentNode, aOldNode, aNewNode, aIndex) {
          let parentElt = aParentNode._DOMElement;
          NS_ASSERT(parentElt, "parent node must have _DOMElement set");

          if (!parentElt._built)
            return;

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

          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 will insert the new element as the last item.
          let next = nodeElt.nextSibling;
          this._self.removeItem(nodeElt);
          this._self.insertNewItem(aNewNode, popup, next);
        },

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

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

        invalidateContainer: function PMV_invalidateContainer(aContainer) {
          // Do nothing if the entire view is already marked un-built.
          if (!this._self.parentNode._built)
            return;

          let containerNodeElt = aContainer._DOMElement;
          NS_ASSERT(containerNodeElt, "node must have _DOMElement set");
          containerNodeElt._built = false;

          // If the menupopup is open we should live-update it.
          if (containerNodeElt.open)
            this._self._rebuild(containerNodeElt.firstChild);
        },

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

      <!-- nsIPlacesView -->
      <property name="place">
        <getter><![CDATA[
          return this.getAttribute("place");
        ]]></getter>
        <setter><![CDATA[
          this.setAttribute("place", val);
          var queries = { }, options = { };
          PlacesUtils.history.queryStringToQueries(val, queries, { }, options);
          if (!queries.value.length)
            queries.value = [PlacesUtils.history.getNewQuery()];
          var result =
            PlacesUtils.history.executeQueries(queries.value,
                                               queries.value.length,
                                               options.value);

          result.viewer = this._viewer;
          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;
            return popupNode.node || popupNode.parentNode._resultNode || null;
          }
          return null;
        ]]></getter>
      </property>

      <!-- nsIPlacesView -->
      <property name="insertionPoint">
        <getter><![CDATA[
          // there is no insertion point for history queries
          // so bail out now and save a lot of work when updating commands
          var resultNode = this._resultNode;
          if (PlacesUtils.nodeIsQuery(resultNode) &&
              asQuery(resultNode).queryOptions.queryType ==
                Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
              return null;

          // By default, the insertion point is at the top level, at the end.
          var index = PlacesUtils.bookmarks.DEFAULT_INDEX;
          var container = null;
          var orientation = Ci.nsITreeView.DROP_BEFORE;
          var isTag = false;

          if (PlacesUtils.nodeIsFolder(resultNode)) {
            container = resultNode;
            isTag = PlacesUtils.nodeIsTagQuery(resultNode);
          }

          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/>
      </method>

      <method name="selectItems">
        <body/>
      </method>

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

      <method name="buildContextMenu">
        <parameter name="aPopup"/>
        <body><![CDATA[
          this._ensureInitialized();
          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>
    </implementation>
    <handlers>
      <handler event="popupshowing" phase="capturing"><![CDATA[
        this._ensureInitialized();
        var popup = event.target;
        // Avoid handling popupshowing of inner views
        if (!popup._resultNode || PlacesUIUtils.getViewForNode(popup) != this)
          return;

        this.onPopupShowing(event);
      ]]></handler>

      <handler event="popuphidden"><![CDATA[
        var popup = event.target;
        // We should avoid to handle events of inner views
        if (!popup._resultNode || PlacesUIUtils.getViewForNode(popup) != this)
          return;

        // 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;

        // The autoopened attribute is set for folders which have been
        // automatically opened when dragged over.  Turn off this attribute
        // when the folder closes because it is no longer applicable.
        popup.removeAttribute("autoopened");
        popup.removeAttribute("dragstart");
      ]]></handler>
    </handlers>
  </binding>

</bindings>
