Bug 1378089 - Part 5. Make the All Bookmarks folder for the left pane of the Library a virtual query. r=IanN a=IanN
authorFrank-Rainer Grahl <frgrahl@gmx.net>
Mon, 07 May 2018 12:41:29 +0200
changeset 31461 d743fc6395f8
parent 31460 551982a6e2bd
child 31462 a414897db0d9
push id383
push userclokep@gmail.com
push date2018-05-07 21:52 +0000
reviewersIanN, IanN
bugs1378089, 1423896
Bug 1378089 - Part 5. Make the All Bookmarks folder for the left pane of the Library a virtual query. r=IanN a=IanN Port Bug 1423896 [Make the All Bookmarks folder for the left pane a virtual query].
suite/browser/browser-places.js
suite/common/places/PlacesUIUtils.jsm
suite/common/places/content/bookmarksPanel.js
suite/common/places/content/controller.js
suite/common/places/content/editBookmarkOverlay.js
suite/common/places/content/places.js
suite/common/places/content/tree.xml
suite/common/places/content/treeView.js
suite/themes/classic/communicator/places/bookmarks.css
suite/themes/classic/mac/communicator/places/bookmarks.css
suite/themes/modern/communicator/places/bookmarks.css
--- a/suite/browser/browser-places.js
+++ b/suite/browser/browser-places.js
@@ -503,30 +503,30 @@ var PlacesCommandHook = {
                                        hiddenRows: [ "feedLocation",
                                                      "siteLocation",
                                                      "description" ]
                                      }, window);
   },
 
   /**
    * Opens the Places Organizer.
-   * @param   aLeftPaneRoot
-   *          The query to select in the organizer window - options
-   *          are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
-   *          UnfiledBookmarks and Tags.
+   * @param {String} item The item to select in the organizer window,
+   *                      options are (case sensitive):
+   *                      BookmarksMenu, BookmarksToolbar, UnfiledBookmarks,
+   *                      AllBookmarks, History.
    */
-  showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
+  showPlacesOrganizer(item) {
     var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
     // Due to bug 528706, getMostRecentWindow can return closed windows.
     if (!organizer || organizer.closed) {
       // No currently open places window, so open one with the specified mode.
       openDialog("chrome://communicator/content/places/places.xul",
-                 "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
+                 "", "chrome,toolbar=yes,dialog=no,resizable", item);
     } else {
-      organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
+      organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(item);
       organizer.focus();
     }
   },
 };
 
 /**
  * Functions for handling events in the Bookmarks Toolbar and menu.
  */
--- a/suite/common/places/PlacesUIUtils.jsm
+++ b/suite/common/places/PlacesUIUtils.jsm
@@ -199,17 +199,17 @@ let InternalFaviconLoader = {
       this._removeLoadDataFromWindowMap(win, loadData);
     }, FAVICON_REQUEST_TIMEOUT);
     let loadDataForWindow = gFaviconLoadDataMap.get(win);
     loadDataForWindow.push(loadData);
   },
 };
 
 this.PlacesUIUtils = {
-  ORGANIZER_LEFTPANE_VERSION: 7,
+  ORGANIZER_LEFTPANE_VERSION: 8,
   ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
   ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
 
   LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
   DESCRIPTION_ANNO: "bookmarkProperties/description",
 
   /**
    * Makes a URI from a spec, and do fixup
@@ -507,16 +507,26 @@ this.PlacesUIUtils = {
    */
   canUserRemove(aNode, aView) {
     let parentNode = aNode.parent;
     if (!parentNode) {
       // canUserRemove doesn't accept root nodes.
       return false;
     }
 
+    // Is it a query pointing to one of the special root folders?
+    if (PlacesUtils.nodeIsQuery(parentNode) && PlacesUtils.nodeIsFolder(aNode)) {
+      let guid = PlacesUtils.getConcreteItemGuid(aNode);
+      // If the parent folder is not a folder, it must be a query, and so this node
+      // cannot be removed.
+      if (PlacesUtils.isRootItem(guid)) {
+        return false;
+      }
+    }
+
     // If it's not a bookmark, we can remove it unless it's a child of a
     // livemark.
     if (aNode.itemId == -1) {
       // Rather than executing a db query, checking the existence of the feedURI
       // annotation, detect livemark children by the fact that they are the only
       // direct non-bookmark children of bookmark folders.
       return !PlacesUtils.nodeIsFolder(parentNode);
     }
@@ -557,32 +567,31 @@ this.PlacesUIUtils = {
     if (!view || typeof view != "object") {
       throw new Error("invalid value for aView");
     }
     let itemId = PlacesUtils.getConcreteItemId(placesNode);
     if (itemId == PlacesUtils.placesRootId ||
         view.controller.hasCachedLivemarkInfo(placesNode))
       return true;
 
-    // leftPaneFolderId, and as a result, allBookmarksFolderId, is a lazy getter
+    // leftPaneFolderId is a lazy getter
     // performing at least a synchronous DB query (and on its very first call
     // in a fresh profile, it also creates the entire structure).
     // Therefore we don't want to this function, which is called very often by
     // isCommandEnabled, to ever be the one that invokes it first, especially
     // because isCommandEnabled may be called way before the left pane folder is
     // even created (for example, if the user only uses the bookmarks menu or
     // toolbar for managing bookmarks).  To do so, we avoid comparing to those
     // special folder if the lazy getter is still in place.  This is safe merely
     // because the only way to access the left pane contents goes through
     // "resolving" the leftPaneFolderId getter.
     if (typeof Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").get == "function") {
       return false;
     }
-    return itemId == this.leftPaneFolderId ||
-           itemId == this.allBookmarksFolderId;
+    return itemId == this.leftPaneFolderId;
   },
 
   /**
    * Gives the user a chance to cancel loading lots of tabs at once
    */
   confirmOpenInTabs(numTabsToOpen, aWindow) {
     const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
     var reallyOpen = true;
@@ -835,43 +844,30 @@ this.PlacesUIUtils = {
   get leftPaneFolderId() {
     delete this.leftPaneFolderId;
     return this.leftPaneFolderId = this.maybeRebuildLeftPane();
   },
 
   // Get the folder id for the organizer left-pane folder.
   maybeRebuildLeftPane() {
     let leftPaneRoot = -1;
-    let allBookmarksId;
 
     // Shortcuts to services.
     let bs = PlacesUtils.bookmarks;
     let as = PlacesUtils.annotations;
 
     // This is the list of the left pane queries.
     let queries = {
       "PlacesRoot": { title: "" },
       "History": { title: this.getString("OrganizerQueryHistory") },
       "Tags": { title: this.getString("OrganizerQueryTags") },
       "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
-      "BookmarksToolbar":
-        { title: "",
-          concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
-          concreteId: PlacesUtils.toolbarFolderId },
-      "BookmarksMenu":
-        { title: "",
-          concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
-          concreteId: PlacesUtils.bookmarksMenuFolderId },
-      "UnfiledBookmarks":
-        { title: "",
-          concreteTitle: PlacesUtils.getString("OtherBookmarksFolderTitle"),
-          concreteId: PlacesUtils.unfiledBookmarksFolderId },
     };
     // All queries but PlacesRoot.
-    const EXPECTED_QUERY_COUNT = 6;
+    const EXPECTED_QUERY_COUNT = 3;
 
     // Removes an item and associated annotations, ignoring eventual errors.
     function safeRemoveItem(aItemId) {
       try {
         if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) &&
             !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) {
           // Some extension annotated their roots with our query annotation,
           // so we should not delete them.
@@ -1041,47 +1037,27 @@ this.PlacesUIUtils = {
         // Tags Query.
         this.create_query("Tags", leftPaneRoot,
                           "place:type=" +
                           Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
                           "&sort=" +
                           Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
 
         // All Bookmarks Folder.
-        allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
-
-        // All Bookmarks->Bookmarks Toolbar Query.
-        this.create_query("BookmarksToolbar", allBookmarksId,
-                          "place:folder=TOOLBAR");
-
-        // All Bookmarks->Bookmarks Menu Query.
-        this.create_query("BookmarksMenu", allBookmarksId,
-                          "place:folder=BOOKMARKS_MENU");
-
-        // All Bookmarks->Unfiled Bookmarks Query.
-        this.create_query("UnfiledBookmarks", allBookmarksId,
-                          "place:folder=UNFILED_BOOKMARKS");
+        this.create_query("AllBookmarks", leftPaneRoot,
+                          "place:type=" +
+                          Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY);
       }
     };
     bs.runInBatchMode(callback, null);
 
     return leftPaneRoot;
   },
 
   /**
-   * Get the folder id for the organizer left-pane folder.
-   */
-  get allBookmarksFolderId() {
-    // ensure the left-pane root is initialized;
-    this.leftPaneFolderId;
-    delete this.allBookmarksFolderId;
-    return this.allBookmarksFolderId = this.leftPaneQueries.AllBookmarks;
-  },
-
-  /**
    * If an item is a left-pane query, returns the name of the query
    * or an empty string if not.
    *
    * @param aItemId id of a container
    * @return the name of the query, or empty string if not a left-pane query
    */
   getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) {
     var queryName = "";
--- a/suite/common/places/content/bookmarksPanel.js
+++ b/suite/common/places/content/bookmarksPanel.js
@@ -1,16 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function init() {
   document.getElementById("bookmarks-view").place =
-    "place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId;
+    "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
 }
 
 function searchBookmarks(aSearchString) {
   var tree = document.getElementById("bookmarks-view");
   if (!aSearchString)
     tree.place = tree.place;
   else
     tree.applyFilter(aSearchString,
--- a/suite/common/places/content/controller.js
+++ b/suite/common/places/content/controller.js
@@ -1372,33 +1372,33 @@ var PlacesControllerDragHelper = {
   /**
    * Determines if an unwrapped node can be moved.
    *
    * @param unwrappedNode
    *        A node unwrapped by PlacesUtils.unwrapNodes().
    * @return True if the node can be moved, false otherwise.
    */
   canMoveUnwrappedNode(unwrappedNode) {
-    if (unwrappedNode.id <= 0 || PlacesUtils.isRootItem(unwrappedNode.id)) {
+    if ((unwrappedNode.concreteGuid && PlacesUtils.isRootItem(unwrappedNode.concreteGuid)) ||
+        unwrappedNode.id <= 0 || PlacesUtils.isRootItem(unwrappedNode.id)) {
       return false;
     }
     let parentId = unwrappedNode.parent;
     if (parentId <= 0 ||
         parentId == PlacesUtils.placesRootId ||
         parentId == PlacesUtils.tagsFolderId ||
         unwrappedNode.grandParentId == PlacesUtils.tagsFolderId) {
       return false;
     }
     // leftPaneFolderId and allBookmarksFolderId are lazy getters running
     // at least a synchronous DB query. Therefore we don't want to invoke
     // them first, especially because isCommandEnabled may be called way
     // before the left pane folder is even necessary.
     if (typeof Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId").get != "function" &&
-        (parentId == PlacesUIUtils.leftPaneFolderId ||
-          parentId == PlacesUIUtils.allBookmarksFolderId)) {
+        (parentId == PlacesUIUtils.leftPaneFolderId)) {
       return false;
     }
     return true;
   },
 
   /**
    * Determines if a node can be moved.
    *
--- a/suite/common/places/content/editBookmarkOverlay.js
+++ b/suite/common/places/content/editBookmarkOverlay.js
@@ -55,18 +55,17 @@ var gEditItemOverlay = {
         throw new Error("Cannot use an incomplete node to initialize the edit bookmark panel");
       }
       let parent = node.parent;
       isParentReadOnly = !PlacesUtils.nodeIsFolder(parent);
       if (!isParentReadOnly) {
         let folderId = PlacesUtils.getConcreteItemId(parent);
         isParentReadOnly = folderId == PlacesUtils.placesRootId ||
                            (!("get" in Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId")) &&
-                            (folderId == PlacesUIUtils.leftPaneFolderId ||
-                             folderId == PlacesUIUtils.allBookmarksFolderId));
+                            (folderId == PlacesUIUtils.leftPaneFolderId));
       }
       parentId = parent.itemId;
       parentGuid = parent.bookmarkGuid;
     }
 
     let focusedElement = aInitInfo.focusedElement;
     let onPanelReady = aInitInfo.onPanelReady;
 
@@ -701,23 +700,23 @@ var gEditItemOverlay = {
       expander.setAttribute("tooltiptext",
                             expander.getAttribute("tooltiptextup"));
       folderTreeRow.collapsed = false;
 
       // XXXmano: Ideally we would only do this once, but for some odd reason,
       // the editable mode set on this tree, together with its collapsed state
       // breaks the view.
       const FOLDER_TREE_PLACE_URI =
-        "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
-        PlacesUIUtils.allBookmarksFolderId;
+        "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&type=" +
+        Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
       this._folderTree.place = FOLDER_TREE_PLACE_URI;
 
       this._element("chooseFolderSeparator").hidden =
         this._element("chooseFolderMenuItem").hidden = true;
-      this._folderTree.selectItems([this._paneInfo.parentId]);
+      this._folderTree.selectItems([this._paneInfo.parentGuid]);
       this._folderTree.focus();
     }
   },
 
   /**
    * Get the corresponding menu-item in the folder-menu-list for a bookmarks
    * folder if such an item exists. Otherwise, this creates a menu-item for the
    * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
@@ -926,17 +925,17 @@ var gEditItemOverlay = {
                .split(/\s*,\s*/) // Split on commas and remove spaces.
                .filter(tag => tag.length > 0); // Kill empty tags.
   },
 
   async newFolder() {
     let ip = this._folderTree.insertionPoint;
 
     // default to the bookmarks menu folder
-    if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
+    if (!ip) {
       ip = new InsertionPoint({
         parentId: PlacesUtils.bookmarksMenuFolderId,
         parentGuid: PlacesUtils.bookmarks.menuGuid
       });
     }
 
     // XXXmano: add a separate "New Folder" string at some point...
     let title = this._element("newFolderButton").label;
--- a/suite/common/places/content/places.js
+++ b/suite/common/places/content/places.js
@@ -37,55 +37,93 @@ var PlacesOrganizer = {
     "editBMPanel_keywordRow",
   ],
 
   _initFolderTree() {
     var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
     this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
   },
 
-  selectLeftPaneBuiltIn(aQueryName) {
-    var itemId = PlacesUIUtils.leftPaneQueries[aQueryName];
-    this._places.selectItems([itemId]);
-    // Forcefully expand all-bookmarks
-    if (aQueryName == "AllBookmarks" || aQueryName == "History")
-      PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+  /**
+   * Selects a left pane built-in item.
+   *
+   * @param {String} item The built-in item to select, may be one of (case sensitive):
+   *                      AllBookmarks, BookmarksMenu, BookmarksToolbar,
+   *                      History, Tags, UnfiledBookmarks.
+   */
+  selectLeftPaneBuiltIn(item) {
+    switch (item) {
+      case "AllBookmarks":
+      case "History":
+      case "Tags": {
+        var itemId = PlacesUIUtils.leftPaneQueries[item];
+        this._places.selectItems([itemId]);
+        // Forcefully expand all-bookmarks
+        if (item == "AllBookmarks" || item == "History")
+          PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+        break;
+      }
+      case "BookmarksMenu":
+        this.selectLeftPaneContainerByHierarchy([
+          PlacesUIUtils.leftPaneQueries.AllBookmarks,
+          PlacesUtils.bookmarks.virtualMenuGuid
+        ]);
+        break;
+      case "BookmarksToolbar":
+        this.selectLeftPaneContainerByHierarchy([
+          PlacesUIUtils.leftPaneQueries.AllBookmarks,
+          PlacesUtils.bookmarks.virtualToolbarGuid
+        ]);
+        break;
+      case "UnfiledBookmarks":
+        this.selectLeftPaneContainerByHierarchy([
+          PlacesUIUtils.leftPaneQueries.AllBookmarks,
+          PlacesUtils.bookmarks.virtualUnfiledGuid
+        ]);
+        break;
+      default:
+        throw new Error(`Unrecognized item ${item} passed to selectLeftPaneRootItem`);
+    }
   },
 
   /**
    * Opens a given hierarchy in the left pane, stopping at the last reachable
-   * container.
+   * container. Note: item ids should be considered deprecated.
    *
    * @param aHierarchy A single container or an array of containers, sorted from
    *                   the outmost to the innermost in the hierarchy. Each
    *                   container may be either an item id, a Places URI string,
-   *                   or a named query.
+   *                   or a named query, like:
+   *                   "BookmarksMenu", "BookmarksToolbar", "UnfiledBookmarks", "AllBookmarks".
    * @see PlacesUIUtils.leftPaneQueries for supported named queries.
    */
-  selectLeftPaneContainerByHierarchy:
-  function PO_selectLeftPaneContainerByHierarchy(aHierarchy) {
+  selectLeftPaneContainerByHierarchy(aHierarchy) {
     if (!aHierarchy)
-      throw new Error("Invalid containers hierarchy");
+      throw new Error("Containers hierarchy not specified");
     let hierarchy = [].concat(aHierarchy);
     let selectWasSuppressed = this._places.view.selection.selectEventsSuppressed;
     if (!selectWasSuppressed)
       this._places.view.selection.selectEventsSuppressed = true;
     try {
       for (let container of hierarchy) {
         switch (typeof container) {
           case "number":
             this._places.selectItems([container], false);
             break;
           case "string":
-            if (container.substr(0, 6) == "place:")
-              this._places.selectPlaceURI(container);
-            else if (container in PlacesUIUtils.leftPaneQueries)
+            try {
               this.selectLeftPaneBuiltIn(container);
-            else
-              throw new Error("Invalid container found: " + container);
+            } catch (ex) {
+              if (container.substr(0, 6) == "place:") {
+                this._places.selectPlaceURI(container);
+              } else {
+                // May be a guid.
+                this._places.selectItems([container], false);
+              }
+            }
             break;
           default:
             throw new Error("Invalid container type found: " + container);
         }
         PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
       }
     } finally {
       if (!selectWasSuppressed)
--- a/suite/common/places/content/tree.xml
+++ b/suite/common/places/content/tree.xml
@@ -71,17 +71,18 @@
           var queryNode = PlacesUtils.asQuery(this.result.root);
           var options = queryNode.queryOptions.clone();
 
           // Make sure we're getting uri results.
           // We do not yet support searching into grouped queries or into
           // tag containers, so we must fall to the default case.
           if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
               options.resultType == options.RESULTS_AS_TAG_QUERY ||
-              options.resultType == options.RESULTS_AS_TAG_CONTENTS)
+              options.resultType == options.RESULTS_AS_TAG_CONTENTS ||
+              options.resultType == options.RESULTS_AS_ROOTS_QUERY)
             options.resultType = options.RESULTS_AS_URI;
 
           var query = PlacesUtils.history.getNewQuery();
           query.searchTerms = filterString;
 
           if (folderRestrict) {
             query.setFolders(folderRestrict, folderRestrict.length);
             options.queryType = options.QUERY_TYPE_BOOKMARKS;
@@ -616,18 +617,22 @@
               ids.splice(index, 1);
             }
 
             var concreteGuid = PlacesUtils.getConcreteItemGuid(node);
             if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
                 checkedGuidsSet.has(concreteGuid))
               return foundOne;
 
-            // Only follow a query if it has been been explicitly opened by the caller.
-            let shouldOpen = aOpenContainers && PlacesUtils.nodeIsFolder(node);
+            // Only follow a query if it has been been explicitly opened by the
+            // caller. We support the "AllBookmarks" case to allow callers to
+            // specify just the top-level bookmark folders.
+            let shouldOpen = aOpenContainers && (PlacesUtils.nodeIsFolder(node) ||
+              (PlacesUtils.nodeIsQuery(node) && node.itemId == PlacesUIUtils.leftPaneQueries.AllBookmarks));
+
             PlacesUtils.asContainer(node);
             if (!node.containerOpen && !shouldOpen)
               return foundOne;
 
             checkedGuidsSet.add(concreteGuid);
 
             // Remember the beginning state so that we can re-close
             // this node if we don't find any additional results here.
--- a/suite/common/places/content/treeView.js
+++ b/suite/common/places/content/treeView.js
@@ -132,16 +132,17 @@ PlacesTreeView.prototype = {
     if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
       return false;
 
     switch (aContainer.queryOptions.resultType) {
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
       case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY:
+      case Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY:
         return false;
     }
 
     // If it's a folder, it's not a plain container.
     let nodeType = aContainer.type;
     return nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER &&
            nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
   },
@@ -186,22 +187,26 @@ PlacesTreeView.prototype = {
     }
 
     // Ensure that the entire chain is open, otherwise that node is invisible.
     for (let ancestor of ancestors) {
       if (!ancestor.containerOpen)
         throw new Error("Invisible node passed to _getRowForNode");
     }
 
-    // Non-plain containers are initially built with their contents.
+    // Non-plain containers, and non-Roots queries are initially built with their
+    // contents.
     let parent = aNode.parent;
     let parentIsPlain = this._isPlainContainer(parent);
-    if (!parentIsPlain) {
-      if (parent == this._rootNode)
+    if (!parentIsPlain &&
+        parent.queryOptions.resultType !=
+        Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY) {
+      if (parent == this._rootNode) {
         return this._rows.indexOf(aNode);
+      }
 
       return this._rows.indexOf(aNode, aParentRow);
     }
 
     let row = -1;
     let useNodeIndex = typeof(aNodeIndex) == "number";
     if (parent == this._rootNode) {
       row = useNodeIndex ? aNodeIndex : this._rootNode.getChildIndex(aNode);
@@ -1271,31 +1276,45 @@ PlacesTreeView.prototype = {
           if (PlacesUtils.nodeIsTagQuery(node))
             properties += " tagContainer";
           else if (PlacesUtils.nodeIsDay(node))
             properties += " dayContainer";
           else if (PlacesUtils.nodeIsHost(node))
             properties += " hostContainer";
         } else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
                  nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
-          if (this._controller.hasCachedLivemarkInfo(node)) {
-            properties += " livemark";
-          } else {
-            PlacesUtils.livemarks.getLivemark({ id: node.itemId })
-              .then(aLivemark => {
-                this._controller.cacheLivemarkInfo(node, aLivemark);
-                let livemarkProps = this._cellProperties.get(node);
-                this._cellProperties.set(node, livemarkProps += " livemark");
-                // The livemark attribute is set as a cell property on the title cell.
-                this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
-              }, () => undefined);
+          if (itemId != -1) {
+            if (this._controller.hasCachedLivemarkInfo(node)) {
+              properties += " livemark";
+            } else {
+              PlacesUtils.livemarks.getLivemark({ id: itemId })
+                .then(aLivemark => {
+                  this._controller.cacheLivemarkInfo(node, aLivemark);
+                  let livemarkProps = this._cellProperties.get(node);
+                  this._cellProperties.set(node, livemarkProps += " livemark");
+                  // The livemark attribute is set as a cell property on the title cell.
+                  this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
+                }, () => undefined);
+            }
           }
         }
 
-        if (itemId != -1) {
+        if (itemId == -1) {
+          switch (node.bookmarkGuid) {
+          case PlacesUtils.bookmarks.virtualToolbarGuid:
+            properties += ` queryFolder_${PlacesUtils.bookmarks.toolbarGuid}`;
+            break;
+          case PlacesUtils.bookmarks.virtualMenuGuid:
+            properties += ` queryFolder_${PlacesUtils.bookmarks.menuGuid}`;
+            break;
+          case PlacesUtils.bookmarks.virtualUnfiledGuid:
+            properties += ` queryFolder_${PlacesUtils.bookmarks.unfiledGuid}`;
+            break;
+          }
+        } else {
           let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
           if (queryName)
             properties += " OrganizerQuery_" + queryName;
         }
       } else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
         properties += " separator";
       else if (PlacesUtils.nodeIsURI(node)) {
         properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri);
@@ -1759,22 +1778,22 @@ PlacesTreeView.prototype = {
     // * places-roots
     // * the left pane special folders and queries (those are place: uri
     //   bookmarks)
     // * separators
     //
     // Note that concrete itemIds aren't used intentionally.  For example, we
     // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar,
     // except for the one under All Bookmarks.
-    if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemGuid))
+    if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemGuid) ||
+        PlacesUtils.isQueryGeneratedFolder(itemGuid))
       return false;
 
     let parentId = PlacesUtils.getConcreteItemId(node.parent);
-    if (parentId == PlacesUIUtils.leftPaneFolderId ||
-        parentId == PlacesUIUtils.allBookmarksFolderId) {
+    if (parentId == PlacesUIUtils.leftPaneFolderId) {
       // Note that the for the time being this is the check that actually
       // blocks renaming places "roots", and not the isRootItem check above.
       // That's because places root are only exposed through folder shortcuts
       // descendants of the left pane folder.
       return false;
     }
 
     return true;
--- a/suite/themes/classic/communicator/places/bookmarks.css
+++ b/suite/themes/classic/communicator/places/bookmarks.css
@@ -51,47 +51,47 @@ treechildren::-moz-tree-image(title, liv
   list-style-image: url("chrome://communicator/skin/places/bookmark-item-updated.png");
 }
 
 .bookmark-item[container][livemark] .bookmark-item[visited],
 treechildren::-moz-tree-image(title, livemarkItem, visited) {
   list-style-image: url("chrome://communicator/skin/places/bookmark-item.svg");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
-  list-style-image: url("chrome://communicator/skin/places/allBookmarks.png");
-  -moz-image-region: auto;
-}
-
 #bookmarksToolbarFolderMenu,
 #BMB_bookmarksToolbarFolderMenu,
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
   list-style-image: url("chrome://communicator/skin/places/bookmarksToolbar.png");
   -moz-image-region: auto;
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+treechildren::-moz-tree-image(container, queryFolder_menu________) {
   list-style-image: url("chrome://communicator/skin/places/bookmarksMenu.png");
   -moz-image-region: auto;
 }
 
 #unsortedBookmarksFolderMenu,
 #BMB_unsortedBookmarksFolderMenu,
-treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+treechildren::-moz-tree-image(container, queryFolder_unfiled_____) {
   list-style-image: url("chrome://communicator/skin/places/unsortedBookmarks.png");
   -moz-image-region: auto;
 }
 
 /* query-nodes should be styled even if they're not expandable */
 .bookmark-item[container][query],
 treechildren::-moz-tree-image(query) {
   list-style-image: url("chrome://communicator/skin/places/query.png");
   -moz-image-region: auto;
 }
 
+treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
+  list-style-image: url("chrome://communicator/skin/places/allBookmarks.png");
+  -moz-image-region: auto;
+}
+
 .bookmark-item[query][tagContainer],
 treechildren::-moz-tree-image(title, query, tagContainer),
 treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
   list-style-image: url("chrome://communicator/skin/places/tag.png");
   -moz-image-region: auto;
 }
 
 /* calendar icon for history grouping items by day */
--- a/suite/themes/classic/mac/communicator/places/bookmarks.css
+++ b/suite/themes/classic/mac/communicator/places/bookmarks.css
@@ -45,42 +45,43 @@ treechildren::-moz-tree-image(title, liv
   list-style-image: url("chrome://communicator/skin/places/bookmark-item-updated.png");
 }
 
 .bookmark-item[container][livemark] .bookmark-item[visited],
 treechildren::-moz-tree-image(title, livemarkItem, visited) {
   list-style-image: url("chrome://communicator/skin/places/bookmark-item.svg");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
-  list-style-image: url("chrome://communicator/skin/places/allBookmarks.png");
-}
-
 #bookmarksToolbarFolderMenu,
 #BMB_bookmarksToolbarFolderMenu,
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
   list-style-image: url("chrome://communicator/skin/places/bookmarksToolbar.png");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+treechildren::-moz-tree-image(container, queryFolder_menu________) {
   list-style-image: url("chrome://communicator/skin/places/bookmarksMenu.png");
 }
 
 #unsortedBookmarksFolderMenu,
 #BMB_unsortedBookmarksFolderMenu,
-treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+treechildren::-moz-tree-image(container, queryFolder_unfiled_____) {
   list-style-image: url("chrome://communicator/skin/places/unsortedBookmarks.png");
 }
 
 /* query-nodes should be styled even if they're not expandable */
 .bookmark-item[query],
 treechildren::-moz-tree-image(query) {
   list-style-image: url("chrome://communicator/skin/places/query.png");
 }
 
+treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
+  list-style-image: url("chrome://communicator/skin/places/allBookmarks.png");
+}
+
+
 .bookmark-item[query][tagContainer],
 treechildren::-moz-tree-image(title, query, tagContainer),
 treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
   list-style-image: url("chrome://communicator/skin/places/tag.png");
 }
 
 /* calendar icon for history grouping items by day */
 treechildren::-moz-tree-image(title, query, dayContainer) {
--- a/suite/themes/modern/communicator/places/bookmarks.css
+++ b/suite/themes/modern/communicator/places/bookmarks.css
@@ -53,47 +53,47 @@ treechildren::-moz-tree-image(title, liv
   list-style-image: url("chrome://communicator/skin/places/bookmark-item-updated.gif");
 }
 
 .bookmark-item[container][livemark] .bookmark-item[visited],
 treechildren::-moz-tree-image(title, livemarkItem, visited) {
   list-style-image: url("chrome://communicator/skin/places/bookmark-item.svg");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
-  list-style-image: url("chrome://communicator/skin/places/allBookmarks.png");
-  -moz-image-region: auto;
-}
-
 #bookmarksToolbarFolderMenu,
 #BMB_bookmarksToolbarFolderMenu,
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
   list-style-image: url("chrome://communicator/skin/places/bookmarksToolbar.png");
   -moz-image-region: auto;
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+treechildren::-moz-tree-image(container, queryFolder_menu________) {
   list-style-image: url("chrome://communicator/skin/places/bookmarksMenu.png");
   -moz-image-region: auto;
 }
 
 #unsortedBookmarksFolderMenu,
 #BMB_unsortedBookmarksFolderMenu,
-treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+treechildren::-moz-tree-image(container, queryFolder_unfiled_____) {
   list-style-image: url("chrome://communicator/skin/places/unsortedBookmarks.png");
   -moz-image-region: auto;
 }
 
 /* query-nodes should be styled even if they're not expandable */
 .bookmark-item[container][query],
 treechildren::-moz-tree-image(query) {
   list-style-image: url("chrome://communicator/skin/places/query.png");
   -moz-image-region: auto;
 }
 
+treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
+  list-style-image: url("chrome://communicator/skin/places/allBookmarks.png");
+  -moz-image-region: auto;
+}
+
 .bookmark-item[query][tagContainer],
 treechildren::-moz-tree-image(title, query, tagContainer),
 treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
   list-style-image: url("chrome://communicator/skin/places/tag.png");
   -moz-image-region: auto;
 }
 
 /* calendar icon for folders grouping items by day */