browser/components/places/content/browserPlacesViews.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     6 Components.utils.import("resource://gre/modules/Services.jsm");
     8 /**
     9  * The base view implements everything that's common to the toolbar and
    10  * menu views.
    11  */
    12 function PlacesViewBase(aPlace, aOptions) {
    13   this.place = aPlace;
    14   this.options = aOptions;
    15   this._controller = new PlacesController(this);
    16   this._viewElt.controllers.appendController(this._controller);
    17 }
    19 PlacesViewBase.prototype = {
    20   // The xul element that holds the entire view.
    21   _viewElt: null,
    22   get viewElt() this._viewElt,
    24   get associatedElement() this._viewElt,
    26   get controllers() this._viewElt.controllers,
    28   // The xul element that represents the root container.
    29   _rootElt: null,
    31   // Set to true for views that are represented by native widgets (i.e.
    32   // the native mac menu).
    33   _nativeView: false,
    35   QueryInterface: XPCOMUtils.generateQI(
    36     [Components.interfaces.nsINavHistoryResultObserver,
    37      Components.interfaces.nsISupportsWeakReference]),
    39   _place: "",
    40   get place() this._place,
    41   set place(val) {
    42     this._place = val;
    44     let history = PlacesUtils.history;
    45     let queries = { }, options = { };
    46     history.queryStringToQueries(val, queries, { }, options);
    47     if (!queries.value.length)
    48       queries.value = [history.getNewQuery()];
    50     let result = history.executeQueries(queries.value, queries.value.length,
    51                                         options.value);
    52     result.addObserver(this, false);
    53     return val;
    54   },
    56   _result: null,
    57   get result() this._result,
    58   set result(val) {
    59     if (this._result == val)
    60       return val;
    62     if (this._result) {
    63       this._result.removeObserver(this);
    64       this._resultNode.containerOpen = false;
    65     }
    67     if (this._rootElt.localName == "menupopup")
    68       this._rootElt._built = false;
    70     this._result = val;
    71     if (val) {
    72       this._resultNode = val.root;
    73       this._rootElt._placesNode = this._resultNode;
    74       this._domNodes = new Map();
    75       this._domNodes.set(this._resultNode, this._rootElt);
    77       // This calls _rebuild through invalidateContainer.
    78       this._resultNode.containerOpen = true;
    79     }
    80     else {
    81       this._resultNode = null;
    82       delete this._domNodes;
    83     }
    85     return val;
    86   },
    88   _options: null,
    89   get options() this._options,
    90   set options(val) {
    91     if (!val)
    92       val = {};
    94     if (!("extraClasses" in val))
    95       val.extraClasses = {};
    96     this._options = val;
    98     return val;
    99   },
   101   /**
   102    * Gets the DOM node used for the given places node.
   103    *
   104    * @param aPlacesNode
   105    *        a places result node.
   106    * @throws if there is no DOM node set for aPlacesNode.
   107    */
   108   _getDOMNodeForPlacesNode:
   109   function PVB__getDOMNodeForPlacesNode(aPlacesNode) {
   110     let node = this._domNodes.get(aPlacesNode, null);
   111     if (!node) {
   112       throw new Error("No DOM node set for aPlacesNode.\nnode.type: " +
   113                       aPlacesNode.type + ". node.parent: " + aPlacesNode);
   114     }
   115     return node;
   116   },
   118   get controller() this._controller,
   120   get selType() "single",
   121   selectItems: function() { },
   122   selectAll: function() { },
   124   get selectedNode() {
   125     if (this._contextMenuShown) {
   126       let popup = document.popupNode;
   127       return popup._placesNode || popup.parentNode._placesNode || null;
   128     }
   129     return null;
   130   },
   132   get hasSelection() this.selectedNode != null,
   134   get selectedNodes() {
   135     let selectedNode = this.selectedNode;
   136     return selectedNode ? [selectedNode] : [];
   137   },
   139   get removableSelectionRanges() {
   140     // On static content the current selectedNode would be the selection's
   141     // parent node. We don't want to allow removing a node when the
   142     // selection is not explicit.
   143     if (document.popupNode &&
   144         (document.popupNode == "menupopup" || !document.popupNode._placesNode))
   145       return [];
   147     return [this.selectedNodes];
   148   },
   150   get draggableSelection() [this._draggedElt],
   152   get insertionPoint() {
   153     // There is no insertion point for history queries, so bail out now and
   154     // save a lot of work when updating commands.
   155     let resultNode = this._resultNode;
   156     if (PlacesUtils.nodeIsQuery(resultNode) &&
   157         PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
   158           Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
   159       return null;
   161     // By default, the insertion point is at the top level, at the end.
   162     let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
   163     let container = this._resultNode;
   164     let orientation = Ci.nsITreeView.DROP_BEFORE;
   165     let isTag = false;
   167     let selectedNode = this.selectedNode;
   168     if (selectedNode) {
   169       let popup = document.popupNode;
   170       if (!popup._placesNode || popup._placesNode == this._resultNode ||
   171           popup._placesNode.itemId == -1) {
   172         // If a static menuitem is selected, or if the root node is selected,
   173         // the insertion point is inside the folder, at the end.
   174         container = selectedNode;
   175         orientation = Ci.nsITreeView.DROP_ON;
   176       }
   177       else {
   178         // In all other cases the insertion point is before that node.
   179         container = selectedNode.parent;
   180         index = container.getChildIndex(selectedNode);
   181         isTag = PlacesUtils.nodeIsTagQuery(container);
   182       }
   183     }
   185     if (PlacesControllerDragHelper.disallowInsertion(container))
   186       return null;
   188     return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
   189                               index, orientation, isTag);
   190   },
   192   buildContextMenu: function PVB_buildContextMenu(aPopup) {
   193     this._contextMenuShown = true;
   194     window.updateCommands("places");
   195     return this.controller.buildContextMenu(aPopup);
   196   },
   198   destroyContextMenu: function PVB_destroyContextMenu(aPopup) {
   199     this._contextMenuShown = false;
   200   },
   202   _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) {
   203     // Remove Places nodes from the popup.
   204     let child = aPopup._startMarker;
   205     while (child.nextSibling != aPopup._endMarker) {
   206       let sibling = child.nextSibling;
   207       if (sibling._placesNode && !aDelay) {
   208         aPopup.removeChild(sibling);
   209       }
   210       else if (sibling._placesNode && aDelay) {
   211         // HACK (bug 733419): the popups originating from the OS X native
   212         // menubar don't live-update while open, thus we don't clean it
   213         // until the next popupshowing, to avoid zombie menuitems.
   214         if (!aPopup._delayedRemovals)
   215           aPopup._delayedRemovals = [];
   216         aPopup._delayedRemovals.push(sibling);
   217         child = child.nextSibling;
   218       }
   219       else {
   220         child = child.nextSibling;
   221       }
   222     }
   223   },
   225   _rebuildPopup: function PVB__rebuildPopup(aPopup) {
   226     let resultNode = aPopup._placesNode;
   227     if (!resultNode.containerOpen)
   228       return;
   230     if (this.controller.hasCachedLivemarkInfo(resultNode)) {
   231       this._setEmptyPopupStatus(aPopup, false);
   232       aPopup._built = true;
   233       this._populateLivemarkPopup(aPopup);
   234       return;
   235     }
   237     this._cleanPopup(aPopup);
   239     let cc = resultNode.childCount;
   240     if (cc > 0) {
   241       this._setEmptyPopupStatus(aPopup, false);
   243       for (let i = 0; i < cc; ++i) {
   244         let child = resultNode.getChild(i);
   245         this._insertNewItemToPopup(child, aPopup, null);
   246       }
   247     }
   248     else {
   249       this._setEmptyPopupStatus(aPopup, true);
   250     }
   251     aPopup._built = true;
   252   },
   254   _removeChild: function PVB__removeChild(aChild) {
   255     // If document.popupNode pointed to this child, null it out,
   256     // otherwise controller's command-updating may rely on the removed
   257     // item still being "selected".
   258     if (document.popupNode == aChild)
   259       document.popupNode = null;
   261     aChild.parentNode.removeChild(aChild);
   262   },
   264   _setEmptyPopupStatus:
   265   function PVB__setEmptyPopupStatus(aPopup, aEmpty) {
   266     if (!aPopup._emptyMenuitem) {
   267       let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
   268       aPopup._emptyMenuitem = document.createElement("menuitem");
   269       aPopup._emptyMenuitem.setAttribute("label", label);
   270       aPopup._emptyMenuitem.setAttribute("disabled", true);
   271       aPopup._emptyMenuitem.className = "bookmark-item";
   272       if (typeof this.options.extraClasses.entry == "string")
   273         aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry);
   274     }
   276     if (aEmpty) {
   277       aPopup.setAttribute("emptyplacesresult", "true");
   278       // Don't add the menuitem if there is static content.
   279       if (!aPopup._startMarker.previousSibling &&
   280           !aPopup._endMarker.nextSibling)
   281         aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker);
   282     }
   283     else {
   284       aPopup.removeAttribute("emptyplacesresult");
   285       try {
   286         aPopup.removeChild(aPopup._emptyMenuitem);
   287       } catch (ex) {}
   288     }
   289   },
   291   _createMenuItemForPlacesNode:
   292   function PVB__createMenuItemForPlacesNode(aPlacesNode) {
   293     this._domNodes.delete(aPlacesNode);
   295     let element;
   296     let type = aPlacesNode.type;
   297     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
   298       element = document.createElement("menuseparator");
   299       element.setAttribute("class", "small-separator");
   300     }
   301     else {
   302       let itemId = aPlacesNode.itemId;
   303       if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
   304         element = document.createElement("menuitem");
   305         element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
   306         element.setAttribute("scheme",
   307                              PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
   308       }
   309       else if (PlacesUtils.containerTypes.indexOf(type) != -1) {
   310         element = document.createElement("menu");
   311         element.setAttribute("container", "true");
   313         if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
   314           element.setAttribute("query", "true");
   315           if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
   316             element.setAttribute("tagContainer", "true");
   317           else if (PlacesUtils.nodeIsDay(aPlacesNode))
   318             element.setAttribute("dayContainer", "true");
   319           else if (PlacesUtils.nodeIsHost(aPlacesNode))
   320             element.setAttribute("hostContainer", "true");
   321         }
   322         else if (itemId != -1) {
   323           PlacesUtils.livemarks.getLivemark({ id: itemId })
   324             .then(aLivemark => {
   325               element.setAttribute("livemark", "true");
   326 #ifdef XP_MACOSX
   327               // OS X native menubar doesn't track list-style-images since
   328               // it doesn't have a frame (bug 733415).  Thus enforce updating.
   329               element.setAttribute("image", "");
   330               element.removeAttribute("image");
   331 #endif
   332               this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
   333             }, () => undefined);
   334         }
   336         let popup = document.createElement("menupopup");
   337         popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
   339         if (!this._nativeView) {
   340           popup.setAttribute("placespopup", "true");
   341         }
   343         element.appendChild(popup);
   344         element.className = "menu-iconic bookmark-item";
   345         if (typeof this.options.extraClasses.entry == "string") {
   346           element.classList.add(this.options.extraClasses.entry);
   347         }
   349         this._domNodes.set(aPlacesNode, popup);
   350       }
   351       else
   352         throw "Unexpected node";
   354       element.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
   356       let icon = aPlacesNode.icon;
   357       if (icon)
   358         element.setAttribute("image", icon);
   359     }
   361     element._placesNode = aPlacesNode;
   362     if (!this._domNodes.has(aPlacesNode))
   363       this._domNodes.set(aPlacesNode, element);
   365     return element;
   366   },
   368   _insertNewItemToPopup:
   369   function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) {
   370     let element = this._createMenuItemForPlacesNode(aNewChild);
   371     let before = aBefore || aPopup._endMarker;
   373     if (element.localName == "menuitem" || element.localName == "menu") {
   374       if (typeof this.options.extraClasses.entry == "string")
   375         element.classList.add(this.options.extraClasses.entry);
   376     }
   378     aPopup.insertBefore(element, before);
   379     return element;
   380   },
   382   _setLivemarkSiteURIMenuItem:
   383   function PVB__setLivemarkSiteURIMenuItem(aPopup) {
   384     let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
   385     let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
   386                   livemarkInfo.siteURI.spec : null;
   387     if (!siteUrl && aPopup._siteURIMenuitem) {
   388       aPopup.removeChild(aPopup._siteURIMenuitem);
   389       aPopup._siteURIMenuitem = null;
   390       aPopup.removeChild(aPopup._siteURIMenuseparator);
   391       aPopup._siteURIMenuseparator = null;
   392     }
   393     else if (siteUrl && !aPopup._siteURIMenuitem) {
   394       // Add "Open (Feed Name)" menuitem.
   395       aPopup._siteURIMenuitem = document.createElement("menuitem");
   396       aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
   397       if (typeof this.options.extraClasses.entry == "string") {
   398         aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
   399       }
   400       aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
   401       aPopup._siteURIMenuitem.setAttribute("oncommand",
   402         "openUILink(this.getAttribute('targetURI'), event);");
   404       // If a user middle-clicks this item we serve the oncommand event.
   405       // We are using checkForMiddleClick because of Bug 246720.
   406       // Note: stopPropagation is needed to avoid serving middle-click
   407       // with BT_onClick that would open all items in tabs.
   408       aPopup._siteURIMenuitem.setAttribute("onclick",
   409         "checkForMiddleClick(this, event); event.stopPropagation();");
   410       let label =
   411         PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
   412                                          [aPopup.parentNode.getAttribute("label")])
   413       aPopup._siteURIMenuitem.setAttribute("label", label);
   414       aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
   416       aPopup._siteURIMenuseparator = document.createElement("menuseparator");
   417       aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
   418     }
   419   },
   421   /**
   422    * Add, update or remove the livemark status menuitem.
   423    * @param aPopup
   424    *        The livemark container popup
   425    * @param aStatus
   426    *        The livemark status
   427    */
   428   _setLivemarkStatusMenuItem:
   429   function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
   430     let statusMenuitem = aPopup._statusMenuitem;
   431     if (!statusMenuitem) {
   432       // Create the status menuitem and cache it in the popup object.
   433       statusMenuitem = document.createElement("menuitem");
   434       statusMenuitem.className = "livemarkstatus-menuitem";
   435       if (typeof this.options.extraClasses.entry == "string") {
   436         statusMenuitem.classList.add(this.options.extraClasses.entry);
   437       }
   438       statusMenuitem.setAttribute("disabled", true);
   439       aPopup._statusMenuitem = statusMenuitem;
   440     }
   442     if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
   443         aStatus == Ci.mozILivemark.STATUS_FAILED) {
   444       // Status has changed, update the cached status menuitem.
   445       let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
   446                        "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
   447       statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
   448       if (aPopup._startMarker.nextSibling != statusMenuitem)
   449         aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
   450     }
   451     else {
   452       // The livemark has finished loading.
   453       if (aPopup._statusMenuitem.parentNode == aPopup)
   454         aPopup.removeChild(aPopup._statusMenuitem);
   455     }
   456   },
   458   toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
   459     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   461     // We may get the popup for menus, but we need the menu itself.
   462     if (elt.localName == "menupopup")
   463       elt = elt.parentNode;
   464     if (aValue)
   465       elt.setAttribute("cutting", "true");
   466     else
   467       elt.removeAttribute("cutting");
   468   },
   470   nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) {
   471     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   473     // Here we need the <menu>.
   474     if (elt.localName == "menupopup")
   475       elt = elt.parentNode;
   477     elt.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aURIString));
   478   },
   480   nodeIconChanged: function PVB_nodeIconChanged(aPlacesNode) {
   481     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   483     // There's no UI representation for the root node, thus there's nothing to
   484     // be done when the icon changes.
   485     if (elt == this._rootElt)
   486       return;
   488     // Here we need the <menu>.
   489     if (elt.localName == "menupopup")
   490       elt = elt.parentNode;
   492     let icon = aPlacesNode.icon;
   493     if (!icon)
   494       elt.removeAttribute("image");
   495     else if (icon != elt.getAttribute("image"))
   496       elt.setAttribute("image", icon);
   497   },
   499   nodeAnnotationChanged:
   500   function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
   501     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   503     // All livemarks have a feedURI, so use it as our indicator of a livemark
   504     // being modified.
   505     if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
   506       let menu = elt.parentNode;
   507       if (!menu.hasAttribute("livemark")) {
   508         menu.setAttribute("livemark", "true");
   509 #ifdef XP_MACOSX
   510         // OS X native menubar doesn't track list-style-images since
   511         // it doesn't have a frame (bug 733415).  Thus enforce updating.
   512         menu.setAttribute("image", "");
   513         menu.removeAttribute("image");
   514 #endif
   515       }
   517       PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
   518         .then(aLivemark => {
   519           // Controller will use this to build the meta data for the node.
   520           this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
   521           this.invalidateContainer(aPlacesNode);
   522         }, () => undefined);
   523     }
   524   },
   526   nodeTitleChanged:
   527   function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
   528     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   530     // There's no UI representation for the root node, thus there's
   531     // nothing to be done when the title changes.
   532     if (elt == this._rootElt)
   533       return;
   535     // Here we need the <menu>.
   536     if (elt.localName == "menupopup")
   537       elt = elt.parentNode;
   539     if (!aNewTitle && elt.localName != "toolbarbutton") {
   540       // Many users consider toolbars as shortcuts containers, so explicitly
   541       // allow empty labels on toolbarbuttons.  For any other element try to be
   542       // smarter, guessing a title from the uri.
   543       elt.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
   544     }
   545     else {
   546       elt.setAttribute("label", aNewTitle);
   547     }
   548   },
   550   nodeRemoved:
   551   function PVB_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
   552     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
   553     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   555     // Here we need the <menu>.
   556     if (elt.localName == "menupopup")
   557       elt = elt.parentNode;
   559     if (parentElt._built) {
   560       parentElt.removeChild(elt);
   562       // Figure out if we need to show the "<Empty>" menu-item.
   563       // TODO Bug 517701: This doesn't seem to handle the case of an empty
   564       // root.
   565       if (parentElt._startMarker.nextSibling == parentElt._endMarker)
   566         this._setEmptyPopupStatus(parentElt, true);
   567     }
   568   },
   570   nodeHistoryDetailsChanged:
   571   function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
   572     if (aPlacesNode.parent &&
   573         this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
   574       // Find the node in the parent.
   575       let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
   576       for (let child = popup._startMarker.nextSibling;
   577            child != popup._endMarker;
   578            child = child.nextSibling) {
   579         if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
   580           if (aCount)
   581             child.setAttribute("visited", "true");
   582           else
   583             child.removeAttribute("visited");
   584           break;
   585         }
   586       }
   587     }
   588   },
   590   nodeTagsChanged: function() { },
   591   nodeDateAddedChanged: function() { },
   592   nodeLastModifiedChanged: function() { },
   593   nodeKeywordChanged: function() { },
   594   sortingChanged: function() { },
   595   batching: function() { },
   597   nodeInserted:
   598   function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
   599     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
   600     if (!parentElt._built)
   601       return;
   603     let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) +
   604                 aIndex + 1;
   605     this._insertNewItemToPopup(aPlacesNode, parentElt,
   606                                parentElt.childNodes[index]);
   607     this._setEmptyPopupStatus(parentElt, false);
   608   },
   610   nodeMoved:
   611   function PBV_nodeMoved(aPlacesNode,
   612                          aOldParentPlacesNode, aOldIndex,
   613                          aNewParentPlacesNode, aNewIndex) {
   614     // Note: the current implementation of moveItem does not actually
   615     // use this notification when the item in question is moved from one
   616     // folder to another.  Instead, it calls nodeRemoved and nodeInserted
   617     // for the two folders.  Thus, we can assume old-parent == new-parent.
   618     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   620     // Here we need the <menu>.
   621     if (elt.localName == "menupopup")
   622       elt = elt.parentNode;
   624     // If our root node is a folder, it might be moved. There's nothing
   625     // we need to do in that case.
   626     if (elt == this._rootElt)
   627       return;
   629     let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
   630     if (parentElt._built) {
   631       // Move the node.
   632       parentElt.removeChild(elt);
   633       let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) +
   634                   aNewIndex + 1;
   635       parentElt.insertBefore(elt, parentElt.childNodes[index]);
   636     }
   637   },
   639   containerStateChanged:
   640   function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
   641     if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
   642         aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
   643       this.invalidateContainer(aPlacesNode);
   645       if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
   646         let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
   647         if (queryOptions.excludeItems) {
   648           return;
   649         }
   651         PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
   652           .then(aLivemark => {
   653             let shouldInvalidate =
   654               !this.controller.hasCachedLivemarkInfo(aPlacesNode);
   655             this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
   656             if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
   657               aLivemark.registerForUpdates(aPlacesNode, this);
   658               // Prioritize the current livemark.
   659               aLivemark.reload();
   660               PlacesUtils.livemarks.reloadLivemarks();
   661               if (shouldInvalidate)
   662                 this.invalidateContainer(aPlacesNode);
   663             }
   664             else {
   665               aLivemark.unregisterForUpdates(aPlacesNode);
   666             }
   667           }, () => undefined);
   668       }
   669     }
   670   },
   672   _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup)
   673   {
   674     this._setLivemarkSiteURIMenuItem(aPopup);
   675     // Show the loading status only if there are no entries yet.
   676     if (aPopup._startMarker.nextSibling == aPopup._endMarker)
   677       this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
   679     PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
   680       .then(aLivemark => {
   681         let placesNode = aPopup._placesNode;
   682         if (!placesNode.containerOpen)
   683           return;
   685         if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
   686           this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
   687         this._cleanPopup(aPopup,
   688           this._nativeView && aPopup.parentNode.hasAttribute("open"));
   690         let children = aLivemark.getNodesForContainer(placesNode);
   691         for (let i = 0; i < children.length; i++) {
   692           let child = children[i];
   693           this.nodeInserted(placesNode, child, i);
   694           if (child.accessCount)
   695             this._getDOMNodeForPlacesNode(child).setAttribute("visited", true);
   696           else
   697             this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
   698         }
   699       }, Components.utils.reportError);
   700   },
   702   invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
   703     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
   704     elt._built = false;
   706     // If the menupopup is open we should live-update it.
   707     if (elt.parentNode.open)
   708       this._rebuildPopup(elt);
   709   },
   711   uninit: function PVB_uninit() {
   712     if (this._result) {
   713       this._result.removeObserver(this);
   714       this._resultNode.containerOpen = false;
   715       this._resultNode = null;
   716       this._result = null;
   717     }
   719     if (this._controller) {
   720       this._controller.terminate();
   721       // Removing the controller will fail if it is already no longer there.
   722       // This can happen if the view element was removed/reinserted without
   723       // our knowledge. There is no way to check for that having happened
   724       // without the possibility of an exception. :-(
   725       try {
   726         this._viewElt.controllers.removeController(this._controller);
   727       } catch (ex) {
   728       } finally {
   729         this._controller = null;
   730       }
   731     }
   733     delete this._viewElt._placesView;
   734   },
   736   get isRTL() {
   737     if ("_isRTL" in this)
   738       return this._isRTL;
   740     return this._isRTL = document.defaultView
   741                                  .getComputedStyle(this.viewElt, "")
   742                                  .direction == "rtl";
   743   },
   745   get ownerWindow() window,
   747   /**
   748    * Adds an "Open All in Tabs" menuitem to the bottom of the popup.
   749    * @param aPopup
   750    *        a Places popup.
   751    */
   752   _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) {
   753     // The command items are never added to the root popup.
   754     if (aPopup == this._rootElt)
   755       return;
   757     let hasMultipleURIs = false;
   759     // Check if the popup contains at least 2 menuitems with places nodes.
   760     // We don't currently support opening multiple uri nodes when they are not
   761     // populated by the result.
   762     if (aPopup._placesNode.childCount > 0) {
   763       let currentChild = aPopup.firstChild;
   764       let numURINodes = 0;
   765       while (currentChild) {
   766         if (currentChild.localName == "menuitem" && currentChild._placesNode) {
   767           if (++numURINodes == 2)
   768             break;
   769         }
   770         currentChild = currentChild.nextSibling;
   771       }
   772       hasMultipleURIs = numURINodes > 1;
   773     }
   775     if (!hasMultipleURIs) {
   776       aPopup.setAttribute("singleitempopup", "true");
   777     } else {
   778       aPopup.removeAttribute("singleitempopup");
   779     }
   781     if (!hasMultipleURIs) {
   782       // We don't have to show any option.
   783       if (aPopup._endOptOpenAllInTabs) {
   784         aPopup.removeChild(aPopup._endOptOpenAllInTabs);
   785         aPopup._endOptOpenAllInTabs = null;
   787         aPopup.removeChild(aPopup._endOptSeparator);
   788         aPopup._endOptSeparator = null;
   789       }
   790     }
   791     else if (!aPopup._endOptOpenAllInTabs) {
   792       // Create a separator before options.
   793       aPopup._endOptSeparator = document.createElement("menuseparator");
   794       aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
   795       aPopup.appendChild(aPopup._endOptSeparator);
   797       // Add the "Open All in Tabs" menuitem.
   798       aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
   799       aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
   801       if (typeof this.options.extraClasses.entry == "string")
   802         aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry);
   803       if (typeof this.options.extraClasses.footer == "string")
   804         aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
   806       aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
   807         "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
   808                                                "PlacesUIUtils.getViewForNode(this));");
   809       aPopup._endOptOpenAllInTabs.setAttribute("onclick",
   810         "checkForMiddleClick(this, event); event.stopPropagation();");
   811       aPopup._endOptOpenAllInTabs.setAttribute("label",
   812         gNavigatorBundle.getString("menuOpenAllInTabs.label"));
   813       aPopup.appendChild(aPopup._endOptOpenAllInTabs);
   814     }
   815   },
   817   _ensureMarkers: function PVB__ensureMarkers(aPopup) {
   818     if (aPopup._startMarker)
   819       return;
   821     // _startMarker is an hidden menuseparator that lives before places nodes.
   822     aPopup._startMarker = document.createElement("menuseparator");
   823     aPopup._startMarker.hidden = true;
   824     aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild);
   826     // _endMarker is a DOM node that lives after places nodes, specified with
   827     // the 'insertionPoint' option or will be a hidden menuseparator.
   828     let node = ("insertionPoint" in this.options) ?
   829                aPopup.querySelector(this.options.insertionPoint) : null;
   830     if (node) {
   831       aPopup._endMarker = node;
   832     } else {
   833       aPopup._endMarker = document.createElement("menuseparator");
   834       aPopup._endMarker.hidden = true;
   835     }
   836     aPopup.appendChild(aPopup._endMarker);
   838     // Move the markers to the right position.
   839     let firstNonStaticNodeFound = false;
   840     for (let i = 0; i < aPopup.childNodes.length; i++) {
   841       let child = aPopup.childNodes[i];
   842       // Menus that have static content at the end, but are initially empty,
   843       // use a special "builder" attribute to figure out where to start
   844       // inserting places nodes.
   845       if (child.getAttribute("builder") == "end") {
   846         aPopup.insertBefore(aPopup._endMarker, child);
   847         break;
   848       }
   850       if (child._placesNode && !firstNonStaticNodeFound) {
   851         firstNonStaticNodeFound = true;
   852         aPopup.insertBefore(aPopup._startMarker, child);
   853       }
   854     }
   855     if (!firstNonStaticNodeFound) {
   856       aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker);
   857     }
   858   },
   860   _onPopupShowing: function PVB__onPopupShowing(aEvent) {
   861     // Avoid handling popupshowing of inner views.
   862     let popup = aEvent.originalTarget;
   864     this._ensureMarkers(popup);
   866     // Remove any delayed element, see _cleanPopup for details.
   867     if ("_delayedRemovals" in popup) {
   868       while (popup._delayedRemovals.length > 0) {
   869         popup.removeChild(popup._delayedRemovals.shift());
   870       }
   871     }
   873     if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
   874       if (!popup._placesNode.containerOpen)
   875         popup._placesNode.containerOpen = true;
   876       if (!popup._built)
   877         this._rebuildPopup(popup);
   879       this._mayAddCommandsItems(popup);
   880     }
   881   },
   883   _addEventListeners:
   884   function PVB__addEventListeners(aObject, aEventNames, aCapturing) {
   885     for (let i = 0; i < aEventNames.length; i++) {
   886       aObject.addEventListener(aEventNames[i], this, aCapturing);
   887     }
   888   },
   890   _removeEventListeners:
   891   function PVB__removeEventListeners(aObject, aEventNames, aCapturing) {
   892     for (let i = 0; i < aEventNames.length; i++) {
   893       aObject.removeEventListener(aEventNames[i], this, aCapturing);
   894     }
   895   },
   896 };
   898 function PlacesToolbar(aPlace) {
   899   let startTime = Date.now();
   900   // Add some smart getters for our elements.
   901   let thisView = this;
   902   [
   903     ["_viewElt",              "PlacesToolbar"],
   904     ["_rootElt",              "PlacesToolbarItems"],
   905     ["_dropIndicator",        "PlacesToolbarDropIndicator"],
   906     ["_chevron",              "PlacesChevron"],
   907     ["_chevronPopup",         "PlacesChevronPopup"]
   908   ].forEach(function (elementGlobal) {
   909     let [name, id] = elementGlobal;
   910     thisView.__defineGetter__(name, function () {
   911       let element = document.getElementById(id);
   912       if (!element)
   913         return null;
   915       delete thisView[name];
   916       return thisView[name] = element;
   917     });
   918   });
   920   this._viewElt._placesView = this;
   922   this._addEventListeners(this._viewElt, this._cbEvents, false);
   923   this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
   924   this._addEventListeners(this._rootElt, ["overflow", "underflow"], true);
   925   this._addEventListeners(window, ["resize", "unload"], false);
   927   // If personal-bookmarks has been dragged to the tabs toolbar,
   928   // we have to track addition and removals of tabs, to properly
   929   // recalculate the available space for bookmarks.
   930   // TODO (bug 734730): Use a performant mutation listener when available.
   931   if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) {
   932     this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
   933   }
   935   PlacesViewBase.call(this, aPlace);
   937   Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS")
   938                     .add(Date.now() - startTime);
   939 }
   941 PlacesToolbar.prototype = {
   942   __proto__: PlacesViewBase.prototype,
   944   _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop",
   945               "mousemove", "mouseover", "mouseout"],
   947   QueryInterface: function PT_QueryInterface(aIID) {
   948     if (aIID.equals(Ci.nsIDOMEventListener) ||
   949         aIID.equals(Ci.nsITimerCallback))
   950       return this;
   952     return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
   953   },
   955   uninit: function PT_uninit() {
   956     this._removeEventListeners(this._viewElt, this._cbEvents, false);
   957     this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
   958                                true);
   959     this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true);
   960     this._removeEventListeners(window, ["resize", "unload"], false);
   961     this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
   963     if (this._chevron._placesView) {
   964       this._chevron._placesView.uninit();
   965     }
   967     PlacesViewBase.prototype.uninit.apply(this, arguments);
   968   },
   970   _openedMenuButton: null,
   971   _allowPopupShowing: true,
   973   _rebuild: function PT__rebuild() {
   974     // Clear out references to existing nodes, since they will be removed
   975     // and re-added.
   976     if (this._overFolder.elt)
   977       this._clearOverFolder();
   979     this._openedMenuButton = null;
   980     while (this._rootElt.hasChildNodes()) {
   981       this._rootElt.removeChild(this._rootElt.firstChild);
   982     }
   984     let cc = this._resultNode.childCount;
   985     for (let i = 0; i < cc; ++i) {
   986       this._insertNewItem(this._resultNode.getChild(i), null);
   987     }
   989     if (this._chevronPopup.hasAttribute("type")) {
   990       // Chevron has already been initialized, but since we are forcing
   991       // a rebuild of the toolbar, it has to be rebuilt.
   992       // Otherwise, it will be initialized when the toolbar overflows.
   993       this._chevronPopup.place = this.place;
   994     }
   995   },
   997   _insertNewItem:
   998   function PT__insertNewItem(aChild, aBefore) {
   999     this._domNodes.delete(aChild);
  1001     let type = aChild.type;
  1002     let button;
  1003     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
  1004       button = document.createElement("toolbarseparator");
  1006     else {
  1007       button = document.createElement("toolbarbutton");
  1008       button.className = "bookmark-item";
  1009       button.setAttribute("label", aChild.title);
  1010       let icon = aChild.icon;
  1011       if (icon)
  1012         button.setAttribute("image", icon);
  1014       if (PlacesUtils.containerTypes.indexOf(type) != -1) {
  1015         button.setAttribute("type", "menu");
  1016         button.setAttribute("container", "true");
  1018         if (PlacesUtils.nodeIsQuery(aChild)) {
  1019           button.setAttribute("query", "true");
  1020           if (PlacesUtils.nodeIsTagQuery(aChild))
  1021             button.setAttribute("tagContainer", "true");
  1023         else if (PlacesUtils.nodeIsFolder(aChild)) {
  1024           PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
  1025             .then(aLivemark => {
  1026               button.setAttribute("livemark", "true");
  1027               this.controller.cacheLivemarkInfo(aChild, aLivemark);
  1028             }, () => undefined);
  1031         let popup = document.createElement("menupopup");
  1032         popup.setAttribute("placespopup", "true");
  1033         button.appendChild(popup);
  1034         popup._placesNode = PlacesUtils.asContainer(aChild);
  1035         popup.setAttribute("context", "placesContext");
  1037         this._domNodes.set(aChild, popup);
  1039       else if (PlacesUtils.nodeIsURI(aChild)) {
  1040         button.setAttribute("scheme",
  1041                             PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
  1045     button._placesNode = aChild;
  1046     if (!this._domNodes.has(aChild))
  1047       this._domNodes.set(aChild, button);
  1049     if (aBefore)
  1050       this._rootElt.insertBefore(button, aBefore);
  1051     else
  1052       this._rootElt.appendChild(button);
  1053   },
  1055   _updateChevronPopupNodesVisibility:
  1056   function PT__updateChevronPopupNodesVisibility() {
  1057     for (let i = 0, node = this._chevronPopup._startMarker.nextSibling;
  1058          node != this._chevronPopup._endMarker;
  1059          i++, node = node.nextSibling) {
  1060       node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden";
  1062   },
  1064   _onChevronPopupShowing:
  1065   function PT__onChevronPopupShowing(aEvent) {
  1066     // Handle popupshowing only for the chevron popup, not for nested ones.
  1067     if (aEvent.target != this._chevronPopup)
  1068       return;
  1070     if (!this._chevron._placesView)
  1071       this._chevron._placesView = new PlacesMenu(aEvent, this.place);
  1073     this._updateChevronPopupNodesVisibility();
  1074   },
  1076   handleEvent: function PT_handleEvent(aEvent) {
  1077     switch (aEvent.type) {
  1078       case "unload":
  1079         this.uninit();
  1080         break;
  1081       case "resize":
  1082         // This handler updates nodes visibility in both the toolbar
  1083         // and the chevron popup when a window resize does not change
  1084         // the overflow status of the toolbar.
  1085         this.updateChevron();
  1086         break;
  1087       case "overflow":
  1088         if (!this._isOverflowStateEventRelevant(aEvent))
  1089           return;
  1090         this._onOverflow();
  1091         break;
  1092       case "underflow":
  1093         if (!this._isOverflowStateEventRelevant(aEvent))
  1094           return;
  1095         this._onUnderflow();
  1096         break;
  1097       case "TabOpen":
  1098       case "TabClose":
  1099         this.updateChevron();
  1100         break;
  1101       case "dragstart":
  1102         this._onDragStart(aEvent);
  1103         break;
  1104       case "dragover":
  1105         this._onDragOver(aEvent);
  1106         break;
  1107       case "dragexit":
  1108         this._onDragExit(aEvent);
  1109         break;
  1110       case "dragend":
  1111         this._onDragEnd(aEvent);
  1112         break;
  1113       case "drop":
  1114         this._onDrop(aEvent);
  1115         break;
  1116       case "mouseover":
  1117         this._onMouseOver(aEvent);
  1118         break;
  1119       case "mousemove":
  1120         this._onMouseMove(aEvent);
  1121         break;
  1122       case "mouseout":
  1123         this._onMouseOut(aEvent);
  1124         break;
  1125       case "popupshowing":
  1126         this._onPopupShowing(aEvent);
  1127         break;
  1128       case "popuphidden":
  1129         this._onPopupHidden(aEvent);
  1130         break;
  1131       default:
  1132         throw "Trying to handle unexpected event.";
  1134   },
  1136   updateOverflowStatus: function() {
  1137     if (this._rootElt.scrollLeftMax > 0) {
  1138       this._onOverflow();
  1139     } else {
  1140       this._onUnderflow();
  1142   },
  1144   _isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) {
  1145     // Ignore events not aimed at ourselves, as well as purely vertical ones:
  1146     return aEvent.target == aEvent.currentTarget && aEvent.detail > 0;
  1147   },
  1149   _onOverflow: function PT_onOverflow() {
  1150     // Attach the popup binding to the chevron popup if it has not yet
  1151     // been initialized.
  1152     if (!this._chevronPopup.hasAttribute("type")) {
  1153       this._chevronPopup.setAttribute("place", this.place);
  1154       this._chevronPopup.setAttribute("type", "places");
  1156     this._chevron.collapsed = false;
  1157     this.updateChevron();
  1158   },
  1160   _onUnderflow: function PT_onUnderflow() {
  1161     this.updateChevron();
  1162     this._chevron.collapsed = true;
  1163   },
  1165   updateChevron: function PT_updateChevron() {
  1166     // If the chevron is collapsed there's nothing to update.
  1167     if (this._chevron.collapsed)
  1168       return;
  1170     // Update the chevron on a timer.  This will avoid repeated work when
  1171     // lot of changes happen in a small timeframe.
  1172     if (this._updateChevronTimer)
  1173       this._updateChevronTimer.cancel();
  1175     this._updateChevronTimer = this._setTimer(100);
  1176   },
  1178   _updateChevronTimerCallback: function PT__updateChevronTimerCallback() {
  1179     let scrollRect = this._rootElt.getBoundingClientRect();
  1180     let childOverflowed = false;
  1181     for (let i = 0; i < this._rootElt.childNodes.length; i++) {
  1182       let child = this._rootElt.childNodes[i];
  1183       // Once a child overflows, all the next ones will.
  1184       if (!childOverflowed) {
  1185         let childRect = child.getBoundingClientRect();
  1186         childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
  1187                                      : (childRect.right > scrollRect.right);
  1190       child.style.visibility = childOverflowed ? "hidden" : "visible";
  1193     // We rebuild the chevron on popupShowing, so if it is open
  1194     // we must update it.
  1195     if (this._chevron.open)
  1196       this._updateChevronPopupNodesVisibility();
  1197   },
  1199   nodeInserted:
  1200   function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
  1201     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1202     if (parentElt == this._rootElt) {
  1203       let children = this._rootElt.childNodes;
  1204       this._insertNewItem(aPlacesNode,
  1205         aIndex < children.length ? children[aIndex] : null);
  1206       this.updateChevron();
  1207       return;
  1210     PlacesViewBase.prototype.nodeInserted.apply(this, arguments);
  1211   },
  1213   nodeRemoved:
  1214   function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
  1215     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1216     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1218     // Here we need the <menu>.
  1219     if (elt.localName == "menupopup")
  1220       elt = elt.parentNode;
  1222     if (parentElt == this._rootElt) {
  1223       this._removeChild(elt);
  1224       this.updateChevron();
  1225       return;
  1228     PlacesViewBase.prototype.nodeRemoved.apply(this, arguments);
  1229   },
  1231   nodeMoved:
  1232   function PT_nodeMoved(aPlacesNode,
  1233                         aOldParentPlacesNode, aOldIndex,
  1234                         aNewParentPlacesNode, aNewIndex) {
  1235     let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
  1236     if (parentElt == this._rootElt) {
  1237       // Container is on the toolbar.
  1239       // Move the element.
  1240       let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1242       // Here we need the <menu>.
  1243       if (elt.localName == "menupopup")
  1244         elt = elt.parentNode;
  1246       this._removeChild(elt);
  1247       this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
  1249       // The chevron view may get nodeMoved after the toolbar.  In such a case,
  1250       // we should ensure (by manually swapping menuitems) that the actual nodes
  1251       // are in the final position before updateChevron tries to updates their
  1252       // visibility, or the chevron may go out of sync.
  1253       // Luckily updateChevron runs on a timer, so, by the time it updates
  1254       // nodes, the menu has already handled the notification.
  1256       this.updateChevron();
  1257       return;
  1260     PlacesViewBase.prototype.nodeMoved.apply(this, arguments);
  1261   },
  1263   nodeAnnotationChanged:
  1264   function PT_nodeAnnotationChanged(aPlacesNode, aAnno) {
  1265     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1266     if (elt == this._rootElt)
  1267       return;
  1269     // We're notified for the menupopup, not the containing toolbarbutton.
  1270     if (elt.localName == "menupopup")
  1271       elt = elt.parentNode;
  1273     if (elt.parentNode == this._rootElt) {
  1274       // Node is on the toolbar.
  1276       // All livemarks have a feedURI, so use it as our indicator.
  1277       if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
  1278         elt.setAttribute("livemark", true);
  1280         PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
  1281           .then(aLivemark => {
  1282             this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
  1283             this.invalidateContainer(aPlacesNode);
  1284           }, Components.utils.reportError);
  1287     else {
  1288       // Node is in a submenu.
  1289       PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
  1291   },
  1293   nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
  1294     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1296     // There's no UI representation for the root node, thus there's
  1297     // nothing to be done when the title changes.
  1298     if (elt == this._rootElt)
  1299       return;
  1301     PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
  1303     // Here we need the <menu>.
  1304     if (elt.localName == "menupopup")
  1305       elt = elt.parentNode;
  1307     if (elt.parentNode == this._rootElt) {
  1308       // Node is on the toolbar
  1309       this.updateChevron();
  1311   },
  1313   invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
  1314     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1315     if (elt == this._rootElt) {
  1316       // Container is the toolbar itself.
  1317       this._rebuild();
  1318       return;
  1321     PlacesViewBase.prototype.invalidateContainer.apply(this, arguments);
  1322   },
  1324   _overFolder: { elt: null,
  1325                  openTimer: null,
  1326                  hoverTime: 350,
  1327                  closeTimer: null },
  1329   _clearOverFolder: function PT__clearOverFolder() {
  1330     // The mouse is no longer dragging over the stored menubutton.
  1331     // Close the menubutton, clear out drag styles, and clear all
  1332     // timers for opening/closing it.
  1333     if (this._overFolder.elt && this._overFolder.elt.lastChild) {
  1334       if (!this._overFolder.elt.lastChild.hasAttribute("dragover")) {
  1335         this._overFolder.elt.lastChild.hidePopup();
  1337       this._overFolder.elt.removeAttribute("dragover");
  1338       this._overFolder.elt = null;
  1340     if (this._overFolder.openTimer) {
  1341       this._overFolder.openTimer.cancel();
  1342       this._overFolder.openTimer = null;
  1344     if (this._overFolder.closeTimer) {
  1345       this._overFolder.closeTimer.cancel();
  1346       this._overFolder.closeTimer = null;
  1348   },
  1350   /**
  1351    * This function returns information about where to drop when dragging over
  1352    * the toolbar.  The returned object has the following properties:
  1353    * - ip: the insertion point for the bookmarks service.
  1354    * - beforeIndex: child index to drop before, for the drop indicator.
  1355    * - folderElt: the folder to drop into, if applicable.
  1356    */
  1357   _getDropPoint: function PT__getDropPoint(aEvent) {
  1358     let result = this.result;
  1359     if (!PlacesUtils.nodeIsFolder(this._resultNode))
  1360       return null;
  1362     let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
  1363     let elt = aEvent.target;
  1364     if (elt._placesNode && elt != this._rootElt &&
  1365         elt.localName != "menupopup") {
  1366       let eltRect = elt.getBoundingClientRect();
  1367       let eltIndex = Array.indexOf(this._rootElt.childNodes, elt);
  1368       if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
  1369           !PlacesUtils.nodeIsReadOnly(elt._placesNode)) {
  1370         // This is a folder.
  1371         // If we are in the middle of it, drop inside it.
  1372         // Otherwise, drop before it, with regards to RTL mode.
  1373         let threshold = eltRect.width * 0.25;
  1374         if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold)
  1375                        : (aEvent.clientX < eltRect.left + threshold)) {
  1376           // Drop before this folder.
  1377           dropPoint.ip =
  1378             new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1379                                eltIndex, Ci.nsITreeView.DROP_BEFORE);
  1380           dropPoint.beforeIndex = eltIndex;
  1382         else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
  1383                             : (aEvent.clientX < eltRect.right - threshold)) {
  1384           // Drop inside this folder.
  1385           dropPoint.ip =
  1386             new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode),
  1387                                -1, Ci.nsITreeView.DROP_ON,
  1388                                PlacesUtils.nodeIsTagQuery(elt._placesNode));
  1389           dropPoint.beforeIndex = eltIndex;
  1390           dropPoint.folderElt = elt;
  1392         else {
  1393           // Drop after this folder.
  1394           let beforeIndex =
  1395             (eltIndex == this._rootElt.childNodes.length - 1) ?
  1396             -1 : eltIndex + 1;
  1398           dropPoint.ip =
  1399             new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1400                                beforeIndex, Ci.nsITreeView.DROP_BEFORE);
  1401           dropPoint.beforeIndex = beforeIndex;
  1404       else {
  1405         // This is a non-folder node or a read-only folder.
  1406         // Drop before it with regards to RTL mode.
  1407         let threshold = eltRect.width * 0.5;
  1408         if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
  1409                        : (aEvent.clientX < eltRect.left + threshold)) {
  1410           // Drop before this bookmark.
  1411           dropPoint.ip =
  1412             new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1413                                eltIndex, Ci.nsITreeView.DROP_BEFORE);
  1414           dropPoint.beforeIndex = eltIndex;
  1416         else {
  1417           // Drop after this bookmark.
  1418           let beforeIndex =
  1419             eltIndex == this._rootElt.childNodes.length - 1 ?
  1420             -1 : eltIndex + 1;
  1421           dropPoint.ip =
  1422             new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1423                                beforeIndex, Ci.nsITreeView.DROP_BEFORE);
  1424           dropPoint.beforeIndex = beforeIndex;
  1428     else {
  1429       // We are most likely dragging on the empty area of the
  1430       // toolbar, we should drop after the last node.
  1431       dropPoint.ip =
  1432         new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
  1433                            -1, Ci.nsITreeView.DROP_BEFORE);
  1434       dropPoint.beforeIndex = -1;
  1437     return dropPoint;
  1438   },
  1440   _setTimer: function PT_setTimer(aTime) {
  1441     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1442     timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
  1443     return timer;
  1444   },
  1446   notify: function PT_notify(aTimer) {
  1447     if (aTimer == this._updateChevronTimer) {
  1448       this._updateChevronTimer = null;
  1449       this._updateChevronTimerCallback();
  1452     // * Timer to turn off indicator bar.
  1453     else if (aTimer == this._ibTimer) {
  1454       this._dropIndicator.collapsed = true;
  1455       this._ibTimer = null;
  1458     // * Timer to open a menubutton that's being dragged over.
  1459     else if (aTimer == this._overFolder.openTimer) {
  1460       // Set the autoopen attribute on the folder's menupopup so that
  1461       // the menu will automatically close when the mouse drags off of it.
  1462       this._overFolder.elt.lastChild.setAttribute("autoopened", "true");
  1463       this._overFolder.elt.open = true;
  1464       this._overFolder.openTimer = null;
  1467     // * Timer to close a menubutton that's been dragged off of.
  1468     else if (aTimer == this._overFolder.closeTimer) {
  1469       // Close the menubutton if we are not dragging over it or one of
  1470       // its children.  The autoopened attribute will let the menu know to
  1471       // close later if the menu is still being dragged over.
  1472       let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget;
  1473       let inHierarchy = false;
  1474       while (currentPlacesNode) {
  1475         if (currentPlacesNode == this._rootElt) {
  1476           inHierarchy = true;
  1477           break;
  1479         currentPlacesNode = currentPlacesNode.parentNode;
  1481       // The _clearOverFolder() function will close the menu for
  1482       // _overFolder.elt.  So null it out if we don't want to close it.
  1483       if (inHierarchy)
  1484         this._overFolder.elt = null;
  1486       // Clear out the folder and all associated timers.
  1487       this._clearOverFolder();
  1489   },
  1491   _onMouseOver: function PT__onMouseOver(aEvent) {
  1492     let button = aEvent.target;
  1493     if (button.parentNode == this._rootElt && button._placesNode &&
  1494         PlacesUtils.nodeIsURI(button._placesNode))
  1495       window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null);
  1496   },
  1498   _onMouseOut: function PT__onMouseOut(aEvent) {
  1499     window.XULBrowserWindow.setOverLink("", null);
  1500   },
  1502   _cleanupDragDetails: function PT__cleanupDragDetails() {
  1503     // Called on dragend and drop.
  1504     PlacesControllerDragHelper.currentDropTarget = null;
  1505     this._draggedElt = null;
  1506     if (this._ibTimer)
  1507       this._ibTimer.cancel();
  1509     this._dropIndicator.collapsed = true;
  1510   },
  1512   _onDragStart: function PT__onDragStart(aEvent) {
  1513     // Sub menus have their own d&d handlers.
  1514     let draggedElt = aEvent.target;
  1515     if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode)
  1516       return;
  1518     if (draggedElt.localName == "toolbarbutton" &&
  1519         draggedElt.getAttribute("type") == "menu") {
  1520       // If the drag gesture on a container is toward down we open instead
  1521       // of dragging.
  1522       let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY;
  1523       let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX;
  1524       if ((translateY) >= Math.abs(translateX/2)) {
  1525         // Don't start the drag.
  1526         aEvent.preventDefault();
  1527         // Open the menu.
  1528         draggedElt.open = true;
  1529         return;
  1532       // If the menu is open, close it.
  1533       if (draggedElt.open) {
  1534         draggedElt.lastChild.hidePopup();
  1535         draggedElt.open = false;
  1539     // Activate the view and cache the dragged element.
  1540     this._draggedElt = draggedElt._placesNode;
  1541     this._rootElt.focus();
  1543     this._controller.setDataTransfer(aEvent);
  1544     aEvent.stopPropagation();
  1545   },
  1547   _onDragOver: function PT__onDragOver(aEvent) {
  1548     // Cache the dataTransfer
  1549     PlacesControllerDragHelper.currentDropTarget = aEvent.target;
  1550     let dt = aEvent.dataTransfer;
  1552     let dropPoint = this._getDropPoint(aEvent);
  1553     if (!dropPoint || !dropPoint.ip ||
  1554         !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
  1555       this._dropIndicator.collapsed = true;
  1556       aEvent.stopPropagation();
  1557       return;
  1560     if (this._ibTimer) {
  1561       this._ibTimer.cancel();
  1562       this._ibTimer = null;
  1565     if (dropPoint.folderElt || aEvent.originalTarget == this._chevron) {
  1566       // Dropping over a menubutton or chevron button.
  1567       // Set styles and timer to open relative menupopup.
  1568       let overElt = dropPoint.folderElt || this._chevron;
  1569       if (this._overFolder.elt != overElt) {
  1570         this._clearOverFolder();
  1571         this._overFolder.elt = overElt;
  1572         this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
  1574       if (!this._overFolder.elt.hasAttribute("dragover"))
  1575         this._overFolder.elt.setAttribute("dragover", "true");
  1577       this._dropIndicator.collapsed = true;
  1579     else {
  1580       // Dragging over a normal toolbarbutton,
  1581       // show indicator bar and move it to the appropriate drop point.
  1582       let ind = this._dropIndicator;
  1583       ind.parentNode.collapsed = false;
  1584       let halfInd = ind.clientWidth / 2;
  1585       let translateX;
  1586       if (this.isRTL) {
  1587         halfInd = Math.ceil(halfInd);
  1588         translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd;
  1589         if (this._rootElt.firstChild) {
  1590           if (dropPoint.beforeIndex == -1)
  1591             translateX += this._rootElt.lastChild.getBoundingClientRect().left;
  1592           else {
  1593             translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
  1594                               .getBoundingClientRect().right;
  1598       else {
  1599         halfInd = Math.floor(halfInd);
  1600         translateX = 0 - this._rootElt.getBoundingClientRect().left +
  1601                      halfInd;
  1602         if (this._rootElt.firstChild) {
  1603           if (dropPoint.beforeIndex == -1)
  1604             translateX += this._rootElt.lastChild.getBoundingClientRect().right;
  1605           else {
  1606             translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
  1607                               .getBoundingClientRect().left;
  1612       ind.style.transform = "translate(" + Math.round(translateX) + "px)";
  1613       ind.style.MozMarginStart = (-ind.clientWidth) + "px";
  1614       ind.collapsed = false;
  1616       // Clear out old folder information.
  1617       this._clearOverFolder();
  1620     aEvent.preventDefault();
  1621     aEvent.stopPropagation();
  1622   },
  1624   _onDrop: function PT__onDrop(aEvent) {
  1625     PlacesControllerDragHelper.currentDropTarget = aEvent.target;
  1627     let dropPoint = this._getDropPoint(aEvent);
  1628     if (dropPoint && dropPoint.ip) {
  1629       PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
  1630       aEvent.preventDefault();
  1633     this._cleanupDragDetails();
  1634     aEvent.stopPropagation();
  1635   },
  1637   _onDragExit: function PT__onDragExit(aEvent) {
  1638     PlacesControllerDragHelper.currentDropTarget = null;
  1640     // Set timer to turn off indicator bar (if we turn it off
  1641     // here, dragenter might be called immediately after, creating
  1642     // flicker).
  1643     if (this._ibTimer)
  1644       this._ibTimer.cancel();
  1645     this._ibTimer = this._setTimer(10);
  1647     // If we hovered over a folder, close it now.
  1648     if (this._overFolder.elt)
  1649         this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
  1650   },
  1652   _onDragEnd: function PT_onDragEnd(aEvent) {
  1653     this._cleanupDragDetails();
  1654   },
  1656   _onPopupShowing: function PT__onPopupShowing(aEvent) {
  1657     if (!this._allowPopupShowing) {
  1658       this._allowPopupShowing = true;
  1659       aEvent.preventDefault();
  1660       return;
  1663     let parent = aEvent.target.parentNode;
  1664     if (parent.localName == "toolbarbutton")
  1665       this._openedMenuButton = parent;
  1667     PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
  1668   },
  1670   _onPopupHidden: function PT__onPopupHidden(aEvent) {
  1671     let popup = aEvent.target;
  1672     let placesNode = popup._placesNode;
  1673     // Avoid handling popuphidden of inner views
  1674     if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
  1675       // UI performance: folder queries are cheap, keep the resultnode open
  1676       // so we don't rebuild its contents whenever the popup is reopened.
  1677       // Though, we want to always close feed containers so their expiration
  1678       // status will be checked at next opening.
  1679       if (!PlacesUtils.nodeIsFolder(placesNode) ||
  1680           this.controller.hasCachedLivemarkInfo(placesNode)) {
  1681         placesNode.containerOpen = false;
  1685     let parent = popup.parentNode;
  1686     if (parent.localName == "toolbarbutton") {
  1687       this._openedMenuButton = null;
  1688       // Clear the dragover attribute if present, if we are dragging into a
  1689       // folder in the hierachy of current opened popup we don't clear
  1690       // this attribute on clearOverFolder.  See Notify for closeTimer.
  1691       if (parent.hasAttribute("dragover"))
  1692         parent.removeAttribute("dragover");
  1694   },
  1696   _onMouseMove: function PT__onMouseMove(aEvent) {
  1697     // Used in dragStart to prevent dragging folders when dragging down.
  1698     this._cachedMouseMoveEvent = aEvent;
  1700     if (this._openedMenuButton == null ||
  1701         PlacesControllerDragHelper.getSession())
  1702       return;
  1704     let target = aEvent.originalTarget;
  1705     if (this._openedMenuButton != target &&
  1706         target.localName == "toolbarbutton" &&
  1707         target.type == "menu") {
  1708       this._openedMenuButton.open = false;
  1709       target.open = true;
  1712 };
  1714 /**
  1715  * View for Places menus.  This object should be created during the first
  1716  * popupshowing that's dispatched on the menu.
  1717  */
  1718 function PlacesMenu(aPopupShowingEvent, aPlace, aOptions) {
  1719   this._rootElt = aPopupShowingEvent.target; // <menupopup>
  1720   this._viewElt = this._rootElt.parentNode;   // <menu>
  1721   this._viewElt._placesView = this;
  1722   this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
  1723   this._addEventListeners(window, ["unload"], false);
  1725 #ifdef XP_MACOSX
  1726   // Must walk up to support views in sub-menus, like Bookmarks Toolbar menu.
  1727   for (let elt = this._viewElt.parentNode; elt; elt = elt.parentNode) {
  1728     if (elt.localName == "menubar") {
  1729       this._nativeView = true;
  1730       break;
  1733 #endif
  1735   PlacesViewBase.call(this, aPlace, aOptions);
  1736   this._onPopupShowing(aPopupShowingEvent);
  1739 PlacesMenu.prototype = {
  1740   __proto__: PlacesViewBase.prototype,
  1742   QueryInterface: function PM_QueryInterface(aIID) {
  1743     if (aIID.equals(Ci.nsIDOMEventListener))
  1744       return this;
  1746     return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
  1747   },
  1749   _removeChild: function PM_removeChild(aChild) {
  1750     PlacesViewBase.prototype._removeChild.apply(this, arguments);
  1751   },
  1753   uninit: function PM_uninit() {
  1754     this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
  1755                                true);
  1756     this._removeEventListeners(window, ["unload"], false);
  1758     PlacesViewBase.prototype.uninit.apply(this, arguments);
  1759   },
  1761   handleEvent: function PM_handleEvent(aEvent) {
  1762     switch (aEvent.type) {
  1763       case "unload":
  1764         this.uninit();
  1765         break;
  1766       case "popupshowing":
  1767         this._onPopupShowing(aEvent);
  1768         break;
  1769       case "popuphidden":
  1770         this._onPopupHidden(aEvent);
  1771         break;
  1773   },
  1775   _onPopupHidden: function PM__onPopupHidden(aEvent) {
  1776     // Avoid handling popuphidden of inner views.
  1777     let popup = aEvent.originalTarget;
  1778     let placesNode = popup._placesNode;
  1779     if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
  1780       return;
  1782     // UI performance: folder queries are cheap, keep the resultnode open
  1783     // so we don't rebuild its contents whenever the popup is reopened.
  1784     // Though, we want to always close feed containers so their expiration
  1785     // status will be checked at next opening.
  1786     if (!PlacesUtils.nodeIsFolder(placesNode) ||
  1787         this.controller.hasCachedLivemarkInfo(placesNode))
  1788       placesNode.containerOpen = false;
  1790     // The autoopened attribute is set for folders which have been
  1791     // automatically opened when dragged over.  Turn off this attribute
  1792     // when the folder closes because it is no longer applicable.
  1793     popup.removeAttribute("autoopened");
  1794     popup.removeAttribute("dragstart");
  1796 };
  1798 function PlacesPanelMenuView(aPlace, aViewId, aRootId, aOptions) {
  1799   this._viewElt = document.getElementById(aViewId);
  1800   this._rootElt = document.getElementById(aRootId);
  1801   this._viewElt._placesView = this;
  1802   this.options = aOptions;
  1804   PlacesViewBase.call(this, aPlace, aOptions);
  1807 PlacesPanelMenuView.prototype = {
  1808   __proto__: PlacesViewBase.prototype,
  1810   QueryInterface: function PAMV_QueryInterface(aIID) {
  1811     return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
  1812   },
  1814   uninit: function PAMV_uninit() {
  1815     PlacesViewBase.prototype.uninit.apply(this, arguments);
  1816   },
  1818   _insertNewItem:
  1819   function PAMV__insertNewItem(aChild, aBefore) {
  1820     this._domNodes.delete(aChild);
  1822     let type = aChild.type;
  1823     let button;
  1824     if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
  1825       button = document.createElement("toolbarseparator");
  1826       button.setAttribute("class", "small-separator");
  1828     else {
  1829       button = document.createElement("toolbarbutton");
  1830       button.className = "bookmark-item";
  1831       if (typeof this.options.extraClasses.entry == "string")
  1832         button.classList.add(this.options.extraClasses.entry);
  1833       button.setAttribute("label", aChild.title);
  1834       let icon = aChild.icon;
  1835       if (icon)
  1836         button.setAttribute("image", icon);
  1838       if (PlacesUtils.containerTypes.indexOf(type) != -1) {
  1839         button.setAttribute("container", "true");
  1841         if (PlacesUtils.nodeIsQuery(aChild)) {
  1842           button.setAttribute("query", "true");
  1843           if (PlacesUtils.nodeIsTagQuery(aChild))
  1844             button.setAttribute("tagContainer", "true");
  1846         else if (PlacesUtils.nodeIsFolder(aChild)) {
  1847           PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
  1848             .then(aLivemark => {
  1849               button.setAttribute("livemark", "true");
  1850               this.controller.cacheLivemarkInfo(aChild, aLivemark);
  1851             }, () => undefined);
  1854       else if (PlacesUtils.nodeIsURI(aChild)) {
  1855         button.setAttribute("scheme",
  1856                             PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
  1860     button._placesNode = aChild;
  1861     if (!this._domNodes.has(aChild))
  1862       this._domNodes.set(aChild, button);
  1864     this._rootElt.insertBefore(button, aBefore);
  1865   },
  1867   nodeInserted:
  1868   function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
  1869     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1870     if (parentElt != this._rootElt)
  1871       return;
  1873     let children = this._rootElt.childNodes;
  1874     this._insertNewItem(aPlacesNode,
  1875       aIndex < children.length ? children[aIndex] : null);
  1876   },
  1878   nodeRemoved:
  1879   function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
  1880     let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
  1881     if (parentElt != this._rootElt)
  1882       return;
  1884     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1885     this._removeChild(elt);
  1886   },
  1888   nodeMoved:
  1889   function PAMV_nodeMoved(aPlacesNode,
  1890                           aOldParentPlacesNode, aOldIndex,
  1891                           aNewParentPlacesNode, aNewIndex) {
  1892     let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
  1893     if (parentElt != this._rootElt)
  1894       return;
  1896     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1897     this._removeChild(elt);
  1898     this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
  1899   },
  1901   nodeAnnotationChanged:
  1902   function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) {
  1903     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1904     // There's no UI representation for the root node.
  1905     if (elt == this._rootElt)
  1906       return;
  1908     if (elt.parentNode != this._rootElt)
  1909       return;
  1911     // All livemarks have a feedURI, so use it as our indicator.
  1912     if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
  1913       elt.setAttribute("livemark", true);
  1915       PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
  1916         .then(aLivemark => {
  1917           this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
  1918           this.invalidateContainer(aPlacesNode);
  1919         }, Components.utils.reportError);
  1921   },
  1923   nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) {
  1924     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1926     // There's no UI representation for the root node.
  1927     if (elt == this._rootElt)
  1928       return;
  1930     PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
  1931   },
  1933   invalidateContainer: function PAMV_invalidateContainer(aPlacesNode) {
  1934     let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
  1935     if (elt != this._rootElt)
  1936       return;
  1938     // Container is the toolbar itself.
  1939     while (this._rootElt.hasChildNodes()) {
  1940       this._rootElt.removeChild(this._rootElt.firstChild);
  1943     for (let i = 0; i < this._resultNode.childCount; ++i) {
  1944       this._insertNewItem(this._resultNode.getChild(i), null);
  1947 };

mercurial