/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Pierre Chanial <chanial@noos.fr>.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of 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 ***** */

var BookmarksMenu = {
  _selection:null,
  _target:null,
  _orientation:null,

  //////////////////////////////////////////////////////////////////////////
  // Fill a context menu popup with menuitems appropriate for the current
  // selection.
  createContextMenu: function (aEvent)
  {
    var target = document.popupNode;
    if (!this.isBTBookmark(target.id) && target.id != "bookmarks-ptf")
      return false;
    target.focus() // buttons in the pt have -moz-user-focus: ignore -->
    this._selection   = this.getBTSelection(target);
    this._orientation = this.getBTOrientation(aEvent, target);
    this._target      = this.getBTTarget(target, this._orientation);
    BookmarksCommand.createContextMenu(aEvent, this._selection);
    this.onCommandUpdate();
    aEvent.target.addEventListener("mousemove", BookmarksMenuController.onMouseMove, false);
    return true;
  },

  /////////////////////////////////////////////////////////////////////////
  // Clean up after closing the context menu popup
  destroyContextMenu: function (aEvent)
  {
    if (content)
      content.focus();
    BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode); // needed on cancel
    aEvent.target.removeEventListener("mousemove", BookmarksMenuController.onMouseMove, false);
  },

  /////////////////////////////////////////////////////////////////////////////
  // returns the formatted selection from aNode
  getBTSelection: function (aNode)
  {
    var item;
    switch (aNode.id) {
    case "bookmarks-ptf":
      item = "NC:PersonalToolbarFolder";
      break;
    case "BookmarksMenu":
      item = "NC:BookmarksRoot";
      break;
    default:
      item = aNode.id;
    }
    if (!this.isBTBookmark(item))
      return {length:0};
    var parent           = this.getBTContainer(aNode);
    var isExpanded       = aNode.hasAttribute("open") && aNode.open;
    var selection        = {};
    selection.item       = [RDF.GetResource(item)];
    selection.parent     = [RDF.GetResource(parent)];
    selection.isExpanded = [isExpanded];
    selection.length     = selection.item.length;
    BookmarksUtils.checkSelection(selection);
    return selection;
  },

  /////////////////////////////////////////////////////////////////////////
  // returns the insertion target from aNode
  getBTTarget: function (aNode, aOrientation)
  {
    var item, parent, index;

    switch (aNode.id) {
    case "BookmarksMenu":
    case "bookmarks-button":
      parent = "NC:BookmarksRoot";
      break;
    case "bookmarks-ptf":
      item = BookmarksToolbar.getLastVisibleBookmark();
      // Continue to next case.
    case "bookmarks-chevron":
      parent = "NC:PersonalToolbarFolder";
      break;
    default:
      if (aOrientation == BookmarksUtils.DROP_ON)
        parent = aNode.id
      else {
        parent = this.getBTContainer(aNode);
        item = aNode;
      }
    }

    parent = RDF.GetResource(parent);
    if (aOrientation == BookmarksUtils.DROP_ON)
      return BookmarksUtils.getTargetFromFolder(parent);

    item = RDF.GetResource(item.id);
    RDFC.Init(BMDS, parent);
    index = RDFC.IndexOf(item);
    if (aOrientation == BookmarksUtils.DROP_AFTER)
      ++index;

    return { parent: parent, index: index };
  },

  /////////////////////////////////////////////////////////////////////////
  // returns the parent resource of a node in the personal toolbar.
  // this is determined by inspecting the source element and walking up the 
  // DOM tree to find the appropriate containing node.
  getBTContainer: function (aNode)
  {
    var parent;
    var item = aNode.id;
    if (!this.isBTBookmark(item))
      return "NC:BookmarksRoot"
    parent = aNode.parentNode.parentNode;
    parent = parent.id;
    switch (parent) {
    case "BookmarksMenu":
    case "bookmarks-button":
      return "NC:BookmarksRoot";
    case "PersonalToolbar":
    case "bookmarks-chevron":
      return "NC:PersonalToolbarFolder";
    default:
      return parent;
    }
  },

  ///////////////////////////////////////////////////////////////////////////
  // returns true if the node is a bookmark, a folder or a bookmark separator
  isBTBookmark: function (aURI)
  {
    if (!aURI)
      return false;
    var type = BookmarksUtils.resolveType(aURI);
    return (type == "BookmarkSeparator" ||
            type == "Bookmark"          ||
            type == "Folder"            ||
            type == "FolderGroup"       ||
            type == "PersonalToolbarFolder")
  },

  /////////////////////////////////////////////////////////////////////////
  // returns true if the node is a container. -->
  isBTContainer: function (aTarget)
  {
    return  aTarget.localName == "menu" || (aTarget.localName == "toolbarbutton" &&
           (aTarget.getAttribute("container") == "true" || aTarget.getAttribute("group") == "true"));
  },

  /////////////////////////////////////////////////////////////////////////
  // returns BookmarksUtils.DROP_BEFORE, DROP_ON or DROP_AFTER accordingly
  // to the event coordinates. Skin authors could break us, we'll cross that 
  // bridge when they turn us 90degrees.  -->
  getBTOrientation: function (aEvent, aTarget)
  {
    var target
    if (!aTarget)
      target = aEvent.target;
    else
      target = aTarget;
    if (target.localName == "menu"                 &&
        target.parentNode.localName != "menupopup")
      return BookmarksUtils.DROP_ON;
    if (target.id == "bookmarks-ptf") {
      return target.hasChildNodes() ?
          BookmarksUtils.DROP_AFTER : BookmarksUtils.DROP_ON;
    }
    if (target.id == "bookmarks-chevron")
      return BookmarksUtils.DROP_ON;

    var overButtonBoxObject = target.boxObject.QueryInterface(Components.interfaces.nsIBoxObject);
    var overParentBoxObject = target.parentNode.boxObject.QueryInterface(Components.interfaces.nsIBoxObject);

    var size, border;
    var coordValue, clientCoordValue;
    switch (target.localName) {
      case "toolbarseparator":
      case "toolbarbutton":
        size = overButtonBoxObject.width;
        coordValue = overButtonBoxObject.x;
        clientCoordValue = aEvent.clientX;
        break;
      case "menuseparator": 
      case "menu":
      case "menuitem":
        size = overButtonBoxObject.height;
        coordValue = overButtonBoxObject.screenY;
        clientCoordValue = aEvent.screenY;
        break;
      default: return BookmarksUtils.DROP_ON;
    }
    if (this.isBTContainer(target))
      if (target.localName == "toolbarbutton") {
        // the DROP_BEFORE area excludes the label
        var iconNode = document.getAnonymousElementByAttribute(target, "class", "toolbarbutton-icon");
        border = parseInt(document.defaultView.getComputedStyle(target,"").getPropertyValue("padding-left")) +
                 parseInt(document.defaultView.getComputedStyle(iconNode     ,"").getPropertyValue("width"));
        border = Math.min(size/5,Math.max(border,4));
      } else
        border = size/5;
    else
      border = size/2;

    // in the first region?
    if (clientCoordValue-coordValue < border)
      return BookmarksUtils.DROP_BEFORE;
    // in the last region?
    if (clientCoordValue-coordValue >= size-border)
      return BookmarksUtils.DROP_AFTER;
    // must be in the middle somewhere
    return BookmarksUtils.DROP_ON;
  },

  /////////////////////////////////////////////////////////////////////////
  // expand the folder targeted by the context menu.
  expandBTFolder: function ()
  {
    var target = document.popupNode.lastChild;
    if (document.popupNode.open)
      target.hidePopup();
    else
      target.showPopup(document.popupNode);
  },

  onCommandUpdate: function ()
  {
    var selection = this._selection;
    var target    = this._target;
    BookmarksController.onCommandUpdate(selection, target);
    if (document.popupNode.id == "bookmarks-ptf") {
      // disabling 'cut' and 'copy' on the empty area of the personal toolbar
      var commandNode = document.getElementById("cmd_bm_cut");
      commandNode.setAttribute("disabled", "true");
      commandNode = document.getElementById("cmd_bm_copy");
      commandNode.setAttribute("disabled", "true");
    }
  },

  loadBookmark: function (aEvent, aDS)
  {
    if (this.isBTBookmark(aEvent.target.id))
      BookmarksUtils.loadBookmarkBrowser(aEvent, aDS);
  },

  ////////////////////////////////////////////////
  // loads a bookmark with the middle mouse button
  loadBookmarkMiddleClick: function (aEvent, aDS)
  {
    if (aEvent.type != "click" || aEvent.button != 1)
      return;
    // unlike for command events, we have to close the menus manually
    for (var node = aEvent.target; node != aEvent.currentTarget;
         node = node.parentNode) {
      if (node.nodeType == node.ELEMENT_NODE && node.tagName == "menupopup")
        node.hidePopup();
    }
    this.loadBookmark(aEvent, aDS);
  }
}

var BookmarksMenuController = {

  supportsCommand: BookmarksController.supportsCommand,

  isCommandEnabled: function (aCommand)
  {
    // warning: this is not the function called in BookmarksController.onCommandUpdate
    var selection = BookmarksMenu._selection;
    var target    = BookmarksMenu._target;
    if (selection)
      return BookmarksController.isCommandEnabled(aCommand, selection, target);
    return false;
  },

  doCommand: function (aCommand)
  {
    BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode);
    var selection = BookmarksMenu._selection;
    var target    = BookmarksMenu._target;
    switch (aCommand) {
    case "cmd_bm_expandfolder":
      setTimeout(BookmarksMenu.expandBTFolder, 0);
      break;
    default:
      BookmarksController.doCommand(aCommand, selection, target);
    }
  },

  onMouseMove: function (aEvent)
  {
    var command = aEvent.target.getAttribute("command");
    var isDisabled = aEvent.target.getAttribute("disabled")
    if (isDisabled != "true" && (command == "cmd_bm_newfolder" || command == "cmd_bm_paste")) {
      BookmarksMenuDNDObserver.onDragSetFeedBack(document.popupNode, BookmarksMenu._orientation);
    } else {
      BookmarksMenuDNDObserver.onDragRemoveFeedBack(document.popupNode);
    }
  }
}

var BookmarksMenuDNDObserver = {

  ////////////////////
  // Public methods //
  ////////////////////

  onDragStart: function (aEvent, aXferData, aDragAction)
  {
    var target = aEvent.target;

    // Prevent dragging from an invalid region
    if (!this.canDrop(aEvent))
      return;

    // Prevent dragging out of menupopups on non Win32 platforms. 
    // a) on Mac drag from menus is generally regarded as being satanic
    // b) on Linux, this causes an X-server crash, (bug 151336)
    // c) on Windows, there is no hang or crash associated with this, so we'll leave 
    // the functionality there. 
    if (navigator.platform != "Win32" && target.localName != "toolbarbutton")
      return;

    // bail if dragging from the empty area of the bookmarks toolbar
    if (target.id == "bookmarks-ptf")
      return

    // a drag start is fired when leaving an open toolbarbutton(type=menu) 
    // (see bug 143031)
    if (this.isContainer(target) && 
        target.getAttribute("group") != "true") {
      if (this.isPlatformNotSupported) 
        return;
      if (!aEvent.shiftKey && !aEvent.altKey && !aEvent.ctrlKey)
        return;
      // menus open on mouse down
      target.firstChild.hidePopup();
    }
    var selection  = BookmarksMenu.getBTSelection(target);
    aXferData.data = BookmarksUtils.getXferDataFromSelection(selection);
  },

  onDragOver: function(aEvent, aFlavour, aDragSession) 
  {
    var orientation = BookmarksMenu.getBTOrientation(aEvent)
    if (aDragSession.canDrop)
      this.onDragSetFeedBack(aEvent.target, orientation);
    if (orientation != this.mCurrentDropPosition) {
      // emulating onDragExit and onDragEnter events since the drop region
      // has changed on the target.
      this.onDragExit(aEvent, aDragSession);
      this.onDragEnter(aEvent, aDragSession);
    }
    if (this.isPlatformNotSupported)
      return;
    if (this.isTimerSupported || !aDragSession.sourceNode)
      return;
    this.onDragOverCheckTimers();
  },

  onDragEnter: function (aEvent, aDragSession)
  {
    var target = aEvent.target;
    var orientation = BookmarksMenu.getBTOrientation(aEvent);
    if (target.localName == "menupopup" || target.id == "bookmarks-ptf")
      target = target.parentNode;
    if (aDragSession.canDrop) {
      this.onDragSetFeedBack(target, orientation);
      this.onDragEnterSetTimer(target, aDragSession);
    }
    this.mCurrentDragOverTarget = target;
    this.mCurrentDropPosition   = orientation;
  },

  onDragExit: function (aEvent, aDragSession)
  {
    var target = aEvent.target;
    if (target.localName == "menupopup" || target.id == "bookmarks-ptf")
      target = target.parentNode;
    this.onDragRemoveFeedBack(target);
    this.onDragExitSetTimer(target, aDragSession);
    this.mCurrentDragOverTarget = null;
    this.mCurrentDropPosition = null;
  },

  onDrop: function (aEvent, aXferData, aDragSession)
  {
    var target = aEvent.target;
    this.onDragRemoveFeedBack(target);

    var selection = BookmarksUtils.getSelectionFromXferData(aDragSession);

    var orientation = BookmarksMenu.getBTOrientation(aEvent);

    // For RTL PersonalBar bookmarks buttons, orientation should be inverted (only in drop case)
    // because "before" (to the left) on the screen translates to "after" in the collection of items.
    if (target.localName == "toolbarbutton")
      if (window.getComputedStyle(document.getElementById("PersonalToolbar"),'').direction == 'rtl')
        if (orientation == BookmarksUtils.DROP_AFTER)
          orientation = BookmarksUtils.DROP_BEFORE;
        else if (orientation == BookmarksUtils.DROP_BEFORE)
          orientation = BookmarksUtils.DROP_AFTER;

    var selTarget   = BookmarksMenu.getBTTarget(target, orientation);

    const kDSIID      = Components.interfaces.nsIDragService;
    const kCopyAction = kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_LINK;

    // hide the 'open in tab' menuseparator because bookmarks
    // can be inserted after it if they are dropped after the last bookmark
    // a more comprehensive fix would be in the menupopup template builder
    var menuTarget = (target.localName == "toolbarbutton" ||
                      target.localName == "menu")         && 
                     orientation == BookmarksUtils.DROP_ON?
                     target.lastChild:target.parentNode;
    if (menuTarget.hasChildNodes() &&
        menuTarget.lastChild.id == "openintabs-menuitem") {
      menuTarget.removeChild(menuTarget.lastChild.previousSibling);
    }

    if (aDragSession.dragAction & kCopyAction)
      BookmarksUtils.insertSelection("drag", selection, selTarget);
    else
      BookmarksUtils.moveSelection("drag", selection, selTarget);

    var chevron = document.getElementById("bookmarks-chevron");
    if (chevron.getAttribute("open") == "true") {
      BookmarksToolbar.resizeFunc(null);
      BookmarksToolbar.updateOverflowMenu(document.getElementById("bookmarks-chevron-popup"));
    }

    // show again the menuseparator
    if (menuTarget.hasChildNodes() &&
        menuTarget.lastChild.id == "openintabs-menuitem") {
      var element = document.createElementNS(XUL_NS, "menuseparator");
      menuTarget.insertBefore(element, menuTarget.lastChild);
    }
  },

  canDrop: function (aEvent, aDragSession)
  {
    var target = aEvent.target;

    // onDragStart calls this without a drag session
    // There will be no sourceNode for drags from external apps
    if (aDragSession && aDragSession.sourceNode) {
      var orientation = BookmarksMenu.getBTOrientation(aEvent, target);
      if (target == aDragSession.sourceNode ||
          (target == aDragSession.sourceNode.previousSibling &&
           orientation == BookmarksUtils.DROP_AFTER) ||
          (target == aDragSession.sourceNode.nextSibling &&
           orientation == BookmarksUtils.DROP_BEFORE))
        return false;
    }

    return BookmarksMenu.isBTBookmark(target.id)       && 
           target.id != "NC:SystemBookmarksStaticRoot" &&
           target.id.substring(0,5) != "find:"         ||
           target.id == "BookmarksMenu"                ||
           target.id == "bookmarks-button"             ||
           target.id == "bookmarks-chevron"            ||
           target.id == "bookmarks-ptf";
  },

  canHandleMultipleItems: true,

  getSupportedFlavours: function () 
  {
    var flavourSet = new FlavourSet();
    flavourSet.appendFlavour("moz/rdfitem");
    flavourSet.appendFlavour("text/x-moz-url");
    flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
    flavourSet.appendFlavour("text/unicode");
    return flavourSet;
  },


  ////////////////////////////////////
  // Private methods and properties //
  ////////////////////////////////////

  springLoadedMenuDelay: 350, // milliseconds
  isPlatformNotSupported: navigator.platform.indexOf("Mac") != -1, // see bug 136524
  // Needs to be dynamically overridden (to |true|) in the case of an external drag: see bug 232795.
  isTimerSupported: navigator.platform.indexOf("Win") == -1,

  mCurrentDragOverTarget: null,
  mCurrentDropPosition: null,
  loadTimer  : null,
  closeTimer : null,
  loadTarget : null,
  closeTarget: null,

  _observers : null,
  get mObservers ()
  {
    if (!this._observers) {
      this._observers = [
        document.getElementById("bookmarks-ptf"),
        document.getElementById("BookmarksMenu").parentNode,
        document.getElementById("bookmarks-chevron").parentNode,
        document.getElementById("PersonalToolbar")
      ]
    }
    return this._observers;
  },

  getObserverForNode: function (aNode)
  {
    if (!aNode)
      return null;
    var node = aNode;
    var observer;
    do {
      for (var i=0; i < this.mObservers.length; i++) {
        observer = this.mObservers[i];
        if (observer == node)
          return observer;
      }
      node = node.parentNode;
    } while (node != document)
    return null;
  },

  onDragCloseMenu: function (aNode)
  {
    var children = aNode.childNodes;
    for (var i = 0; i < children.length; i++) {
      if (this.isContainer(children[i]) && 
          children[i].getAttribute("open") == "true") {
        this.onDragCloseMenu(children[i].lastChild);
        if (children[i] != this.mCurrentDragOverTarget || this.mCurrentDropPosition != BookmarksUtils.DROP_ON)
          children[i].lastChild.hidePopup();
      }
    }
  },

  onDragCloseTarget: function ()
  {
    var currentObserver = this.getObserverForNode(this.mCurrentDragOverTarget);
    // close all the menus not hovered by the mouse
    for (var i=0; i < this.mObservers.length; i++) {
      if (currentObserver != this.mObservers[i])
        this.onDragCloseMenu(this.mObservers[i]);
      else
        this.onDragCloseMenu(this.mCurrentDragOverTarget.parentNode);
    }
  },

  onDragLoadTarget: function (aTarget) 
  {
    if (!this.mCurrentDragOverTarget)
      return;
    // Load the current menu
    if (this.mCurrentDropPosition == BookmarksUtils.DROP_ON && 
        this.isContainer(aTarget)             && 
        aTarget.getAttribute("group") != "true")
      aTarget.lastChild.showPopup(aTarget);
  },

  onDragOverCheckTimers: function ()
  {
    var now = new Date().getTime();
    if (this.closeTimer && now-this.springLoadedMenuDelay>this.closeTimer) {
      this.onDragCloseTarget();
      this.closeTimer = null;
    }
    if (this.loadTimer && (now-this.springLoadedMenuDelay>this.loadTimer)) {
      this.onDragLoadTarget(this.loadTarget);
      this.loadTimer = null;
    }
  },

  onDragEnterSetTimer: function (aTarget, aDragSession)
  {
    if (this.isPlatformNotSupported)
      return;
    if (this.isTimerSupported || !aDragSession.sourceNode) {
      var targetToBeLoaded = aTarget;
      clearTimeout(this.loadTimer);
      if (aTarget == aDragSession.sourceNode)
        return;
      var This = this;
      this.loadTimer=setTimeout(function () {This.onDragLoadTarget(targetToBeLoaded)}, This.springLoadedMenuDelay);
    } else {
      var now = new Date().getTime();
      this.loadTimer  = now;
      this.loadTarget = aTarget;
    }
  },

  onDragExitSetTimer: function (aTarget, aDragSession)
  {
    if (this.isPlatformNotSupported)
      return;
    var This = this;
    if (this.isTimerSupported || !aDragSession.sourceNode) {
      clearTimeout(this.closeTimer)
      this.closeTimer=setTimeout(function () {This.onDragCloseTarget()}, This.springLoadedMenuDelay);
    } else {
      var now = new Date().getTime();
      this.closeTimer  = now;
      this.closeTarget = aTarget;
      this.loadTimer = null;

      // If the user isn't rearranging within the menu, close it
      // To do so, we exploit a Mac bug: timeout set during
      // drag and drop on Windows and Mac are fired only after that the drop is released.
      // timeouts will pile up, we may have a better approach but for the moment, this one
      // correctly close the menus after a drop/cancel outside the personal toolbar.
      // The if statement in the function has been introduced to deal with rare but reproducible
      // missing Exit events.
      if (aDragSession.sourceNode.localName != "menuitem" && aDragSession.sourceNode.localName != "menu")
        setTimeout(function () { if (This.mCurrentDragOverTarget) {This.onDragRemoveFeedBack(This.mCurrentDragOverTarget); This.mCurrentDragOverTarget=null} This.loadTimer=null; This.onDragCloseTarget() }, 0);
    }
  },

  onDragSetFeedBack: function (aTarget, aOrientation)
  {
   switch (aTarget.localName) {
      case "toolbarseparator":
      case "toolbarbutton":
        switch (aOrientation) {
          case BookmarksUtils.DROP_BEFORE: 
            aTarget.setAttribute("dragover-left", "true");
            break;
          case BookmarksUtils.DROP_AFTER:
            aTarget.setAttribute("dragover-right", "true");
            break;
          case BookmarksUtils.DROP_ON:
            aTarget.setAttribute("dragover-top"   , "true");
            aTarget.setAttribute("dragover-bottom", "true");
            aTarget.setAttribute("dragover-left"  , "true");
            aTarget.setAttribute("dragover-right" , "true");
            break;
        }
        break;
      case "menuseparator": 
      case "menu":
      case "menuitem":
        switch (aOrientation) {
          case BookmarksUtils.DROP_BEFORE: 
            aTarget.setAttribute("dragover-top", "true");
            break;
          case BookmarksUtils.DROP_AFTER:
            aTarget.setAttribute("dragover-bottom", "true");
            break;
          case BookmarksUtils.DROP_ON:
            break;
        }
        break;
      case "toolbar": 
        var newTarget = BookmarksToolbar.getLastVisibleBookmark();
        if (newTarget)
          newTarget.setAttribute("dragover-right", "true");
        break;
      case "hbox":
      case "menupopup": break; 
     default: dump("No feedback for: "+aTarget.localName+"\n");
    }
  },

  onDragRemoveFeedBack: function (aTarget)
  {
    var newTarget;
    var bt;
    if (aTarget.id == "PersonalToolbar" || aTarget.id == "bookmarks-ptf") { 
      newTarget = BookmarksToolbar.getLastVisibleBookmark();
      if (newTarget)
        newTarget.removeAttribute("dragover-right");
    } else {
      aTarget.removeAttribute("dragover-left");
      aTarget.removeAttribute("dragover-right");
      aTarget.removeAttribute("dragover-top");
      aTarget.removeAttribute("dragover-bottom");
    }
  },

  onDropSetFeedBack: function (aTarget)
  {
    //XXX Not yet...
  },

  isContainer: function (aTarget)
  {
    return aTarget.localName == "menu"          || 
           aTarget.localName == "toolbarbutton" &&
           aTarget.getAttribute("type") == "menu";
  }
}

var BookmarksToolbar = 
{
  /////////////////////////////////////////////////////////////////////////////
  // make bookmarks toolbar act like menus
  openMenuButton: null,
  autoOpenMenu: function (aTarget)
  {
    if (this.openMenuButton &&
        this.openMenuButton != aTarget &&
        aTarget.localName == "toolbarbutton" &&
        (aTarget.type == "menu" ||
         aTarget.type == "menu-button")) {
      this.openMenuButton.open = false;
      aTarget.open = true;
    }
  },
  onMenuOpen: function (aTarget)
  {
    if (aTarget.parentNode.localName == "toolbarbutton")
      this.openMenuButton = aTarget.parentNode;
  },
  onMenuClose: function (aTarget)
  {
    if (aTarget.parentNode.localName == "toolbarbutton")
      this.openMenuButton = null;
  },

  /////////////////////////////////////////////////////////////////////////////
  // returns the node of the last visible bookmark on the toolbar -->
  getLastVisibleBookmark: function ()
  {
    var buttons = document.getElementById("bookmarks-ptf");
    var button = buttons.firstChild;
    if (!button)
      return null; // empty bookmarks toolbar
    do {
      if (button.collapsed)
        return button.previousSibling;
      button = button.nextSibling;
    } while (button)
    return buttons.lastChild;
  },

  updateOverflowMenu: function (aMenuPopup)
  {
    var hbox = document.getElementById("bookmarks-ptf");
    for (var i = 0; i < hbox.childNodes.length; i++) {
      var button = hbox.childNodes[i];
      var menu = aMenuPopup.childNodes[i];
      if (menu.hidden == button.collapsed)
        menu.hidden = !menu.hidden;
    }
  },

  resizeFunc: function(event) 
  {
    if (!event) // timer callback case
      BookmarksToolbarRDFObserver._overflowTimerInEffect = false;
    // XXXcst - work around bug 295340 (broken event targets) on 1.8 branch
    //else if (event.target != document)
    //  return; // only interested in chrome resizes

    var buttons = document.getElementById("bookmarks-ptf");
    if (!buttons)
      return;

    var chevron = document.getElementById("bookmarks-chevron");
    if (!buttons.firstChild) {
      // No bookmarks means no chevron
      chevron.collapsed = true;
      return;
    }

    chevron.collapsed = false;
    var chevronWidth = chevron.boxObject.width;
    chevron.collapsed = true;

    var remainingWidth = buttons.boxObject.width;
    var overflowed = false;

    for (var i=0; i<buttons.childNodes.length; i++) {
      var button = buttons.childNodes[i];
      button.collapsed = overflowed;

      if (i == buttons.childNodes.length - 1)
        chevronWidth = 0;
      remainingWidth -= button.boxObject.width;
      if (remainingWidth < chevronWidth) {
        overflowed = true;
        // This button doesn't fit. Show it in the menu. Hide it in the toolbar.
        if (!button.collapsed)
          button.collapsed = true;
        if (chevron.collapsed) {
          chevron.collapsed = false;
        }
      }
    }
  },

  // Fill in tooltips for personal toolbar (and Bookmarks menu).
  fillInBTTooltip: function (tipElement)
  {
    // Don't show a tooltip for non bookmark related elements.
    if (!/bookmark/.test(tipElement.className))
      return false;

    var title = tipElement.label;
    var url = tipElement.statusText;

    // Don't show a tooltip without any data.
    if (!title && !url)
      return false;

    var tooltipElement = document.getElementById("btTitleText");
    tooltipElement.hidden = !title || (title == url);
    if (!tooltipElement.hidden)
      tooltipElement.setAttribute("value", title);

    tooltipElement = document.getElementById("btUrlText");
    tooltipElement.hidden = !url;
    if (!tooltipElement.hidden)
      tooltipElement.setAttribute("value", url);

    // Show the tooltip.
    return true;
  }
}

// Implement nsIRDFObserver so we can update our overflow state when items get
// added/removed from the toolbar
var BookmarksToolbarRDFObserver =
{
  onAssert: function (aDataSource, aSource, aProperty, aTarget)
  {
    this.setOverflowTimeout(aSource, aProperty);
  },
  onUnassert: function (aDataSource, aSource, aProperty, aTarget)
  {
    this.setOverflowTimeout(aSource, aProperty);
  },
  onChange: function (aDataSource, aSource, aProperty, aOldTarget, aNewTarget) {},
  onMove: function (aDataSource, aOldSource, aNewSource, aProperty, aTarget) {},
  onBeginUpdateBatch: function (aDataSource) {},
  onEndUpdateBatch: function (aDataSource)
  {
    if (this._overflowTimerInEffect)
      return;
    this._overflowTimerInEffect = true;
    setTimeout(BookmarksToolbar.resizeFunc, 0, null);
  },
  _overflowTimerInEffect: false,
  setOverflowTimeout: function (aSource, aProperty)
  {
    if (this._overflowTimerInEffect)
      return;
    if (aSource.Value != "NC:PersonalToolbarFolder" || aProperty.Value == NC_NS+"LastModifiedDate")
      return;
    this._overflowTimerInEffect = true;
    setTimeout(BookmarksToolbar.resizeFunc, 0, null);
  }
}
