browser/components/places/content/browserPlacesViews.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/places/content/browserPlacesViews.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1947 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     1.9 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.10 +
    1.11 +/**
    1.12 + * The base view implements everything that's common to the toolbar and
    1.13 + * menu views.
    1.14 + */
    1.15 +function PlacesViewBase(aPlace, aOptions) {
    1.16 +  this.place = aPlace;
    1.17 +  this.options = aOptions;
    1.18 +  this._controller = new PlacesController(this);
    1.19 +  this._viewElt.controllers.appendController(this._controller);
    1.20 +}
    1.21 +
    1.22 +PlacesViewBase.prototype = {
    1.23 +  // The xul element that holds the entire view.
    1.24 +  _viewElt: null,
    1.25 +  get viewElt() this._viewElt,
    1.26 +
    1.27 +  get associatedElement() this._viewElt,
    1.28 +
    1.29 +  get controllers() this._viewElt.controllers,
    1.30 +
    1.31 +  // The xul element that represents the root container.
    1.32 +  _rootElt: null,
    1.33 +
    1.34 +  // Set to true for views that are represented by native widgets (i.e.
    1.35 +  // the native mac menu).
    1.36 +  _nativeView: false,
    1.37 +
    1.38 +  QueryInterface: XPCOMUtils.generateQI(
    1.39 +    [Components.interfaces.nsINavHistoryResultObserver,
    1.40 +     Components.interfaces.nsISupportsWeakReference]),
    1.41 +
    1.42 +  _place: "",
    1.43 +  get place() this._place,
    1.44 +  set place(val) {
    1.45 +    this._place = val;
    1.46 +
    1.47 +    let history = PlacesUtils.history;
    1.48 +    let queries = { }, options = { };
    1.49 +    history.queryStringToQueries(val, queries, { }, options);
    1.50 +    if (!queries.value.length)
    1.51 +      queries.value = [history.getNewQuery()];
    1.52 +
    1.53 +    let result = history.executeQueries(queries.value, queries.value.length,
    1.54 +                                        options.value);
    1.55 +    result.addObserver(this, false);
    1.56 +    return val;
    1.57 +  },
    1.58 +
    1.59 +  _result: null,
    1.60 +  get result() this._result,
    1.61 +  set result(val) {
    1.62 +    if (this._result == val)
    1.63 +      return val;
    1.64 +
    1.65 +    if (this._result) {
    1.66 +      this._result.removeObserver(this);
    1.67 +      this._resultNode.containerOpen = false;
    1.68 +    }
    1.69 +
    1.70 +    if (this._rootElt.localName == "menupopup")
    1.71 +      this._rootElt._built = false;
    1.72 +
    1.73 +    this._result = val;
    1.74 +    if (val) {
    1.75 +      this._resultNode = val.root;
    1.76 +      this._rootElt._placesNode = this._resultNode;
    1.77 +      this._domNodes = new Map();
    1.78 +      this._domNodes.set(this._resultNode, this._rootElt);
    1.79 +
    1.80 +      // This calls _rebuild through invalidateContainer.
    1.81 +      this._resultNode.containerOpen = true;
    1.82 +    }
    1.83 +    else {
    1.84 +      this._resultNode = null;
    1.85 +      delete this._domNodes;
    1.86 +    }
    1.87 +
    1.88 +    return val;
    1.89 +  },
    1.90 +
    1.91 +  _options: null,
    1.92 +  get options() this._options,
    1.93 +  set options(val) {
    1.94 +    if (!val)
    1.95 +      val = {};
    1.96 +
    1.97 +    if (!("extraClasses" in val))
    1.98 +      val.extraClasses = {};
    1.99 +    this._options = val;
   1.100 +
   1.101 +    return val;
   1.102 +  },
   1.103 +
   1.104 +  /**
   1.105 +   * Gets the DOM node used for the given places node.
   1.106 +   *
   1.107 +   * @param aPlacesNode
   1.108 +   *        a places result node.
   1.109 +   * @throws if there is no DOM node set for aPlacesNode.
   1.110 +   */
   1.111 +  _getDOMNodeForPlacesNode:
   1.112 +  function PVB__getDOMNodeForPlacesNode(aPlacesNode) {
   1.113 +    let node = this._domNodes.get(aPlacesNode, null);
   1.114 +    if (!node) {
   1.115 +      throw new Error("No DOM node set for aPlacesNode.\nnode.type: " +
   1.116 +                      aPlacesNode.type + ". node.parent: " + aPlacesNode);
   1.117 +    }
   1.118 +    return node;
   1.119 +  },
   1.120 +
   1.121 +  get controller() this._controller,
   1.122 +
   1.123 +  get selType() "single",
   1.124 +  selectItems: function() { },
   1.125 +  selectAll: function() { },
   1.126 +
   1.127 +  get selectedNode() {
   1.128 +    if (this._contextMenuShown) {
   1.129 +      let popup = document.popupNode;
   1.130 +      return popup._placesNode || popup.parentNode._placesNode || null;
   1.131 +    }
   1.132 +    return null;
   1.133 +  },
   1.134 +
   1.135 +  get hasSelection() this.selectedNode != null,
   1.136 +
   1.137 +  get selectedNodes() {
   1.138 +    let selectedNode = this.selectedNode;
   1.139 +    return selectedNode ? [selectedNode] : [];
   1.140 +  },
   1.141 +
   1.142 +  get removableSelectionRanges() {
   1.143 +    // On static content the current selectedNode would be the selection's
   1.144 +    // parent node. We don't want to allow removing a node when the
   1.145 +    // selection is not explicit.
   1.146 +    if (document.popupNode &&
   1.147 +        (document.popupNode == "menupopup" || !document.popupNode._placesNode))
   1.148 +      return [];
   1.149 +
   1.150 +    return [this.selectedNodes];
   1.151 +  },
   1.152 +
   1.153 +  get draggableSelection() [this._draggedElt],
   1.154 +
   1.155 +  get insertionPoint() {
   1.156 +    // There is no insertion point for history queries, so bail out now and
   1.157 +    // save a lot of work when updating commands.
   1.158 +    let resultNode = this._resultNode;
   1.159 +    if (PlacesUtils.nodeIsQuery(resultNode) &&
   1.160 +        PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
   1.161 +          Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
   1.162 +      return null;
   1.163 +
   1.164 +    // By default, the insertion point is at the top level, at the end.
   1.165 +    let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
   1.166 +    let container = this._resultNode;
   1.167 +    let orientation = Ci.nsITreeView.DROP_BEFORE;
   1.168 +    let isTag = false;
   1.169 +
   1.170 +    let selectedNode = this.selectedNode;
   1.171 +    if (selectedNode) {
   1.172 +      let popup = document.popupNode;
   1.173 +      if (!popup._placesNode || popup._placesNode == this._resultNode ||
   1.174 +          popup._placesNode.itemId == -1) {
   1.175 +        // If a static menuitem is selected, or if the root node is selected,
   1.176 +        // the insertion point is inside the folder, at the end.
   1.177 +        container = selectedNode;
   1.178 +        orientation = Ci.nsITreeView.DROP_ON;
   1.179 +      }
   1.180 +      else {
   1.181 +        // In all other cases the insertion point is before that node.
   1.182 +        container = selectedNode.parent;
   1.183 +        index = container.getChildIndex(selectedNode);
   1.184 +        isTag = PlacesUtils.nodeIsTagQuery(container);
   1.185 +      }
   1.186 +    }
   1.187 +
   1.188 +    if (PlacesControllerDragHelper.disallowInsertion(container))
   1.189 +      return null;
   1.190 +
   1.191 +    return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
   1.192 +                              index, orientation, isTag);
   1.193 +  },
   1.194 +
   1.195 +  buildContextMenu: function PVB_buildContextMenu(aPopup) {
   1.196 +    this._contextMenuShown = true;
   1.197 +    window.updateCommands("places");
   1.198 +    return this.controller.buildContextMenu(aPopup);
   1.199 +  },
   1.200 +
   1.201 +  destroyContextMenu: function PVB_destroyContextMenu(aPopup) {
   1.202 +    this._contextMenuShown = false;
   1.203 +  },
   1.204 +
   1.205 +  _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) {
   1.206 +    // Remove Places nodes from the popup.
   1.207 +    let child = aPopup._startMarker;
   1.208 +    while (child.nextSibling != aPopup._endMarker) {
   1.209 +      let sibling = child.nextSibling;
   1.210 +      if (sibling._placesNode && !aDelay) {
   1.211 +        aPopup.removeChild(sibling);
   1.212 +      }
   1.213 +      else if (sibling._placesNode && aDelay) {
   1.214 +        // HACK (bug 733419): the popups originating from the OS X native
   1.215 +        // menubar don't live-update while open, thus we don't clean it
   1.216 +        // until the next popupshowing, to avoid zombie menuitems.
   1.217 +        if (!aPopup._delayedRemovals)
   1.218 +          aPopup._delayedRemovals = [];
   1.219 +        aPopup._delayedRemovals.push(sibling);
   1.220 +        child = child.nextSibling;
   1.221 +      }
   1.222 +      else {
   1.223 +        child = child.nextSibling;
   1.224 +      }
   1.225 +    }
   1.226 +  },
   1.227 +
   1.228 +  _rebuildPopup: function PVB__rebuildPopup(aPopup) {
   1.229 +    let resultNode = aPopup._placesNode;
   1.230 +    if (!resultNode.containerOpen)
   1.231 +      return;
   1.232 +
   1.233 +    if (this.controller.hasCachedLivemarkInfo(resultNode)) {
   1.234 +      this._setEmptyPopupStatus(aPopup, false);
   1.235 +      aPopup._built = true;
   1.236 +      this._populateLivemarkPopup(aPopup);
   1.237 +      return;
   1.238 +    }
   1.239 +
   1.240 +    this._cleanPopup(aPopup);
   1.241 +
   1.242 +    let cc = resultNode.childCount;
   1.243 +    if (cc > 0) {
   1.244 +      this._setEmptyPopupStatus(aPopup, false);
   1.245 +
   1.246 +      for (let i = 0; i < cc; ++i) {
   1.247 +        let child = resultNode.getChild(i);
   1.248 +        this._insertNewItemToPopup(child, aPopup, null);
   1.249 +      }
   1.250 +    }
   1.251 +    else {
   1.252 +      this._setEmptyPopupStatus(aPopup, true);
   1.253 +    }
   1.254 +    aPopup._built = true;
   1.255 +  },
   1.256 +
   1.257 +  _removeChild: function PVB__removeChild(aChild) {
   1.258 +    // If document.popupNode pointed to this child, null it out,
   1.259 +    // otherwise controller's command-updating may rely on the removed
   1.260 +    // item still being "selected".
   1.261 +    if (document.popupNode == aChild)
   1.262 +      document.popupNode = null;
   1.263 +
   1.264 +    aChild.parentNode.removeChild(aChild);
   1.265 +  },
   1.266 +
   1.267 +  _setEmptyPopupStatus:
   1.268 +  function PVB__setEmptyPopupStatus(aPopup, aEmpty) {
   1.269 +    if (!aPopup._emptyMenuitem) {
   1.270 +      let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
   1.271 +      aPopup._emptyMenuitem = document.createElement("menuitem");
   1.272 +      aPopup._emptyMenuitem.setAttribute("label", label);
   1.273 +      aPopup._emptyMenuitem.setAttribute("disabled", true);
   1.274 +      aPopup._emptyMenuitem.className = "bookmark-item";
   1.275 +      if (typeof this.options.extraClasses.entry == "string")
   1.276 +        aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry);
   1.277 +    }
   1.278 +
   1.279 +    if (aEmpty) {
   1.280 +      aPopup.setAttribute("emptyplacesresult", "true");
   1.281 +      // Don't add the menuitem if there is static content.
   1.282 +      if (!aPopup._startMarker.previousSibling &&
   1.283 +          !aPopup._endMarker.nextSibling)
   1.284 +        aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker);
   1.285 +    }
   1.286 +    else {
   1.287 +      aPopup.removeAttribute("emptyplacesresult");
   1.288 +      try {
   1.289 +        aPopup.removeChild(aPopup._emptyMenuitem);
   1.290 +      } catch (ex) {}
   1.291 +    }
   1.292 +  },
   1.293 +
   1.294 +  _createMenuItemForPlacesNode:
   1.295 +  function PVB__createMenuItemForPlacesNode(aPlacesNode) {
   1.296 +    this._domNodes.delete(aPlacesNode);
   1.297 +
   1.298 +    let element;
   1.299 +    let type = aPlacesNode.type;
   1.300 +    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
   1.301 +      element = document.createElement("menuseparator");
   1.302 +      element.setAttribute("class", "small-separator");
   1.303 +    }
   1.304 +    else {
   1.305 +      let itemId = aPlacesNode.itemId;
   1.306 +      if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
   1.307 +        element = document.createElement("menuitem");
   1.308 +        element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
   1.309 +        element.setAttribute("scheme",
   1.310 +                             PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
   1.311 +      }
   1.312 +      else if (PlacesUtils.containerTypes.indexOf(type) != -1) {
   1.313 +        element = document.createElement("menu");
   1.314 +        element.setAttribute("container", "true");
   1.315 +
   1.316 +        if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
   1.317 +          element.setAttribute("query", "true");
   1.318 +          if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
   1.319 +            element.setAttribute("tagContainer", "true");
   1.320 +          else if (PlacesUtils.nodeIsDay(aPlacesNode))
   1.321 +            element.setAttribute("dayContainer", "true");
   1.322 +          else if (PlacesUtils.nodeIsHost(aPlacesNode))
   1.323 +            element.setAttribute("hostContainer", "true");
   1.324 +        }
   1.325 +        else if (itemId != -1) {
   1.326 +          PlacesUtils.livemarks.getLivemark({ id: itemId })
   1.327 +            .then(aLivemark => {
   1.328 +              element.setAttribute("livemark", "true");
   1.329 +#ifdef XP_MACOSX
   1.330 +              // OS X native menubar doesn't track list-style-images since
   1.331 +              // it doesn't have a frame (bug 733415).  Thus enforce updating.
   1.332 +              element.setAttribute("image", "");
   1.333 +              element.removeAttribute("image");
   1.334 +#endif
   1.335 +              this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
   1.336 +            }, () => undefined);
   1.337 +        }
   1.338 +
   1.339 +        let popup = document.createElement("menupopup");
   1.340 +        popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
   1.341 +
   1.342 +        if (!this._nativeView) {
   1.343 +          popup.setAttribute("placespopup", "true");
   1.344 +        }
   1.345 +
   1.346 +        element.appendChild(popup);
   1.347 +        element.className = "menu-iconic bookmark-item";
   1.348 +        if (typeof this.options.extraClasses.entry == "string") {
   1.349 +          element.classList.add(this.options.extraClasses.entry);
   1.350 +        }
   1.351 +
   1.352 +        this._domNodes.set(aPlacesNode, popup);
   1.353 +      }
   1.354 +      else
   1.355 +        throw "Unexpected node";
   1.356 +
   1.357 +      element.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
   1.358 +
   1.359 +      let icon = aPlacesNode.icon;
   1.360 +      if (icon)
   1.361 +        element.setAttribute("image", icon);
   1.362 +    }
   1.363 +
   1.364 +    element._placesNode = aPlacesNode;
   1.365 +    if (!this._domNodes.has(aPlacesNode))
   1.366 +      this._domNodes.set(aPlacesNode, element);
   1.367 +
   1.368 +    return element;
   1.369 +  },
   1.370 +
   1.371 +  _insertNewItemToPopup:
   1.372 +  function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) {
   1.373 +    let element = this._createMenuItemForPlacesNode(aNewChild);
   1.374 +    let before = aBefore || aPopup._endMarker;
   1.375 +
   1.376 +    if (element.localName == "menuitem" || element.localName == "menu") {
   1.377 +      if (typeof this.options.extraClasses.entry == "string")
   1.378 +        element.classList.add(this.options.extraClasses.entry);
   1.379 +    }
   1.380 +
   1.381 +    aPopup.insertBefore(element, before);
   1.382 +    return element;
   1.383 +  },
   1.384 +
   1.385 +  _setLivemarkSiteURIMenuItem:
   1.386 +  function PVB__setLivemarkSiteURIMenuItem(aPopup) {
   1.387 +    let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
   1.388 +    let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
   1.389 +                  livemarkInfo.siteURI.spec : null;
   1.390 +    if (!siteUrl && aPopup._siteURIMenuitem) {
   1.391 +      aPopup.removeChild(aPopup._siteURIMenuitem);
   1.392 +      aPopup._siteURIMenuitem = null;
   1.393 +      aPopup.removeChild(aPopup._siteURIMenuseparator);
   1.394 +      aPopup._siteURIMenuseparator = null;
   1.395 +    }
   1.396 +    else if (siteUrl && !aPopup._siteURIMenuitem) {
   1.397 +      // Add "Open (Feed Name)" menuitem.
   1.398 +      aPopup._siteURIMenuitem = document.createElement("menuitem");
   1.399 +      aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
   1.400 +      if (typeof this.options.extraClasses.entry == "string") {
   1.401 +        aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
   1.402 +      }
   1.403 +      aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
   1.404 +      aPopup._siteURIMenuitem.setAttribute("oncommand",
   1.405 +        "openUILink(this.getAttribute('targetURI'), event);");
   1.406 +
   1.407 +      // If a user middle-clicks this item we serve the oncommand event.
   1.408 +      // We are using checkForMiddleClick because of Bug 246720.
   1.409 +      // Note: stopPropagation is needed to avoid serving middle-click
   1.410 +      // with BT_onClick that would open all items in tabs.
   1.411 +      aPopup._siteURIMenuitem.setAttribute("onclick",
   1.412 +        "checkForMiddleClick(this, event); event.stopPropagation();");
   1.413 +      let label =
   1.414 +        PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
   1.415 +                                         [aPopup.parentNode.getAttribute("label")])
   1.416 +      aPopup._siteURIMenuitem.setAttribute("label", label);
   1.417 +      aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
   1.418 +
   1.419 +      aPopup._siteURIMenuseparator = document.createElement("menuseparator");
   1.420 +      aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
   1.421 +    }
   1.422 +  },
   1.423 +
   1.424 +  /**
   1.425 +   * Add, update or remove the livemark status menuitem.
   1.426 +   * @param aPopup
   1.427 +   *        The livemark container popup
   1.428 +   * @param aStatus
   1.429 +   *        The livemark status
   1.430 +   */
   1.431 +  _setLivemarkStatusMenuItem:
   1.432 +  function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
   1.433 +    let statusMenuitem = aPopup._statusMenuitem;
   1.434 +    if (!statusMenuitem) {
   1.435 +      // Create the status menuitem and cache it in the popup object.
   1.436 +      statusMenuitem = document.createElement("menuitem");
   1.437 +      statusMenuitem.className = "livemarkstatus-menuitem";
   1.438 +      if (typeof this.options.extraClasses.entry == "string") {
   1.439 +        statusMenuitem.classList.add(this.options.extraClasses.entry);
   1.440 +      }
   1.441 +      statusMenuitem.setAttribute("disabled", true);
   1.442 +      aPopup._statusMenuitem = statusMenuitem;
   1.443 +    }
   1.444 +
   1.445 +    if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
   1.446 +        aStatus == Ci.mozILivemark.STATUS_FAILED) {
   1.447 +      // Status has changed, update the cached status menuitem.
   1.448 +      let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
   1.449 +                       "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
   1.450 +      statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
   1.451 +      if (aPopup._startMarker.nextSibling != statusMenuitem)
   1.452 +        aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
   1.453 +    }
   1.454 +    else {
   1.455 +      // The livemark has finished loading.
   1.456 +      if (aPopup._statusMenuitem.parentNode == aPopup)
   1.457 +        aPopup.removeChild(aPopup._statusMenuitem);
   1.458 +    }
   1.459 +  },
   1.460 +
   1.461 +  toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
   1.462 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.463 +
   1.464 +    // We may get the popup for menus, but we need the menu itself.
   1.465 +    if (elt.localName == "menupopup")
   1.466 +      elt = elt.parentNode;
   1.467 +    if (aValue)
   1.468 +      elt.setAttribute("cutting", "true");
   1.469 +    else
   1.470 +      elt.removeAttribute("cutting");
   1.471 +  },
   1.472 +
   1.473 +  nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) {
   1.474 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.475 +
   1.476 +    // Here we need the <menu>.
   1.477 +    if (elt.localName == "menupopup")
   1.478 +      elt = elt.parentNode;
   1.479 +
   1.480 +    elt.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aURIString));
   1.481 +  },
   1.482 +
   1.483 +  nodeIconChanged: function PVB_nodeIconChanged(aPlacesNode) {
   1.484 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.485 +
   1.486 +    // There's no UI representation for the root node, thus there's nothing to
   1.487 +    // be done when the icon changes.
   1.488 +    if (elt == this._rootElt)
   1.489 +      return;
   1.490 +
   1.491 +    // Here we need the <menu>.
   1.492 +    if (elt.localName == "menupopup")
   1.493 +      elt = elt.parentNode;
   1.494 +
   1.495 +    let icon = aPlacesNode.icon;
   1.496 +    if (!icon)
   1.497 +      elt.removeAttribute("image");
   1.498 +    else if (icon != elt.getAttribute("image"))
   1.499 +      elt.setAttribute("image", icon);
   1.500 +  },
   1.501 +
   1.502 +  nodeAnnotationChanged:
   1.503 +  function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
   1.504 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.505 +
   1.506 +    // All livemarks have a feedURI, so use it as our indicator of a livemark
   1.507 +    // being modified.
   1.508 +    if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
   1.509 +      let menu = elt.parentNode;
   1.510 +      if (!menu.hasAttribute("livemark")) {
   1.511 +        menu.setAttribute("livemark", "true");
   1.512 +#ifdef XP_MACOSX
   1.513 +        // OS X native menubar doesn't track list-style-images since
   1.514 +        // it doesn't have a frame (bug 733415).  Thus enforce updating.
   1.515 +        menu.setAttribute("image", "");
   1.516 +        menu.removeAttribute("image");
   1.517 +#endif
   1.518 +      }
   1.519 +
   1.520 +      PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
   1.521 +        .then(aLivemark => {
   1.522 +          // Controller will use this to build the meta data for the node.
   1.523 +          this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
   1.524 +          this.invalidateContainer(aPlacesNode);
   1.525 +        }, () => undefined);
   1.526 +    }
   1.527 +  },
   1.528 +
   1.529 +  nodeTitleChanged:
   1.530 +  function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
   1.531 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.532 +
   1.533 +    // There's no UI representation for the root node, thus there's
   1.534 +    // nothing to be done when the title changes.
   1.535 +    if (elt == this._rootElt)
   1.536 +      return;
   1.537 +
   1.538 +    // Here we need the <menu>.
   1.539 +    if (elt.localName == "menupopup")
   1.540 +      elt = elt.parentNode;
   1.541 +
   1.542 +    if (!aNewTitle && elt.localName != "toolbarbutton") {
   1.543 +      // Many users consider toolbars as shortcuts containers, so explicitly
   1.544 +      // allow empty labels on toolbarbuttons.  For any other element try to be
   1.545 +      // smarter, guessing a title from the uri.
   1.546 +      elt.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
   1.547 +    }
   1.548 +    else {
   1.549 +      elt.setAttribute("label", aNewTitle);
   1.550 +    }
   1.551 +  },
   1.552 +
   1.553 +  nodeRemoved:
   1.554 +  function PVB_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
   1.555 +    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
   1.556 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.557 +
   1.558 +    // Here we need the <menu>.
   1.559 +    if (elt.localName == "menupopup")
   1.560 +      elt = elt.parentNode;
   1.561 +
   1.562 +    if (parentElt._built) {
   1.563 +      parentElt.removeChild(elt);
   1.564 +
   1.565 +      // Figure out if we need to show the "<Empty>" menu-item.
   1.566 +      // TODO Bug 517701: This doesn't seem to handle the case of an empty
   1.567 +      // root.
   1.568 +      if (parentElt._startMarker.nextSibling == parentElt._endMarker)
   1.569 +        this._setEmptyPopupStatus(parentElt, true);
   1.570 +    }
   1.571 +  },
   1.572 +
   1.573 +  nodeHistoryDetailsChanged:
   1.574 +  function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
   1.575 +    if (aPlacesNode.parent &&
   1.576 +        this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
   1.577 +      // Find the node in the parent.
   1.578 +      let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
   1.579 +      for (let child = popup._startMarker.nextSibling;
   1.580 +           child != popup._endMarker;
   1.581 +           child = child.nextSibling) {
   1.582 +        if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
   1.583 +          if (aCount)
   1.584 +            child.setAttribute("visited", "true");
   1.585 +          else
   1.586 +            child.removeAttribute("visited");
   1.587 +          break;
   1.588 +        }
   1.589 +      }
   1.590 +    }
   1.591 +  },
   1.592 +
   1.593 +  nodeTagsChanged: function() { },
   1.594 +  nodeDateAddedChanged: function() { },
   1.595 +  nodeLastModifiedChanged: function() { },
   1.596 +  nodeKeywordChanged: function() { },
   1.597 +  sortingChanged: function() { },
   1.598 +  batching: function() { },
   1.599 +
   1.600 +  nodeInserted:
   1.601 +  function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
   1.602 +    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
   1.603 +    if (!parentElt._built)
   1.604 +      return;
   1.605 +
   1.606 +    let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) +
   1.607 +                aIndex + 1;
   1.608 +    this._insertNewItemToPopup(aPlacesNode, parentElt,
   1.609 +                               parentElt.childNodes[index]);
   1.610 +    this._setEmptyPopupStatus(parentElt, false);
   1.611 +  },
   1.612 +
   1.613 +  nodeMoved:
   1.614 +  function PBV_nodeMoved(aPlacesNode,
   1.615 +                         aOldParentPlacesNode, aOldIndex,
   1.616 +                         aNewParentPlacesNode, aNewIndex) {
   1.617 +    // Note: the current implementation of moveItem does not actually
   1.618 +    // use this notification when the item in question is moved from one
   1.619 +    // folder to another.  Instead, it calls nodeRemoved and nodeInserted
   1.620 +    // for the two folders.  Thus, we can assume old-parent == new-parent.
   1.621 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.622 +
   1.623 +    // Here we need the <menu>.
   1.624 +    if (elt.localName == "menupopup")
   1.625 +      elt = elt.parentNode;
   1.626 +
   1.627 +    // If our root node is a folder, it might be moved. There's nothing
   1.628 +    // we need to do in that case.
   1.629 +    if (elt == this._rootElt)
   1.630 +      return;
   1.631 +
   1.632 +    let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
   1.633 +    if (parentElt._built) {
   1.634 +      // Move the node.
   1.635 +      parentElt.removeChild(elt);
   1.636 +      let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) +
   1.637 +                  aNewIndex + 1;
   1.638 +      parentElt.insertBefore(elt, parentElt.childNodes[index]);
   1.639 +    }
   1.640 +  },
   1.641 +
   1.642 +  containerStateChanged:
   1.643 +  function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
   1.644 +    if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
   1.645 +        aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
   1.646 +      this.invalidateContainer(aPlacesNode);
   1.647 +
   1.648 +      if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
   1.649 +        let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
   1.650 +        if (queryOptions.excludeItems) {
   1.651 +          return;
   1.652 +        }
   1.653 +
   1.654 +        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
   1.655 +          .then(aLivemark => {
   1.656 +            let shouldInvalidate =
   1.657 +              !this.controller.hasCachedLivemarkInfo(aPlacesNode);
   1.658 +            this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
   1.659 +            if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
   1.660 +              aLivemark.registerForUpdates(aPlacesNode, this);
   1.661 +              // Prioritize the current livemark.
   1.662 +              aLivemark.reload();
   1.663 +              PlacesUtils.livemarks.reloadLivemarks();
   1.664 +              if (shouldInvalidate)
   1.665 +                this.invalidateContainer(aPlacesNode);
   1.666 +            }
   1.667 +            else {
   1.668 +              aLivemark.unregisterForUpdates(aPlacesNode);
   1.669 +            }
   1.670 +          }, () => undefined);
   1.671 +      }
   1.672 +    }
   1.673 +  },
   1.674 +
   1.675 +  _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup)
   1.676 +  {
   1.677 +    this._setLivemarkSiteURIMenuItem(aPopup);
   1.678 +    // Show the loading status only if there are no entries yet.
   1.679 +    if (aPopup._startMarker.nextSibling == aPopup._endMarker)
   1.680 +      this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
   1.681 +
   1.682 +    PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
   1.683 +      .then(aLivemark => {
   1.684 +        let placesNode = aPopup._placesNode;
   1.685 +        if (!placesNode.containerOpen)
   1.686 +          return;
   1.687 +
   1.688 +        if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
   1.689 +          this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
   1.690 +        this._cleanPopup(aPopup,
   1.691 +          this._nativeView && aPopup.parentNode.hasAttribute("open"));
   1.692 +
   1.693 +        let children = aLivemark.getNodesForContainer(placesNode);
   1.694 +        for (let i = 0; i < children.length; i++) {
   1.695 +          let child = children[i];
   1.696 +          this.nodeInserted(placesNode, child, i);
   1.697 +          if (child.accessCount)
   1.698 +            this._getDOMNodeForPlacesNode(child).setAttribute("visited", true);
   1.699 +          else
   1.700 +            this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
   1.701 +        }
   1.702 +      }, Components.utils.reportError);
   1.703 +  },
   1.704 +
   1.705 +  invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
   1.706 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   1.707 +    elt._built = false;
   1.708 +
   1.709 +    // If the menupopup is open we should live-update it.
   1.710 +    if (elt.parentNode.open)
   1.711 +      this._rebuildPopup(elt);
   1.712 +  },
   1.713 +
   1.714 +  uninit: function PVB_uninit() {
   1.715 +    if (this._result) {
   1.716 +      this._result.removeObserver(this);
   1.717 +      this._resultNode.containerOpen = false;
   1.718 +      this._resultNode = null;
   1.719 +      this._result = null;
   1.720 +    }
   1.721 +
   1.722 +    if (this._controller) {
   1.723 +      this._controller.terminate();
   1.724 +      // Removing the controller will fail if it is already no longer there.
   1.725 +      // This can happen if the view element was removed/reinserted without
   1.726 +      // our knowledge. There is no way to check for that having happened
   1.727 +      // without the possibility of an exception. :-(
   1.728 +      try {
   1.729 +        this._viewElt.controllers.removeController(this._controller);
   1.730 +      } catch (ex) {
   1.731 +      } finally {
   1.732 +        this._controller = null;
   1.733 +      }
   1.734 +    }
   1.735 +
   1.736 +    delete this._viewElt._placesView;
   1.737 +  },
   1.738 +
   1.739 +  get isRTL() {
   1.740 +    if ("_isRTL" in this)
   1.741 +      return this._isRTL;
   1.742 +
   1.743 +    return this._isRTL = document.defaultView
   1.744 +                                 .getComputedStyle(this.viewElt, "")
   1.745 +                                 .direction == "rtl";
   1.746 +  },
   1.747 +
   1.748 +  get ownerWindow() window,
   1.749 +
   1.750 +  /**
   1.751 +   * Adds an "Open All in Tabs" menuitem to the bottom of the popup.
   1.752 +   * @param aPopup
   1.753 +   *        a Places popup.
   1.754 +   */
   1.755 +  _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) {
   1.756 +    // The command items are never added to the root popup.
   1.757 +    if (aPopup == this._rootElt)
   1.758 +      return;
   1.759 +
   1.760 +    let hasMultipleURIs = false;
   1.761 +
   1.762 +    // Check if the popup contains at least 2 menuitems with places nodes.
   1.763 +    // We don't currently support opening multiple uri nodes when they are not
   1.764 +    // populated by the result.
   1.765 +    if (aPopup._placesNode.childCount > 0) {
   1.766 +      let currentChild = aPopup.firstChild;
   1.767 +      let numURINodes = 0;
   1.768 +      while (currentChild) {
   1.769 +        if (currentChild.localName == "menuitem" && currentChild._placesNode) {
   1.770 +          if (++numURINodes == 2)
   1.771 +            break;
   1.772 +        }
   1.773 +        currentChild = currentChild.nextSibling;
   1.774 +      }
   1.775 +      hasMultipleURIs = numURINodes > 1;
   1.776 +    }
   1.777 +
   1.778 +    if (!hasMultipleURIs) {
   1.779 +      aPopup.setAttribute("singleitempopup", "true");
   1.780 +    } else {
   1.781 +      aPopup.removeAttribute("singleitempopup");
   1.782 +    }
   1.783 +
   1.784 +    if (!hasMultipleURIs) {
   1.785 +      // We don't have to show any option.
   1.786 +      if (aPopup._endOptOpenAllInTabs) {
   1.787 +        aPopup.removeChild(aPopup._endOptOpenAllInTabs);
   1.788 +        aPopup._endOptOpenAllInTabs = null;
   1.789 +
   1.790 +        aPopup.removeChild(aPopup._endOptSeparator);
   1.791 +        aPopup._endOptSeparator = null;
   1.792 +      }
   1.793 +    }
   1.794 +    else if (!aPopup._endOptOpenAllInTabs) {
   1.795 +      // Create a separator before options.
   1.796 +      aPopup._endOptSeparator = document.createElement("menuseparator");
   1.797 +      aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
   1.798 +      aPopup.appendChild(aPopup._endOptSeparator);
   1.799 +
   1.800 +      // Add the "Open All in Tabs" menuitem.
   1.801 +      aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
   1.802 +      aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
   1.803 +
   1.804 +      if (typeof this.options.extraClasses.entry == "string")
   1.805 +        aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry);
   1.806 +      if (typeof this.options.extraClasses.footer == "string")
   1.807 +        aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
   1.808 +
   1.809 +      aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
   1.810 +        "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
   1.811 +                                               "PlacesUIUtils.getViewForNode(this));");
   1.812 +      aPopup._endOptOpenAllInTabs.setAttribute("onclick",
   1.813 +        "checkForMiddleClick(this, event); event.stopPropagation();");
   1.814 +      aPopup._endOptOpenAllInTabs.setAttribute("label",
   1.815 +        gNavigatorBundle.getString("menuOpenAllInTabs.label"));
   1.816 +      aPopup.appendChild(aPopup._endOptOpenAllInTabs);
   1.817 +    }
   1.818 +  },
   1.819 +
   1.820 +  _ensureMarkers: function PVB__ensureMarkers(aPopup) {
   1.821 +    if (aPopup._startMarker)
   1.822 +      return;
   1.823 +
   1.824 +    // _startMarker is an hidden menuseparator that lives before places nodes.
   1.825 +    aPopup._startMarker = document.createElement("menuseparator");
   1.826 +    aPopup._startMarker.hidden = true;
   1.827 +    aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild);
   1.828 +
   1.829 +    // _endMarker is a DOM node that lives after places nodes, specified with
   1.830 +    // the 'insertionPoint' option or will be a hidden menuseparator.
   1.831 +    let node = ("insertionPoint" in this.options) ?
   1.832 +               aPopup.querySelector(this.options.insertionPoint) : null;
   1.833 +    if (node) {
   1.834 +      aPopup._endMarker = node;
   1.835 +    } else {
   1.836 +      aPopup._endMarker = document.createElement("menuseparator");
   1.837 +      aPopup._endMarker.hidden = true;
   1.838 +    }
   1.839 +    aPopup.appendChild(aPopup._endMarker);
   1.840 +
   1.841 +    // Move the markers to the right position.
   1.842 +    let firstNonStaticNodeFound = false;
   1.843 +    for (let i = 0; i < aPopup.childNodes.length; i++) {
   1.844 +      let child = aPopup.childNodes[i];
   1.845 +      // Menus that have static content at the end, but are initially empty,
   1.846 +      // use a special "builder" attribute to figure out where to start
   1.847 +      // inserting places nodes.
   1.848 +      if (child.getAttribute("builder") == "end") {
   1.849 +        aPopup.insertBefore(aPopup._endMarker, child);
   1.850 +        break;
   1.851 +      }
   1.852 +
   1.853 +      if (child._placesNode && !firstNonStaticNodeFound) {
   1.854 +        firstNonStaticNodeFound = true;
   1.855 +        aPopup.insertBefore(aPopup._startMarker, child);
   1.856 +      }
   1.857 +    }
   1.858 +    if (!firstNonStaticNodeFound) {
   1.859 +      aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker);
   1.860 +    }
   1.861 +  },
   1.862 +
   1.863 +  _onPopupShowing: function PVB__onPopupShowing(aEvent) {
   1.864 +    // Avoid handling popupshowing of inner views.
   1.865 +    let popup = aEvent.originalTarget;
   1.866 +
   1.867 +    this._ensureMarkers(popup);
   1.868 +
   1.869 +    // Remove any delayed element, see _cleanPopup for details.
   1.870 +    if ("_delayedRemovals" in popup) {
   1.871 +      while (popup._delayedRemovals.length > 0) {
   1.872 +        popup.removeChild(popup._delayedRemovals.shift());
   1.873 +      }
   1.874 +    }
   1.875 +
   1.876 +    if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
   1.877 +      if (!popup._placesNode.containerOpen)
   1.878 +        popup._placesNode.containerOpen = true;
   1.879 +      if (!popup._built)
   1.880 +        this._rebuildPopup(popup);
   1.881 +
   1.882 +      this._mayAddCommandsItems(popup);
   1.883 +    }
   1.884 +  },
   1.885 +
   1.886 +  _addEventListeners:
   1.887 +  function PVB__addEventListeners(aObject, aEventNames, aCapturing) {
   1.888 +    for (let i = 0; i < aEventNames.length; i++) {
   1.889 +      aObject.addEventListener(aEventNames[i], this, aCapturing);
   1.890 +    }
   1.891 +  },
   1.892 +
   1.893 +  _removeEventListeners:
   1.894 +  function PVB__removeEventListeners(aObject, aEventNames, aCapturing) {
   1.895 +    for (let i = 0; i < aEventNames.length; i++) {
   1.896 +      aObject.removeEventListener(aEventNames[i], this, aCapturing);
   1.897 +    }
   1.898 +  },
   1.899 +};
   1.900 +
   1.901 +function PlacesToolbar(aPlace) {
   1.902 +  let startTime = Date.now();
   1.903 +  // Add some smart getters for our elements.
   1.904 +  let thisView = this;
   1.905 +  [
   1.906 +    ["_viewElt",              "PlacesToolbar"],
   1.907 +    ["_rootElt",              "PlacesToolbarItems"],
   1.908 +    ["_dropIndicator",        "PlacesToolbarDropIndicator"],
   1.909 +    ["_chevron",              "PlacesChevron"],
   1.910 +    ["_chevronPopup",         "PlacesChevronPopup"]
   1.911 +  ].forEach(function (elementGlobal) {
   1.912 +    let [name, id] = elementGlobal;
   1.913 +    thisView.__defineGetter__(name, function () {
   1.914 +      let element = document.getElementById(id);
   1.915 +      if (!element)
   1.916 +        return null;
   1.917 +
   1.918 +      delete thisView[name];
   1.919 +      return thisView[name] = element;
   1.920 +    });
   1.921 +  });
   1.922 +
   1.923 +  this._viewElt._placesView = this;
   1.924 +
   1.925 +  this._addEventListeners(this._viewElt, this._cbEvents, false);
   1.926 +  this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
   1.927 +  this._addEventListeners(this._rootElt, ["overflow", "underflow"], true);
   1.928 +  this._addEventListeners(window, ["resize", "unload"], false);
   1.929 +
   1.930 +  // If personal-bookmarks has been dragged to the tabs toolbar,
   1.931 +  // we have to track addition and removals of tabs, to properly
   1.932 +  // recalculate the available space for bookmarks.
   1.933 +  // TODO (bug 734730): Use a performant mutation listener when available.
   1.934 +  if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) {
   1.935 +    this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
   1.936 +  }
   1.937 +
   1.938 +  PlacesViewBase.call(this, aPlace);
   1.939 +
   1.940 +  Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS")
   1.941 +                    .add(Date.now() - startTime);
   1.942 +}
   1.943 +
   1.944 +PlacesToolbar.prototype = {
   1.945 +  __proto__: PlacesViewBase.prototype,
   1.946 +
   1.947 +  _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop",
   1.948 +              "mousemove", "mouseover", "mouseout"],
   1.949 +
   1.950 +  QueryInterface: function PT_QueryInterface(aIID) {
   1.951 +    if (aIID.equals(Ci.nsIDOMEventListener) ||
   1.952 +        aIID.equals(Ci.nsITimerCallback))
   1.953 +      return this;
   1.954 +
   1.955 +    return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
   1.956 +  },
   1.957 +
   1.958 +  uninit: function PT_uninit() {
   1.959 +    this._removeEventListeners(this._viewElt, this._cbEvents, false);
   1.960 +    this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
   1.961 +                               true);
   1.962 +    this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true);
   1.963 +    this._removeEventListeners(window, ["resize", "unload"], false);
   1.964 +    this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
   1.965 +
   1.966 +    if (this._chevron._placesView) {
   1.967 +      this._chevron._placesView.uninit();
   1.968 +    }
   1.969 +
   1.970 +    PlacesViewBase.prototype.uninit.apply(this, arguments);
   1.971 +  },
   1.972 +
   1.973 +  _openedMenuButton: null,
   1.974 +  _allowPopupShowing: true,
   1.975 +
   1.976 +  _rebuild: function PT__rebuild() {
   1.977 +    // Clear out references to existing nodes, since they will be removed
   1.978 +    // and re-added.
   1.979 +    if (this._overFolder.elt)
   1.980 +      this._clearOverFolder();
   1.981 +
   1.982 +    this._openedMenuButton = null;
   1.983 +    while (this._rootElt.hasChildNodes()) {
   1.984 +      this._rootElt.removeChild(this._rootElt.firstChild);
   1.985 +    }
   1.986 +
   1.987 +    let cc = this._resultNode.childCount;
   1.988 +    for (let i = 0; i < cc; ++i) {
   1.989 +      this._insertNewItem(this._resultNode.getChild(i), null);
   1.990 +    }
   1.991 +
   1.992 +    if (this._chevronPopup.hasAttribute("type")) {
   1.993 +      // Chevron has already been initialized, but since we are forcing
   1.994 +      // a rebuild of the toolbar, it has to be rebuilt.
   1.995 +      // Otherwise, it will be initialized when the toolbar overflows.
   1.996 +      this._chevronPopup.place = this.place;
   1.997 +    }
   1.998 +  },
   1.999 +
  1.1000 +  _insertNewItem:
  1.1001 +  function PT__insertNewItem(aChild, aBefore) {
  1.1002 +    this._domNodes.delete(aChild);
  1.1003 +
  1.1004 +    let type = aChild.type;
  1.1005 +    let button;
  1.1006 +    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
  1.1007 +      button = document.createElement("toolbarseparator");
  1.1008 +    }
  1.1009 +    else {
  1.1010 +      button = document.createElement("toolbarbutton");
  1.1011 +      button.className = "bookmark-item";
  1.1012 +      button.setAttribute("label", aChild.title);
  1.1013 +      let icon = aChild.icon;
  1.1014 +      if (icon)
  1.1015 +        button.setAttribute("image", icon);
  1.1016 +
  1.1017 +      if (PlacesUtils.containerTypes.indexOf(type) != -1) {
  1.1018 +        button.setAttribute("type", "menu");
  1.1019 +        button.setAttribute("container", "true");
  1.1020 +
  1.1021 +        if (PlacesUtils.nodeIsQuery(aChild)) {
  1.1022 +          button.setAttribute("query", "true");
  1.1023 +          if (PlacesUtils.nodeIsTagQuery(aChild))
  1.1024 +            button.setAttribute("tagContainer", "true");
  1.1025 +        }
  1.1026 +        else if (PlacesUtils.nodeIsFolder(aChild)) {
  1.1027 +          PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
  1.1028 +            .then(aLivemark => {
  1.1029 +              button.setAttribute("livemark", "true");
  1.1030 +              this.controller.cacheLivemarkInfo(aChild, aLivemark);
  1.1031 +            }, () => undefined);
  1.1032 +        }
  1.1033 +
  1.1034 +        let popup = document.createElement("menupopup");
  1.1035 +        popup.setAttribute("placespopup", "true");
  1.1036 +        button.appendChild(popup);
  1.1037 +        popup._placesNode = PlacesUtils.asContainer(aChild);
  1.1038 +        popup.setAttribute("context", "placesContext");
  1.1039 +
  1.1040 +        this._domNodes.set(aChild, popup);
  1.1041 +      }
  1.1042 +      else if (PlacesUtils.nodeIsURI(aChild)) {
  1.1043 +        button.setAttribute("scheme",
  1.1044 +                            PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
  1.1045 +      }
  1.1046 +    }
  1.1047 +
  1.1048 +    button._placesNode = aChild;
  1.1049 +    if (!this._domNodes.has(aChild))
  1.1050 +      this._domNodes.set(aChild, button);
  1.1051 +
  1.1052 +    if (aBefore)
  1.1053 +      this._rootElt.insertBefore(button, aBefore);
  1.1054 +    else
  1.1055 +      this._rootElt.appendChild(button);
  1.1056 +  },
  1.1057 +
  1.1058 +  _updateChevronPopupNodesVisibility:
  1.1059 +  function PT__updateChevronPopupNodesVisibility() {
  1.1060 +    for (let i = 0, node = this._chevronPopup._startMarker.nextSibling;
  1.1061 +         node != this._chevronPopup._endMarker;
  1.1062 +         i++, node = node.nextSibling) {
  1.1063 +      node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden";
  1.1064 +    }
  1.1065 +  },
  1.1066 +
  1.1067 +  _onChevronPopupShowing:
  1.1068 +  function PT__onChevronPopupShowing(aEvent) {
  1.1069 +    // Handle popupshowing only for the chevron popup, not for nested ones.
  1.1070 +    if (aEvent.target != this._chevronPopup)
  1.1071 +      return;
  1.1072 +
  1.1073 +    if (!this._chevron._placesView)
  1.1074 +      this._chevron._placesView = new PlacesMenu(aEvent, this.place);
  1.1075 +
  1.1076 +    this._updateChevronPopupNodesVisibility();
  1.1077 +  },
  1.1078 +
  1.1079 +  handleEvent: function PT_handleEvent(aEvent) {
  1.1080 +    switch (aEvent.type) {
  1.1081 +      case "unload":
  1.1082 +        this.uninit();
  1.1083 +        break;
  1.1084 +      case "resize":
  1.1085 +        // This handler updates nodes visibility in both the toolbar
  1.1086 +        // and the chevron popup when a window resize does not change
  1.1087 +        // the overflow status of the toolbar.
  1.1088 +        this.updateChevron();
  1.1089 +        break;
  1.1090 +      case "overflow":
  1.1091 +        if (!this._isOverflowStateEventRelevant(aEvent))
  1.1092 +          return;
  1.1093 +        this._onOverflow();
  1.1094 +        break;
  1.1095 +      case "underflow":
  1.1096 +        if (!this._isOverflowStateEventRelevant(aEvent))
  1.1097 +          return;
  1.1098 +        this._onUnderflow();
  1.1099 +        break;
  1.1100 +      case "TabOpen":
  1.1101 +      case "TabClose":
  1.1102 +        this.updateChevron();
  1.1103 +        break;
  1.1104 +      case "dragstart":
  1.1105 +        this._onDragStart(aEvent);
  1.1106 +        break;
  1.1107 +      case "dragover":
  1.1108 +        this._onDragOver(aEvent);
  1.1109 +        break;
  1.1110 +      case "dragexit":
  1.1111 +        this._onDragExit(aEvent);
  1.1112 +        break;
  1.1113 +      case "dragend":
  1.1114 +        this._onDragEnd(aEvent);
  1.1115 +        break;
  1.1116 +      case "drop":
  1.1117 +        this._onDrop(aEvent);
  1.1118 +        break;
  1.1119 +      case "mouseover":
  1.1120 +        this._onMouseOver(aEvent);
  1.1121 +        break;
  1.1122 +      case "mousemove":
  1.1123 +        this._onMouseMove(aEvent);
  1.1124 +        break;
  1.1125 +      case "mouseout":
  1.1126 +        this._onMouseOut(aEvent);
  1.1127 +        break;
  1.1128 +      case "popupshowing":
  1.1129 +        this._onPopupShowing(aEvent);
  1.1130 +        break;
  1.1131 +      case "popuphidden":
  1.1132 +        this._onPopupHidden(aEvent);
  1.1133 +        break;
  1.1134 +      default:
  1.1135 +        throw "Trying to handle unexpected event.";
  1.1136 +    }
  1.1137 +  },
  1.1138 +
  1.1139 +  updateOverflowStatus: function() {
  1.1140 +    if (this._rootElt.scrollLeftMax > 0) {
  1.1141 +      this._onOverflow();
  1.1142 +    } else {
  1.1143 +      this._onUnderflow();
  1.1144 +    }
  1.1145 +  },
  1.1146 +
  1.1147 +  _isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) {
  1.1148 +    // Ignore events not aimed at ourselves, as well as purely vertical ones:
  1.1149 +    return aEvent.target == aEvent.currentTarget && aEvent.detail > 0;
  1.1150 +  },
  1.1151 +
  1.1152 +  _onOverflow: function PT_onOverflow() {
  1.1153 +    // Attach the popup binding to the chevron popup if it has not yet
  1.1154 +    // been initialized.
  1.1155 +    if (!this._chevronPopup.hasAttribute("type")) {
  1.1156 +      this._chevronPopup.setAttribute("place", this.place);
  1.1157 +      this._chevronPopup.setAttribute("type", "places");
  1.1158 +    }
  1.1159 +    this._chevron.collapsed = false;
  1.1160 +    this.updateChevron();
  1.1161 +  },
  1.1162 +
  1.1163 +  _onUnderflow: function PT_onUnderflow() {
  1.1164 +    this.updateChevron();
  1.1165 +    this._chevron.collapsed = true;
  1.1166 +  },
  1.1167 +
  1.1168 +  updateChevron: function PT_updateChevron() {
  1.1169 +    // If the chevron is collapsed there's nothing to update.
  1.1170 +    if (this._chevron.collapsed)
  1.1171 +      return;
  1.1172 +
  1.1173 +    // Update the chevron on a timer.  This will avoid repeated work when
  1.1174 +    // lot of changes happen in a small timeframe.
  1.1175 +    if (this._updateChevronTimer)
  1.1176 +      this._updateChevronTimer.cancel();
  1.1177 +
  1.1178 +    this._updateChevronTimer = this._setTimer(100);
  1.1179 +  },
  1.1180 +
  1.1181 +  _updateChevronTimerCallback: function PT__updateChevronTimerCallback() {
  1.1182 +    let scrollRect = this._rootElt.getBoundingClientRect();
  1.1183 +    let childOverflowed = false;
  1.1184 +    for (let i = 0; i < this._rootElt.childNodes.length; i++) {
  1.1185 +      let child = this._rootElt.childNodes[i];
  1.1186 +      // Once a child overflows, all the next ones will.
  1.1187 +      if (!childOverflowed) {
  1.1188 +        let childRect = child.getBoundingClientRect();
  1.1189 +        childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
  1.1190 +                                     : (childRect.right > scrollRect.right);
  1.1191 +                                      
  1.1192 +      }
  1.1193 +      child.style.visibility = childOverflowed ? "hidden" : "visible";
  1.1194 +    }
  1.1195 +
  1.1196 +    // We rebuild the chevron on popupShowing, so if it is open
  1.1197 +    // we must update it.
  1.1198 +    if (this._chevron.open)
  1.1199 +      this._updateChevronPopupNodesVisibility();
  1.1200 +  },
  1.1201 +
  1.1202 +  nodeInserted:
  1.1203 +  function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
  1.1204 +    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1.1205 +    if (parentElt == this._rootElt) {
  1.1206 +      let children = this._rootElt.childNodes;
  1.1207 +      this._insertNewItem(aPlacesNode,
  1.1208 +        aIndex < children.length ? children[aIndex] : null);
  1.1209 +      this.updateChevron();
  1.1210 +      return;
  1.1211 +    }
  1.1212 +
  1.1213 +    PlacesViewBase.prototype.nodeInserted.apply(this, arguments);
  1.1214 +  },
  1.1215 +
  1.1216 +  nodeRemoved:
  1.1217 +  function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
  1.1218 +    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1.1219 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1220 +
  1.1221 +    // Here we need the <menu>.
  1.1222 +    if (elt.localName == "menupopup")
  1.1223 +      elt = elt.parentNode;
  1.1224 +
  1.1225 +    if (parentElt == this._rootElt) {
  1.1226 +      this._removeChild(elt);
  1.1227 +      this.updateChevron();
  1.1228 +      return;
  1.1229 +    }
  1.1230 +
  1.1231 +    PlacesViewBase.prototype.nodeRemoved.apply(this, arguments);
  1.1232 +  },
  1.1233 +
  1.1234 +  nodeMoved:
  1.1235 +  function PT_nodeMoved(aPlacesNode,
  1.1236 +                        aOldParentPlacesNode, aOldIndex,
  1.1237 +                        aNewParentPlacesNode, aNewIndex) {
  1.1238 +    let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
  1.1239 +    if (parentElt == this._rootElt) {
  1.1240 +      // Container is on the toolbar.
  1.1241 +
  1.1242 +      // Move the element.
  1.1243 +      let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1244 +
  1.1245 +      // Here we need the <menu>.
  1.1246 +      if (elt.localName == "menupopup")
  1.1247 +        elt = elt.parentNode;
  1.1248 +
  1.1249 +      this._removeChild(elt);
  1.1250 +      this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
  1.1251 +
  1.1252 +      // The chevron view may get nodeMoved after the toolbar.  In such a case,
  1.1253 +      // we should ensure (by manually swapping menuitems) that the actual nodes
  1.1254 +      // are in the final position before updateChevron tries to updates their
  1.1255 +      // visibility, or the chevron may go out of sync.
  1.1256 +      // Luckily updateChevron runs on a timer, so, by the time it updates
  1.1257 +      // nodes, the menu has already handled the notification.
  1.1258 +
  1.1259 +      this.updateChevron();
  1.1260 +      return;
  1.1261 +    }
  1.1262 +
  1.1263 +    PlacesViewBase.prototype.nodeMoved.apply(this, arguments);
  1.1264 +  },
  1.1265 +
  1.1266 +  nodeAnnotationChanged:
  1.1267 +  function PT_nodeAnnotationChanged(aPlacesNode, aAnno) {
  1.1268 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1269 +    if (elt == this._rootElt)
  1.1270 +      return;
  1.1271 +
  1.1272 +    // We're notified for the menupopup, not the containing toolbarbutton.
  1.1273 +    if (elt.localName == "menupopup")
  1.1274 +      elt = elt.parentNode;
  1.1275 +
  1.1276 +    if (elt.parentNode == this._rootElt) {
  1.1277 +      // Node is on the toolbar.
  1.1278 +
  1.1279 +      // All livemarks have a feedURI, so use it as our indicator.
  1.1280 +      if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
  1.1281 +        elt.setAttribute("livemark", true);
  1.1282 +
  1.1283 +        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
  1.1284 +          .then(aLivemark => {
  1.1285 +            this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
  1.1286 +            this.invalidateContainer(aPlacesNode);
  1.1287 +          }, Components.utils.reportError);
  1.1288 +      }
  1.1289 +    }
  1.1290 +    else {
  1.1291 +      // Node is in a submenu.
  1.1292 +      PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
  1.1293 +    }
  1.1294 +  },
  1.1295 +
  1.1296 +  nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
  1.1297 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1298 +
  1.1299 +    // There's no UI representation for the root node, thus there's
  1.1300 +    // nothing to be done when the title changes.
  1.1301 +    if (elt == this._rootElt)
  1.1302 +      return;
  1.1303 +
  1.1304 +    PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
  1.1305 +
  1.1306 +    // Here we need the <menu>.
  1.1307 +    if (elt.localName == "menupopup")
  1.1308 +      elt = elt.parentNode;
  1.1309 +
  1.1310 +    if (elt.parentNode == this._rootElt) {
  1.1311 +      // Node is on the toolbar
  1.1312 +      this.updateChevron();
  1.1313 +    }
  1.1314 +  },
  1.1315 +
  1.1316 +  invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
  1.1317 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1318 +    if (elt == this._rootElt) {
  1.1319 +      // Container is the toolbar itself.
  1.1320 +      this._rebuild();
  1.1321 +      return;
  1.1322 +    }
  1.1323 +
  1.1324 +    PlacesViewBase.prototype.invalidateContainer.apply(this, arguments);
  1.1325 +  },
  1.1326 +
  1.1327 +  _overFolder: { elt: null,
  1.1328 +                 openTimer: null,
  1.1329 +                 hoverTime: 350,
  1.1330 +                 closeTimer: null },
  1.1331 +
  1.1332 +  _clearOverFolder: function PT__clearOverFolder() {
  1.1333 +    // The mouse is no longer dragging over the stored menubutton.
  1.1334 +    // Close the menubutton, clear out drag styles, and clear all
  1.1335 +    // timers for opening/closing it.
  1.1336 +    if (this._overFolder.elt && this._overFolder.elt.lastChild) {
  1.1337 +      if (!this._overFolder.elt.lastChild.hasAttribute("dragover")) {
  1.1338 +        this._overFolder.elt.lastChild.hidePopup();
  1.1339 +      }
  1.1340 +      this._overFolder.elt.removeAttribute("dragover");
  1.1341 +      this._overFolder.elt = null;
  1.1342 +    }
  1.1343 +    if (this._overFolder.openTimer) {
  1.1344 +      this._overFolder.openTimer.cancel();
  1.1345 +      this._overFolder.openTimer = null;
  1.1346 +    }
  1.1347 +    if (this._overFolder.closeTimer) {
  1.1348 +      this._overFolder.closeTimer.cancel();
  1.1349 +      this._overFolder.closeTimer = null;
  1.1350 +    }
  1.1351 +  },
  1.1352 +
  1.1353 +  /**
  1.1354 +   * This function returns information about where to drop when dragging over
  1.1355 +   * the toolbar.  The returned object has the following properties:
  1.1356 +   * - ip: the insertion point for the bookmarks service.
  1.1357 +   * - beforeIndex: child index to drop before, for the drop indicator.
  1.1358 +   * - folderElt: the folder to drop into, if applicable.
  1.1359 +   */
  1.1360 +  _getDropPoint: function PT__getDropPoint(aEvent) {
  1.1361 +    let result = this.result;
  1.1362 +    if (!PlacesUtils.nodeIsFolder(this._resultNode))
  1.1363 +      return null;
  1.1364 +
  1.1365 +    let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
  1.1366 +    let elt = aEvent.target;
  1.1367 +    if (elt._placesNode && elt != this._rootElt &&
  1.1368 +        elt.localName != "menupopup") {
  1.1369 +      let eltRect = elt.getBoundingClientRect();
  1.1370 +      let eltIndex = Array.indexOf(this._rootElt.childNodes, elt);
  1.1371 +      if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
  1.1372 +          !PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
  1.1373 +        // This is a folder.
  1.1374 +        // If we are in the middle of it, drop inside it.
  1.1375 +        // Otherwise, drop before it, with regards to RTL mode.
  1.1376 +        let threshold = eltRect.width * 0.25;
  1.1377 +        if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold)
  1.1378 +                       : (aEvent.clientX < eltRect.left + threshold)) {
  1.1379 +          // Drop before this folder.
  1.1380 +          dropPoint.ip =
  1.1381 +            new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1.1382 +                               eltIndex, Ci.nsITreeView.DROP_BEFORE);
  1.1383 +          dropPoint.beforeIndex = eltIndex;
  1.1384 +        }
  1.1385 +        else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
  1.1386 +                            : (aEvent.clientX < eltRect.right - threshold)) {
  1.1387 +          // Drop inside this folder.
  1.1388 +          dropPoint.ip =
  1.1389 +            new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode),
  1.1390 +                               -1, Ci.nsITreeView.DROP_ON,
  1.1391 +                               PlacesUtils.nodeIsTagQuery(elt._placesNode));
  1.1392 +          dropPoint.beforeIndex = eltIndex;
  1.1393 +          dropPoint.folderElt = elt;
  1.1394 +        }
  1.1395 +        else {
  1.1396 +          // Drop after this folder.
  1.1397 +          let beforeIndex =
  1.1398 +            (eltIndex == this._rootElt.childNodes.length - 1) ?
  1.1399 +            -1 : eltIndex + 1;
  1.1400 +
  1.1401 +          dropPoint.ip =
  1.1402 +            new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1.1403 +                               beforeIndex, Ci.nsITreeView.DROP_BEFORE);
  1.1404 +          dropPoint.beforeIndex = beforeIndex;
  1.1405 +        }
  1.1406 +      }
  1.1407 +      else {
  1.1408 +        // This is a non-folder node or a read-only folder.
  1.1409 +        // Drop before it with regards to RTL mode.
  1.1410 +        let threshold = eltRect.width * 0.5;
  1.1411 +        if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
  1.1412 +                       : (aEvent.clientX < eltRect.left + threshold)) {
  1.1413 +          // Drop before this bookmark.
  1.1414 +          dropPoint.ip =
  1.1415 +            new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1.1416 +                               eltIndex, Ci.nsITreeView.DROP_BEFORE);
  1.1417 +          dropPoint.beforeIndex = eltIndex;
  1.1418 +        }
  1.1419 +        else {
  1.1420 +          // Drop after this bookmark.
  1.1421 +          let beforeIndex =
  1.1422 +            eltIndex == this._rootElt.childNodes.length - 1 ?
  1.1423 +            -1 : eltIndex + 1;
  1.1424 +          dropPoint.ip =
  1.1425 +            new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1.1426 +                               beforeIndex, Ci.nsITreeView.DROP_BEFORE);
  1.1427 +          dropPoint.beforeIndex = beforeIndex;
  1.1428 +        }
  1.1429 +      }
  1.1430 +    }
  1.1431 +    else {
  1.1432 +      // We are most likely dragging on the empty area of the
  1.1433 +      // toolbar, we should drop after the last node.
  1.1434 +      dropPoint.ip =
  1.1435 +        new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1.1436 +                           -1, Ci.nsITreeView.DROP_BEFORE);
  1.1437 +      dropPoint.beforeIndex = -1;
  1.1438 +    }
  1.1439 +
  1.1440 +    return dropPoint;
  1.1441 +  },
  1.1442 +
  1.1443 +  _setTimer: function PT_setTimer(aTime) {
  1.1444 +    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1.1445 +    timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
  1.1446 +    return timer;
  1.1447 +  },
  1.1448 +
  1.1449 +  notify: function PT_notify(aTimer) {
  1.1450 +    if (aTimer == this._updateChevronTimer) {
  1.1451 +      this._updateChevronTimer = null;
  1.1452 +      this._updateChevronTimerCallback();
  1.1453 +    }
  1.1454 +
  1.1455 +    // * Timer to turn off indicator bar.
  1.1456 +    else if (aTimer == this._ibTimer) {
  1.1457 +      this._dropIndicator.collapsed = true;
  1.1458 +      this._ibTimer = null;
  1.1459 +    }
  1.1460 +
  1.1461 +    // * Timer to open a menubutton that's being dragged over.
  1.1462 +    else if (aTimer == this._overFolder.openTimer) {
  1.1463 +      // Set the autoopen attribute on the folder's menupopup so that
  1.1464 +      // the menu will automatically close when the mouse drags off of it.
  1.1465 +      this._overFolder.elt.lastChild.setAttribute("autoopened", "true");
  1.1466 +      this._overFolder.elt.open = true;
  1.1467 +      this._overFolder.openTimer = null;
  1.1468 +    }
  1.1469 +
  1.1470 +    // * Timer to close a menubutton that's been dragged off of.
  1.1471 +    else if (aTimer == this._overFolder.closeTimer) {
  1.1472 +      // Close the menubutton if we are not dragging over it or one of
  1.1473 +      // its children.  The autoopened attribute will let the menu know to
  1.1474 +      // close later if the menu is still being dragged over.
  1.1475 +      let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget;
  1.1476 +      let inHierarchy = false;
  1.1477 +      while (currentPlacesNode) {
  1.1478 +        if (currentPlacesNode == this._rootElt) {
  1.1479 +          inHierarchy = true;
  1.1480 +          break;
  1.1481 +        }
  1.1482 +        currentPlacesNode = currentPlacesNode.parentNode;
  1.1483 +      }
  1.1484 +      // The _clearOverFolder() function will close the menu for
  1.1485 +      // _overFolder.elt.  So null it out if we don't want to close it.
  1.1486 +      if (inHierarchy)
  1.1487 +        this._overFolder.elt = null;
  1.1488 +
  1.1489 +      // Clear out the folder and all associated timers.
  1.1490 +      this._clearOverFolder();
  1.1491 +    }
  1.1492 +  },
  1.1493 +
  1.1494 +  _onMouseOver: function PT__onMouseOver(aEvent) {
  1.1495 +    let button = aEvent.target;
  1.1496 +    if (button.parentNode == this._rootElt && button._placesNode &&
  1.1497 +        PlacesUtils.nodeIsURI(button._placesNode))
  1.1498 +      window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null);
  1.1499 +  },
  1.1500 +
  1.1501 +  _onMouseOut: function PT__onMouseOut(aEvent) {
  1.1502 +    window.XULBrowserWindow.setOverLink("", null);
  1.1503 +  },
  1.1504 +
  1.1505 +  _cleanupDragDetails: function PT__cleanupDragDetails() {
  1.1506 +    // Called on dragend and drop.
  1.1507 +    PlacesControllerDragHelper.currentDropTarget = null;
  1.1508 +    this._draggedElt = null;
  1.1509 +    if (this._ibTimer)
  1.1510 +      this._ibTimer.cancel();
  1.1511 +
  1.1512 +    this._dropIndicator.collapsed = true;
  1.1513 +  },
  1.1514 +
  1.1515 +  _onDragStart: function PT__onDragStart(aEvent) {
  1.1516 +    // Sub menus have their own d&d handlers.
  1.1517 +    let draggedElt = aEvent.target;
  1.1518 +    if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode)
  1.1519 +      return;
  1.1520 +
  1.1521 +    if (draggedElt.localName == "toolbarbutton" &&
  1.1522 +        draggedElt.getAttribute("type") == "menu") {
  1.1523 +      // If the drag gesture on a container is toward down we open instead
  1.1524 +      // of dragging.
  1.1525 +      let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY;
  1.1526 +      let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX;
  1.1527 +      if ((translateY) >= Math.abs(translateX/2)) {
  1.1528 +        // Don't start the drag.
  1.1529 +        aEvent.preventDefault();
  1.1530 +        // Open the menu.
  1.1531 +        draggedElt.open = true;
  1.1532 +        return;
  1.1533 +      }
  1.1534 +
  1.1535 +      // If the menu is open, close it.
  1.1536 +      if (draggedElt.open) {
  1.1537 +        draggedElt.lastChild.hidePopup();
  1.1538 +        draggedElt.open = false;
  1.1539 +      }
  1.1540 +    }
  1.1541 +
  1.1542 +    // Activate the view and cache the dragged element.
  1.1543 +    this._draggedElt = draggedElt._placesNode;
  1.1544 +    this._rootElt.focus();
  1.1545 +
  1.1546 +    this._controller.setDataTransfer(aEvent);
  1.1547 +    aEvent.stopPropagation();
  1.1548 +  },
  1.1549 +
  1.1550 +  _onDragOver: function PT__onDragOver(aEvent) {
  1.1551 +    // Cache the dataTransfer
  1.1552 +    PlacesControllerDragHelper.currentDropTarget = aEvent.target;
  1.1553 +    let dt = aEvent.dataTransfer;
  1.1554 +
  1.1555 +    let dropPoint = this._getDropPoint(aEvent);
  1.1556 +    if (!dropPoint || !dropPoint.ip ||
  1.1557 +        !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
  1.1558 +      this._dropIndicator.collapsed = true;
  1.1559 +      aEvent.stopPropagation();
  1.1560 +      return;
  1.1561 +    }
  1.1562 +
  1.1563 +    if (this._ibTimer) {
  1.1564 +      this._ibTimer.cancel();
  1.1565 +      this._ibTimer = null;
  1.1566 +    }
  1.1567 +
  1.1568 +    if (dropPoint.folderElt || aEvent.originalTarget == this._chevron) {
  1.1569 +      // Dropping over a menubutton or chevron button.
  1.1570 +      // Set styles and timer to open relative menupopup.
  1.1571 +      let overElt = dropPoint.folderElt || this._chevron;
  1.1572 +      if (this._overFolder.elt != overElt) {
  1.1573 +        this._clearOverFolder();
  1.1574 +        this._overFolder.elt = overElt;
  1.1575 +        this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
  1.1576 +      }
  1.1577 +      if (!this._overFolder.elt.hasAttribute("dragover"))
  1.1578 +        this._overFolder.elt.setAttribute("dragover", "true");
  1.1579 +
  1.1580 +      this._dropIndicator.collapsed = true;
  1.1581 +    }
  1.1582 +    else {
  1.1583 +      // Dragging over a normal toolbarbutton,
  1.1584 +      // show indicator bar and move it to the appropriate drop point.
  1.1585 +      let ind = this._dropIndicator;
  1.1586 +      ind.parentNode.collapsed = false;
  1.1587 +      let halfInd = ind.clientWidth / 2;
  1.1588 +      let translateX;
  1.1589 +      if (this.isRTL) {
  1.1590 +        halfInd = Math.ceil(halfInd);
  1.1591 +        translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd;
  1.1592 +        if (this._rootElt.firstChild) {
  1.1593 +          if (dropPoint.beforeIndex == -1)
  1.1594 +            translateX += this._rootElt.lastChild.getBoundingClientRect().left;
  1.1595 +          else {
  1.1596 +            translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
  1.1597 +                              .getBoundingClientRect().right;
  1.1598 +          }
  1.1599 +        }
  1.1600 +      }
  1.1601 +      else {
  1.1602 +        halfInd = Math.floor(halfInd);
  1.1603 +        translateX = 0 - this._rootElt.getBoundingClientRect().left +
  1.1604 +                     halfInd;
  1.1605 +        if (this._rootElt.firstChild) {
  1.1606 +          if (dropPoint.beforeIndex == -1)
  1.1607 +            translateX += this._rootElt.lastChild.getBoundingClientRect().right;
  1.1608 +          else {
  1.1609 +            translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
  1.1610 +                              .getBoundingClientRect().left;
  1.1611 +          }
  1.1612 +        }
  1.1613 +      }
  1.1614 +
  1.1615 +      ind.style.transform = "translate(" + Math.round(translateX) + "px)";
  1.1616 +      ind.style.MozMarginStart = (-ind.clientWidth) + "px";
  1.1617 +      ind.collapsed = false;
  1.1618 +
  1.1619 +      // Clear out old folder information.
  1.1620 +      this._clearOverFolder();
  1.1621 +    }
  1.1622 +
  1.1623 +    aEvent.preventDefault();
  1.1624 +    aEvent.stopPropagation();
  1.1625 +  },
  1.1626 +
  1.1627 +  _onDrop: function PT__onDrop(aEvent) {
  1.1628 +    PlacesControllerDragHelper.currentDropTarget = aEvent.target;
  1.1629 +
  1.1630 +    let dropPoint = this._getDropPoint(aEvent);
  1.1631 +    if (dropPoint && dropPoint.ip) {
  1.1632 +      PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
  1.1633 +      aEvent.preventDefault();
  1.1634 +    }
  1.1635 +
  1.1636 +    this._cleanupDragDetails();
  1.1637 +    aEvent.stopPropagation();
  1.1638 +  },
  1.1639 +
  1.1640 +  _onDragExit: function PT__onDragExit(aEvent) {
  1.1641 +    PlacesControllerDragHelper.currentDropTarget = null;
  1.1642 +
  1.1643 +    // Set timer to turn off indicator bar (if we turn it off
  1.1644 +    // here, dragenter might be called immediately after, creating
  1.1645 +    // flicker).
  1.1646 +    if (this._ibTimer)
  1.1647 +      this._ibTimer.cancel();
  1.1648 +    this._ibTimer = this._setTimer(10);
  1.1649 +
  1.1650 +    // If we hovered over a folder, close it now.
  1.1651 +    if (this._overFolder.elt)
  1.1652 +        this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
  1.1653 +  },
  1.1654 +
  1.1655 +  _onDragEnd: function PT_onDragEnd(aEvent) {
  1.1656 +    this._cleanupDragDetails();
  1.1657 +  },
  1.1658 +
  1.1659 +  _onPopupShowing: function PT__onPopupShowing(aEvent) {
  1.1660 +    if (!this._allowPopupShowing) {
  1.1661 +      this._allowPopupShowing = true;
  1.1662 +      aEvent.preventDefault();
  1.1663 +      return;
  1.1664 +    }
  1.1665 +
  1.1666 +    let parent = aEvent.target.parentNode;
  1.1667 +    if (parent.localName == "toolbarbutton")
  1.1668 +      this._openedMenuButton = parent;
  1.1669 +
  1.1670 +    PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
  1.1671 +  },
  1.1672 +
  1.1673 +  _onPopupHidden: function PT__onPopupHidden(aEvent) {
  1.1674 +    let popup = aEvent.target;
  1.1675 +    let placesNode = popup._placesNode;
  1.1676 +    // Avoid handling popuphidden of inner views
  1.1677 +    if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
  1.1678 +      // UI performance: folder queries are cheap, keep the resultnode open
  1.1679 +      // so we don't rebuild its contents whenever the popup is reopened.
  1.1680 +      // Though, we want to always close feed containers so their expiration
  1.1681 +      // status will be checked at next opening.
  1.1682 +      if (!PlacesUtils.nodeIsFolder(placesNode) ||
  1.1683 +          this.controller.hasCachedLivemarkInfo(placesNode)) {
  1.1684 +        placesNode.containerOpen = false;
  1.1685 +      }
  1.1686 +    }
  1.1687 +
  1.1688 +    let parent = popup.parentNode;
  1.1689 +    if (parent.localName == "toolbarbutton") {
  1.1690 +      this._openedMenuButton = null;
  1.1691 +      // Clear the dragover attribute if present, if we are dragging into a
  1.1692 +      // folder in the hierachy of current opened popup we don't clear
  1.1693 +      // this attribute on clearOverFolder.  See Notify for closeTimer.
  1.1694 +      if (parent.hasAttribute("dragover"))
  1.1695 +        parent.removeAttribute("dragover");
  1.1696 +    }
  1.1697 +  },
  1.1698 +
  1.1699 +  _onMouseMove: function PT__onMouseMove(aEvent) {
  1.1700 +    // Used in dragStart to prevent dragging folders when dragging down.
  1.1701 +    this._cachedMouseMoveEvent = aEvent;
  1.1702 +
  1.1703 +    if (this._openedMenuButton == null ||
  1.1704 +        PlacesControllerDragHelper.getSession())
  1.1705 +      return;
  1.1706 +
  1.1707 +    let target = aEvent.originalTarget;
  1.1708 +    if (this._openedMenuButton != target &&
  1.1709 +        target.localName == "toolbarbutton" &&
  1.1710 +        target.type == "menu") {
  1.1711 +      this._openedMenuButton.open = false;
  1.1712 +      target.open = true;
  1.1713 +    }
  1.1714 +  }
  1.1715 +};
  1.1716 +
  1.1717 +/**
  1.1718 + * View for Places menus.  This object should be created during the first
  1.1719 + * popupshowing that's dispatched on the menu.
  1.1720 + */
  1.1721 +function PlacesMenu(aPopupShowingEvent, aPlace, aOptions) {
  1.1722 +  this._rootElt = aPopupShowingEvent.target; // <menupopup>
  1.1723 +  this._viewElt = this._rootElt.parentNode;   // <menu>
  1.1724 +  this._viewElt._placesView = this;
  1.1725 +  this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
  1.1726 +  this._addEventListeners(window, ["unload"], false);
  1.1727 +
  1.1728 +#ifdef XP_MACOSX
  1.1729 +  // Must walk up to support views in sub-menus, like Bookmarks Toolbar menu.
  1.1730 +  for (let elt = this._viewElt.parentNode; elt; elt = elt.parentNode) {
  1.1731 +    if (elt.localName == "menubar") {
  1.1732 +      this._nativeView = true;
  1.1733 +      break;
  1.1734 +    }
  1.1735 +  }
  1.1736 +#endif
  1.1737 +
  1.1738 +  PlacesViewBase.call(this, aPlace, aOptions);
  1.1739 +  this._onPopupShowing(aPopupShowingEvent);
  1.1740 +}
  1.1741 +
  1.1742 +PlacesMenu.prototype = {
  1.1743 +  __proto__: PlacesViewBase.prototype,
  1.1744 +
  1.1745 +  QueryInterface: function PM_QueryInterface(aIID) {
  1.1746 +    if (aIID.equals(Ci.nsIDOMEventListener))
  1.1747 +      return this;
  1.1748 +
  1.1749 +    return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
  1.1750 +  },
  1.1751 +
  1.1752 +  _removeChild: function PM_removeChild(aChild) {
  1.1753 +    PlacesViewBase.prototype._removeChild.apply(this, arguments);
  1.1754 +  },
  1.1755 +
  1.1756 +  uninit: function PM_uninit() {
  1.1757 +    this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
  1.1758 +                               true);
  1.1759 +    this._removeEventListeners(window, ["unload"], false);
  1.1760 +
  1.1761 +    PlacesViewBase.prototype.uninit.apply(this, arguments);
  1.1762 +  },
  1.1763 +
  1.1764 +  handleEvent: function PM_handleEvent(aEvent) {
  1.1765 +    switch (aEvent.type) {
  1.1766 +      case "unload":
  1.1767 +        this.uninit();
  1.1768 +        break;
  1.1769 +      case "popupshowing":
  1.1770 +        this._onPopupShowing(aEvent);
  1.1771 +        break;
  1.1772 +      case "popuphidden":
  1.1773 +        this._onPopupHidden(aEvent);
  1.1774 +        break;
  1.1775 +    }
  1.1776 +  },
  1.1777 +
  1.1778 +  _onPopupHidden: function PM__onPopupHidden(aEvent) {
  1.1779 +    // Avoid handling popuphidden of inner views.
  1.1780 +    let popup = aEvent.originalTarget;
  1.1781 +    let placesNode = popup._placesNode;
  1.1782 +    if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
  1.1783 +      return;
  1.1784 +
  1.1785 +    // UI performance: folder queries are cheap, keep the resultnode open
  1.1786 +    // so we don't rebuild its contents whenever the popup is reopened.
  1.1787 +    // Though, we want to always close feed containers so their expiration
  1.1788 +    // status will be checked at next opening.
  1.1789 +    if (!PlacesUtils.nodeIsFolder(placesNode) ||
  1.1790 +        this.controller.hasCachedLivemarkInfo(placesNode))
  1.1791 +      placesNode.containerOpen = false;
  1.1792 +
  1.1793 +    // The autoopened attribute is set for folders which have been
  1.1794 +    // automatically opened when dragged over.  Turn off this attribute
  1.1795 +    // when the folder closes because it is no longer applicable.
  1.1796 +    popup.removeAttribute("autoopened");
  1.1797 +    popup.removeAttribute("dragstart");
  1.1798 +  }
  1.1799 +};
  1.1800 +
  1.1801 +function PlacesPanelMenuView(aPlace, aViewId, aRootId, aOptions) {
  1.1802 +  this._viewElt = document.getElementById(aViewId);
  1.1803 +  this._rootElt = document.getElementById(aRootId);
  1.1804 +  this._viewElt._placesView = this;
  1.1805 +  this.options = aOptions;
  1.1806 +
  1.1807 +  PlacesViewBase.call(this, aPlace, aOptions);
  1.1808 +}
  1.1809 +
  1.1810 +PlacesPanelMenuView.prototype = {
  1.1811 +  __proto__: PlacesViewBase.prototype,
  1.1812 +
  1.1813 +  QueryInterface: function PAMV_QueryInterface(aIID) {
  1.1814 +    return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
  1.1815 +  },
  1.1816 +
  1.1817 +  uninit: function PAMV_uninit() {
  1.1818 +    PlacesViewBase.prototype.uninit.apply(this, arguments);
  1.1819 +  },
  1.1820 +
  1.1821 +  _insertNewItem:
  1.1822 +  function PAMV__insertNewItem(aChild, aBefore) {
  1.1823 +    this._domNodes.delete(aChild);
  1.1824 +
  1.1825 +    let type = aChild.type;
  1.1826 +    let button;
  1.1827 +    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
  1.1828 +      button = document.createElement("toolbarseparator");
  1.1829 +      button.setAttribute("class", "small-separator");
  1.1830 +    }
  1.1831 +    else {
  1.1832 +      button = document.createElement("toolbarbutton");
  1.1833 +      button.className = "bookmark-item";
  1.1834 +      if (typeof this.options.extraClasses.entry == "string")
  1.1835 +        button.classList.add(this.options.extraClasses.entry);
  1.1836 +      button.setAttribute("label", aChild.title);
  1.1837 +      let icon = aChild.icon;
  1.1838 +      if (icon)
  1.1839 +        button.setAttribute("image", icon);
  1.1840 +
  1.1841 +      if (PlacesUtils.containerTypes.indexOf(type) != -1) {
  1.1842 +        button.setAttribute("container", "true");
  1.1843 +
  1.1844 +        if (PlacesUtils.nodeIsQuery(aChild)) {
  1.1845 +          button.setAttribute("query", "true");
  1.1846 +          if (PlacesUtils.nodeIsTagQuery(aChild))
  1.1847 +            button.setAttribute("tagContainer", "true");
  1.1848 +        }
  1.1849 +        else if (PlacesUtils.nodeIsFolder(aChild)) {
  1.1850 +          PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
  1.1851 +            .then(aLivemark => {
  1.1852 +              button.setAttribute("livemark", "true");
  1.1853 +              this.controller.cacheLivemarkInfo(aChild, aLivemark);
  1.1854 +            }, () => undefined);
  1.1855 +        }
  1.1856 +      }
  1.1857 +      else if (PlacesUtils.nodeIsURI(aChild)) {
  1.1858 +        button.setAttribute("scheme",
  1.1859 +                            PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
  1.1860 +      }
  1.1861 +    }
  1.1862 +
  1.1863 +    button._placesNode = aChild;
  1.1864 +    if (!this._domNodes.has(aChild))
  1.1865 +      this._domNodes.set(aChild, button);
  1.1866 +
  1.1867 +    this._rootElt.insertBefore(button, aBefore);
  1.1868 +  },
  1.1869 +
  1.1870 +  nodeInserted:
  1.1871 +  function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
  1.1872 +    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1.1873 +    if (parentElt != this._rootElt)
  1.1874 +      return;
  1.1875 +
  1.1876 +    let children = this._rootElt.childNodes;
  1.1877 +    this._insertNewItem(aPlacesNode,
  1.1878 +      aIndex < children.length ? children[aIndex] : null);
  1.1879 +  },
  1.1880 +
  1.1881 +  nodeRemoved:
  1.1882 +  function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
  1.1883 +    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1.1884 +    if (parentElt != this._rootElt)
  1.1885 +      return;
  1.1886 +
  1.1887 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1888 +    this._removeChild(elt);
  1.1889 +  },
  1.1890 +
  1.1891 +  nodeMoved:
  1.1892 +  function PAMV_nodeMoved(aPlacesNode,
  1.1893 +                          aOldParentPlacesNode, aOldIndex,
  1.1894 +                          aNewParentPlacesNode, aNewIndex) {
  1.1895 +    let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
  1.1896 +    if (parentElt != this._rootElt)
  1.1897 +      return;
  1.1898 +
  1.1899 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1900 +    this._removeChild(elt);
  1.1901 +    this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
  1.1902 +  },
  1.1903 +
  1.1904 +  nodeAnnotationChanged:
  1.1905 +  function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) {
  1.1906 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1907 +    // There's no UI representation for the root node.
  1.1908 +    if (elt == this._rootElt)
  1.1909 +      return;
  1.1910 +
  1.1911 +    if (elt.parentNode != this._rootElt)
  1.1912 +      return;
  1.1913 +
  1.1914 +    // All livemarks have a feedURI, so use it as our indicator.
  1.1915 +    if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
  1.1916 +      elt.setAttribute("livemark", true);
  1.1917 +
  1.1918 +      PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
  1.1919 +        .then(aLivemark => {
  1.1920 +          this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
  1.1921 +          this.invalidateContainer(aPlacesNode);
  1.1922 +        }, Components.utils.reportError);
  1.1923 +    }
  1.1924 +  },
  1.1925 +
  1.1926 +  nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) {
  1.1927 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1928 +
  1.1929 +    // There's no UI representation for the root node.
  1.1930 +    if (elt == this._rootElt)
  1.1931 +      return;
  1.1932 +
  1.1933 +    PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
  1.1934 +  },
  1.1935 +
  1.1936 +  invalidateContainer: function PAMV_invalidateContainer(aPlacesNode) {
  1.1937 +    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1.1938 +    if (elt != this._rootElt)
  1.1939 +      return;
  1.1940 +
  1.1941 +    // Container is the toolbar itself.
  1.1942 +    while (this._rootElt.hasChildNodes()) {
  1.1943 +      this._rootElt.removeChild(this._rootElt.firstChild);
  1.1944 +    }
  1.1945 +
  1.1946 +    for (let i = 0; i < this._resultNode.childCount; ++i) {
  1.1947 +      this._insertNewItem(this._resultNode.getChild(i), null);
  1.1948 +    }
  1.1949 +  }
  1.1950 +};

mercurial