Bug 1423896 - Make the All Bookmarks folder for the left pane of the Library a virtual query. r?mak draft
authorMark Banner <standard8@mozilla.com>
Thu, 07 Dec 2017 14:15:39 +0000
changeset 751996 1c8a09475b3b88b3a1ef2b67be79217e03f62e17
parent 751995 a17b935129df319cd8edce3ff05d184042d2dae2
child 751997 69bcdf2f9b23e037fbc24d02ca635ce45fa596d4
child 752055 1bf7c9fcee805a52837fdf6a559c664b23b527fd
push id98127
push userbmo:standard8@mozilla.com
push dateWed, 07 Feb 2018 11:39:15 +0000
reviewersmak
bugs1423896
milestone60.0a1
Bug 1423896 - Make the All Bookmarks folder for the left pane of the Library a virtual query. r?mak MozReview-Commit-ID: HzJ9y1fiEz1
browser/base/content/browser-places.js
browser/components/places/PlacesUIUtils.jsm
browser/components/places/content/bookmarksPanel.js
browser/components/places/content/controller.js
browser/components/places/content/editBookmarkOverlay.js
browser/components/places/content/places.js
browser/components/places/content/tree.xml
browser/components/places/content/treeView.js
browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
browser/components/places/tests/browser/browser_bookmark_folder_moveability.js
browser/components/places/tests/browser/browser_library_commands.js
browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
browser/components/places/tests/browser/browser_library_openFlatContainer.js
browser/components/places/tests/browser/browser_library_search.js
browser/components/places/tests/browser/head.js
browser/components/places/tests/chrome/test_0_bug510634.xul
browser/components/places/tests/unit/test_leftpane_corruption_handling.js
browser/components/preferences/selectBookmark.js
browser/themes/shared/places/tree-icons.inc.css
services/sync/tests/unit/test_bookmark_repair_responder.js
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/PlacesSyncUtils.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsINavHistoryService.idl
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistoryQuery.cpp
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/tests/queries/test_results-as-roots.js
toolkit/components/places/tests/queries/xpcshell.ini
toolkit/components/places/tests/unit/test_sync_utils.js
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -579,30 +579,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, Tags and Downloads.
+   * @param {String} item The item to select in the organizer window,
+   *                      options are (case sensitive):
+   *                      BookmarksMenu, BookmarksToolbar, UnfiledBookmarks,
+   *                      AllBookmarks, History, Downloads.
    */
-  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://browser/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();
     }
   },
 
   searchBookmarks() {
     if (!focusAndSelectUrlBar()) {
       return;
     }
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -200,17 +200,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
@@ -508,16 +508,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);
     }
@@ -558,32 +568,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;
@@ -834,44 +843,31 @@ 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") },
       "Downloads": { title: this.getString("OrganizerQueryDownloads") },
       "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 = 7;
+    const EXPECTED_QUERY_COUNT = 4;
 
     // 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.
@@ -1048,47 +1044,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/browser/components/places/content/bookmarksPanel.js
+++ b/browser/components/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/browser/components/places/content/controller.js
+++ b/browser/components/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/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/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;
 
@@ -699,23 +698,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,
@@ -924,17 +923,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/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -36,55 +36,94 @@ 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, Downloads, Tags, UnfiledBookmarks.
+   */
+  selectLeftPaneBuiltIn(item) {
+    switch (item) {
+      case "AllBookmarks":
+      case "History":
+      case "Downloads":
+      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/browser/components/places/content/tree.xml
+++ b/browser/components/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/browser/components/places/content/treeView.js
+++ b/browser/components/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/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
@@ -24,16 +24,12 @@ add_task(async function() {
         let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
         Assert.ok(namepicker.readOnly, "Name field is read-only");
         let bookmark = await PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid);
         Assert.equal(namepicker.value, bookmark.title, "Node title is correct");
         // Blur the field and ensure root's name has not been changed.
         namepicker.blur();
         bookmark = await PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid);
         Assert.equal(namepicker.value, bookmark.title, "Root title is correct");
-        // Check the shortcut's title.
-        info(tree.selectedNode.bookmarkGuid);
-        bookmark = await PlacesUtils.bookmarks.fetch(tree.selectedNode.bookmarkGuid);
-        Assert.equal(bookmark.title, "", "Shortcut title is null");
       }
     );
   });
 });
--- a/browser/components/places/tests/browser/browser_bookmark_folder_moveability.js
+++ b/browser/components/places/tests/browser/browser_bookmark_folder_moveability.js
@@ -80,41 +80,31 @@ add_task(async function() {
     Assert.equal(tree.selectedNode.childCount, 1, "has tags");
     let tagNode = tree.selectedNode.getChild(0);
     Assert.ok(!PlacesControllerDragHelper.canMoveNode(tagNode, tree),
               "should not be able to move tag container node");
     tree.selectedNode.containerOpen = false;
 
     info("Test that special folders and cannot be moved but other shortcuts can.");
     let roots = [
-      PlacesUtils.bookmarksMenuFolderId,
-      PlacesUtils.unfiledBookmarksFolderId,
-      PlacesUtils.toolbarFolderId,
+      PlacesUtils.bookmarks.menuGuid,
+      PlacesUtils.bookmarks.unfiledGuid,
+      PlacesUtils.bookmarks.toolbarGuid,
     ];
 
-    for (let id of roots) {
-      selectShortcutForRootId(tree, id);
+    for (let guid of roots) {
+      tree.selectItems([guid]);
       Assert.ok(!PlacesControllerDragHelper.canMoveNode(tree.selectedNode, tree),
                 "shouldn't be able to move default shortcuts to roots");
+      let id = await PlacesUtils.promiseItemId(guid);
       let s = await PlacesUtils.bookmarks.insert({
         parentGuid: root.guid,
         title: "bar",
         url: `place:folder=${id}`,
       });
       tree.selectItems([s.guid]);
       Assert.equal(tree.selectedNode.bookmarkGuid, s.guid,
                    "Selected the expected node");
       Assert.ok(PlacesControllerDragHelper.canMoveNode(tree.selectedNode, tree),
                 "should be able to move user-created shortcuts to roots");
     }
   });
 });
-
-function selectShortcutForRootId(tree, id) {
-  for (let i = 0; i < tree.result.root.childCount; ++i) {
-    let child = tree.result.root.getChild(i);
-    if (PlacesUtils.getConcreteItemId(child) == id) {
-      tree.selectItems([child.itemId]);
-      return;
-    }
-  }
-  Assert.ok(false, "Cannot find shortcut to root");
-}
--- a/browser/components/places/tests/browser/browser_library_commands.js
+++ b/browser/components/places/tests/browser/browser_library_commands.js
@@ -83,17 +83,17 @@ add_task(async function test_query_on_to
 
   PO.selectLeftPaneBuiltIn("BookmarksToolbar");
   isnot(PO._places.selectedNode, null, "We have a valid selection");
   is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
      PlacesUtils.toolbarFolderId,
      "We have correctly selected bookmarks toolbar node.");
 
   // Check that both cut and delete commands are disabled, cause this is a child
-  // of AllBookmarksFolderId.
+  // of the All Bookmarks special query.
   ok(PO._places.controller.isCommandEnabled("cmd_copy"),
      "Copy command is enabled");
   ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
      "Cut command is disabled");
   ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
      "Delete command is disabled");
 
   let toolbarNode = PlacesUtils.asContainer(PO._places.selectedNode);
--- a/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
+++ b/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
@@ -55,34 +55,19 @@ function test() {
   for (var i = 0; i < items.length; i++) {
     var itemId = items[i];
     var queryName = PlacesUtils.annotations
                                .getItemAnnotation(items[i],
                                                   PlacesUIUtils.ORGANIZER_QUERY_ANNO);
     var query = { name: queryName,
                   itemId,
                   correctTitle: PlacesUtils.bookmarks.getItemTitle(itemId) };
-    switch (queryName) {
-      case "BookmarksToolbar":
-        query.concreteId = PlacesUtils.toolbarFolderId;
-        query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
-        break;
-      case "BookmarksMenu":
-        query.concreteId = PlacesUtils.bookmarksMenuFolderId;
-        query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
-        break;
-      case "UnfiledBookmarks":
-        query.concreteId = PlacesUtils.unfiledBookmarksFolderId;
-        query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
-        break;
-    }
+
     leftPaneQueries.push(query);
     // Rename to a bad title.
     PlacesUtils.bookmarks.setItemTitle(query.itemId, "badName");
-    if ("concreteId" in query)
-      PlacesUtils.bookmarks.setItemTitle(query.concreteId, "badName");
   }
 
   restoreLeftPaneGetters();
 
   // Open Library, this will kick-off left pane code.
   openLibrary(onLibraryReady);
 }
--- a/browser/components/places/tests/browser/browser_library_openFlatContainer.js
+++ b/browser/components/places/tests/browser/browser_library_openFlatContainer.js
@@ -15,26 +15,24 @@ add_task(async function() {
       type: PlacesUtils.bookmarks.TYPE_FOLDER,
       children: [{
         title: "Bookmark",
         url: "http://example.com",
       }],
     }],
   });
 
-  let library = await promiseLibrary("AllBookmarks");
+  let library = await promiseLibrary("UnfiledBookmarks");
   registerCleanupFunction(async function() {
     await promiseLibraryClosed(library);
     await PlacesUtils.bookmarks.eraseEverything();
   });
 
-  // Select unfiled later, to ensure it's closed.
-  library.PlacesOrganizer.selectLeftPaneBuiltIn("UnfiledBookmarks");
-  ok(!library.PlacesOrganizer._places.selectedNode.containerOpen,
-     "Unfiled container is closed");
+  // Ensure the container is closed.
+  library.PlacesOrganizer._places.selectedNode.containerOpen = false;
 
   let folderNode = library.ContentTree.view.view.nodeForTreeIndex(0);
   is(folderNode.bookmarkGuid, bookmarks[0].guid,
      "Found the expected folder in the right pane");
   // Select the folder node in the right pane.
   library.ContentTree.view.selectNode(folderNode);
 
   synthesizeClickOnSelectedTreeCell(library.ContentTree.view,
--- a/browser/components/places/tests/browser/browser_library_search.js
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -27,17 +27,17 @@
 
 const TEST_URL = "http://dummy.mozilla.org/";
 const TEST_DOWNLOAD_URL = "http://dummy.mozilla.org/dummy.pdf";
 
 var gLibrary;
 
 var testCases = [
   function allBookmarksScope() {
-    let defScope = getDefaultScope(PlacesUIUtils.allBookmarksFolderId);
+    let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.AllBookmarks);
     search(PlacesUIUtils.allBookmarksFolderId, "dummy", defScope);
   },
 
   function historyScope() {
     let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.History);
     search(PlacesUIUtils.leftPaneQueries.History, "dummy", defScope);
   },
 
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -1,18 +1,17 @@
 ChromeUtils.defineModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "TestUtils",
   "resource://testing-common/TestUtils.jsm");
 
 // We need to cache these before test runs...
-let leftPaneGetters = new Map([["leftPaneFolderId", null],
-                               ["allBookmarksFolderId", null]]);
+let leftPaneGetters = new Map([["leftPaneFolderId", null]]);
 for (let [key, val] of leftPaneGetters) {
   if (!val) {
     let getter = Object.getOwnPropertyDescriptor(PlacesUIUtils, key).get;
     if (typeof getter == "function") {
       leftPaneGetters.set(key, getter);
     }
   }
 }
--- a/browser/components/places/tests/chrome/test_0_bug510634.xul
+++ b/browser/components/places/tests/chrome/test_0_bug510634.xul
@@ -41,18 +41,17 @@
      * even if PlacesUIUtils.leftPaneFolderId was not initialized.
      */
 
     SimpleTest.waitForExplicitFinish();
 
     function runTest() {
       // We need to cache and restore the getters in order to simulate
       // Bug 510634.
-      let leftPaneGetters = new Map([["leftPaneFolderId", null],
-                               ["allBookmarksFolderId", null]]);
+      let leftPaneGetters = new Map([["leftPaneFolderId", null]]);
       for (let [key, val] of leftPaneGetters) {
         if (!val) {
           let getter = Object.getOwnPropertyDescriptor(PlacesUIUtils, key).get;
           if (typeof getter == "function") {
             leftPaneGetters.set(key, getter);
           }
         }
       }
@@ -73,30 +72,44 @@
       tree.place = "place:queryType=1&folder=" + leftPaneFolderId;
 
       // The query-property is set on the title column for each row.
       let titleColumn = tree.treeBoxObject.columns.getColumnAt(0);
 
       // Open All Bookmarks
       tree.selectItems([PlacesUIUtils.leftPaneQueries["AllBookmarks"]]);
       PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
-      is(PlacesUIUtils.allBookmarksFolderId, tree.selectedNode.itemId,
+      is(tree.selectedNode.uri,
+         "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY +
+         "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
          "Opened All Bookmarks");
 
-      ["History", "Downloads", "Tags", "AllBookmarks", "BookmarksToolbar",
-       "BookmarksMenu", "UnfiledBookmarks"].forEach(
-        function(aQueryName, aRow) {
-          let found = false;
-          for (let i = 0; i < tree.view.rowCount && !found; i++) {
-            rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
-            found = rowProperties.includes("OrganizerQuery_" + aQueryName);
-          }
-          ok(found, "OrganizerQuery_" + aQueryName + " is set");
+      for (let queryName of ["History", "Downloads", "Tags", "AllBookmarks"]) {
+        let found = false;
+        for (let i = 0; i < tree.view.rowCount && !found; i++) {
+          rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
+          found = rowProperties.includes("OrganizerQuery_" + queryName);
         }
-      );
+        ok(found, "OrganizerQuery_" + queryName + " is set");
+      }
+
+      const folderGuids = [
+        PlacesUtils.bookmarks.toolbarGuid,
+        PlacesUtils.bookmarks.menuGuid,
+        PlacesUtils.bookmarks.unfiledGuid,
+      ];
+
+      for (let guid of folderGuids) {
+        let found = false;
+        for (let i = 0; i < tree.view.rowCount && !found; i++) {
+          rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
+          found = rowProperties.includes("queryFolder_" + guid);
+        }
+        ok(found, "queryFolder_" + guid + " is set");
+      }
 
       // Close the root node
       tree.result.root.containerOpen = false;
 
       // Restore the getters for the next test.
       restoreLeftPaneGetters();
 
       SimpleTest.finish();
--- a/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
+++ b/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
@@ -5,33 +5,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Tests that we build a working leftpane in various corruption situations.
  */
 
 // Used to store the original leftPaneFolderId getter.
 var gLeftPaneFolderIdGetter;
-var gAllBookmarksFolderIdGetter;
 // Used to store the original left Pane status as a JSON string.
 var gReferenceHierarchy;
 var gLeftPaneFolderId;
 
 add_task(async function() {
   // We want empty roots.
   await PlacesUtils.bookmarks.eraseEverything();
 
   // Sanity check.
   Assert.ok(!!PlacesUIUtils);
 
   // Check getters.
   gLeftPaneFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId");
   Assert.equal(typeof(gLeftPaneFolderIdGetter.get), "function");
-  gAllBookmarksFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "allBookmarksFolderId");
-  Assert.equal(typeof(gAllBookmarksFolderIdGetter.get), "function");
 
   registerCleanupFunction(() => PlacesUtils.bookmarks.eraseEverything());
 });
 
 add_task(async function() {
   // Add a third party bogus annotated item.  Should not be removed.
   let folder = await PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
@@ -47,22 +44,21 @@ add_task(async function() {
 
   // Create the left pane, and store its current status, it will be used
   // as reference value.
   gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
   gReferenceHierarchy = folderIdToHierarchy(gLeftPaneFolderId);
 
   while (gTests.length) {
     // Run current test.
-    await gTests.shift();
+    await gTests.shift()();
 
     // Regenerate getters.
     Object.defineProperty(PlacesUIUtils, "leftPaneFolderId", gLeftPaneFolderIdGetter);
     gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
-    Object.defineProperty(PlacesUIUtils, "allBookmarksFolderId", gAllBookmarksFolderIdGetter);
 
     // Check the new left pane folder.
     let leftPaneHierarchy = folderIdToHierarchy(gLeftPaneFolderId);
     Assert.equal(gReferenceHierarchy, leftPaneHierarchy);
 
     folder = await PlacesUtils.bookmarks.fetch({guid: folder.guid});
     Assert.equal(folder.title, "test");
   }
@@ -84,70 +80,50 @@ var gTests = [
   async function test3() {
     print("3. Delete a child of the left pane folder.");
     let guid = await PlacesUtils.promiseItemGuid(gLeftPaneFolderId);
     let bm = await PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0});
     await PlacesUtils.bookmarks.remove(bm.guid);
   },
 
   async function test4() {
-    print("4. Delete AllBookmarks.");
-    let guid = await PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId);
-    await PlacesUtils.bookmarks.remove(guid);
-  },
-
-  async function test5() {
-    print("5. Create a duplicated left pane folder.");
+    print("4. Create a duplicated left pane folder.");
     let folder = await PlacesUtils.bookmarks.insert({
       parentGuid: PlacesUtils.bookmarks.unfiledGuid,
       title: "PlacesRoot",
       index: PlacesUtils.bookmarks.DEFAULT_INDEX,
       type: PlacesUtils.bookmarks.TYPE_FOLDER
     });
 
     let folderId = await PlacesUtils.promiseItemId(folder.guid);
     PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_FOLDER_ANNO,
                                               "PlacesRoot", 0,
                                               PlacesUtils.annotations.EXPIRE_NEVER);
   },
 
-  async function test6() {
-    print("6. Create a duplicated left pane query.");
+  async function test5() {
+    print("5. Create a duplicated left pane query.");
     let folder = await PlacesUtils.bookmarks.insert({
       parentGuid: PlacesUtils.bookmarks.unfiledGuid,
       title: "AllBookmarks",
       index: PlacesUtils.bookmarks.DEFAULT_INDEX,
       type: PlacesUtils.bookmarks.TYPE_FOLDER
     });
 
     let folderId = await PlacesUtils.promiseItemId(folder.guid);
     PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO,
                                               "AllBookmarks", 0,
                                               PlacesUtils.annotations.EXPIRE_NEVER);
   },
 
-  function test7() {
-    print("7. Remove the left pane folder annotation.");
+  function test6() {
+    print("6. Remove the left pane folder annotation.");
     PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId,
                                                  ORGANIZER_FOLDER_ANNO);
   },
-
-  function test8() {
-    print("8. Remove a left pane query annotation.");
-    PlacesUtils.annotations.removeItemAnnotation(PlacesUIUtils.allBookmarksFolderId,
-                                                 ORGANIZER_QUERY_ANNO);
-  },
-
-  async function test9() {
-    print("9. Remove a child of AllBookmarks.");
-    let guid = await PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId);
-    let bm = await PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0});
-    await PlacesUtils.bookmarks.remove(bm.guid);
-  }
-
 ];
 
 /**
  * Convert a folder item id to a JSON representation of it and its contents.
  */
 function folderIdToHierarchy(aFolderId) {
   let root = PlacesUtils.getFolderContents(aFolderId).root;
   let hier = JSON.stringify(hierarchyToObj(root));
--- a/browser/components/preferences/selectBookmark.js
+++ b/browser/components/preferences/selectBookmark.js
@@ -14,17 +14,17 @@
  * a .names property.  This dialog is responsible for updating the contents of
  * the .urls property with an array of URLs to use as home pages and for
  * updating the .names property with an array of names for those URLs before it
  * closes.
  */
 var SelectBookmarkDialog = {
   init: function SBD_init() {
     document.getElementById("bookmarks").place =
-      "place:queryType=1&folder=" + PlacesUIUtils.allBookmarksFolderId;
+      "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
 
     // Initial update of the OK button.
     this.selectionChanged();
   },
 
   /**
    * Update the disabled state of the OK button as the user changes the
    * selection within the view.
--- a/browser/themes/shared/places/tree-icons.inc.css
+++ b/browser/themes/shared/places/tree-icons.inc.css
@@ -32,41 +32,41 @@ treechildren::-moz-tree-image(title, ope
 
 treechildren::-moz-tree-image(title, separator) {
   list-style-image: none;
   width: 0 !important;
   height: 0 !important;
   margin: 0;
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
-  list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
-}
-
 treechildren::-moz-tree-image(container, livemark) {
   list-style-image: url("chrome://browser/skin/places/folder-live.svg");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+treechildren::-moz-tree-image(container, queryFolder_toolbar_____) {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.svg");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+treechildren::-moz-tree-image(container, queryFolder_menu________) {
   list-style-image: url("chrome://browser/skin/places/bookmarksMenu.svg");
 }
 
-treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+treechildren::-moz-tree-image(container, queryFolder_unfiled_____) {
   list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.svg");
 }
 
 /* query-nodes should be styled even if they're not expandable */
 treechildren::-moz-tree-image(query) {
   list-style-image: url("chrome://browser/skin/places/folder-smart.svg");
 }
 
+treechildren::-moz-tree-image(query, OrganizerQuery_AllBookmarks) {
+  list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+}
+
 treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
   list-style-image: url("chrome://browser/skin/places/downloads.png");
 }
 
 treechildren::-moz-tree-image(title, query, tagContainer),
 treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
   list-style-image: url("chrome://browser/skin/places/tag.png");
 }
--- a/services/sync/tests/unit/test_bookmark_repair_responder.js
+++ b/services/sync/tests/unit/test_bookmark_repair_responder.js
@@ -275,107 +275,16 @@ add_task(async function test_responder_m
       value: undefined,
       extra: {flowID: request.flowID, numIDs: "2"},
     },
   ]);
 
   await cleanup(server);
 });
 
-add_task(async function test_non_syncable() {
-  let server = await makeServer();
-
-  await Service.sync(); // to create the collections on the server.
-
-  // Creates the left pane queries as a side effect.
-  let leftPaneId = PlacesUIUtils.leftPaneFolderId;
-  _(`Left pane root ID: ${leftPaneId}`);
-  await PlacesTestUtils.promiseAsyncUpdates();
-
-  // A child folder of the left pane root, containing queries for the menu,
-  // toolbar, and unfiled queries.
-  let allBookmarksId = PlacesUIUtils.leftPaneQueries.AllBookmarks;
-  let allBookmarksGuid = await PlacesUtils.promiseItemGuid(allBookmarksId);
-
-  let unfiledQueryId = PlacesUIUtils.leftPaneQueries.UnfiledBookmarks;
-  let unfiledQueryGuid = await PlacesUtils.promiseItemGuid(unfiledQueryId);
-
-  // Put the "Bookmarks Menu" on the server to simulate old bugs.
-  let bookmarksMenuQueryId = PlacesUIUtils.leftPaneQueries.BookmarksMenu;
-  let bookmarksMenuQueryGuid = await PlacesUtils.promiseItemGuid(bookmarksMenuQueryId);
-  let collection = getServerBookmarks(server);
-  collection.insert(bookmarksMenuQueryGuid, "doesn't matter");
-
-  // Explicitly request the unfiled and allBookmarksGuid queries; these will
-  // get tombstones. Because the BookmarksMenu is already on the server it
-  // should be removed even though it wasn't requested. We should ignore the
-  // toolbar query as it wasn't explicitly requested and isn't on the server.
-  let request = {
-    request: "upload",
-    ids: [allBookmarksGuid, unfiledQueryGuid],
-    flowID: Utils.makeGUID(),
-  };
-  let responder = new BookmarkRepairResponder();
-  await responder.repair(request, null);
-
-  checkRecordedEvents([
-    { object: "repairResponse",
-      method: "uploading",
-      value: undefined,
-      // Tombstones for the 2 items we requested and for bookmarksMenu
-      extra: {flowID: request.flowID, numIDs: "3"},
-    },
-  ]);
-
-  _("Sync to upload tombstones for items");
-  await Service.sync();
-
-  let toolbarQueryId = PlacesUIUtils.leftPaneQueries.BookmarksToolbar;
-  let menuQueryId = PlacesUIUtils.leftPaneQueries.BookmarksMenu;
-  let queryGuids = [
-    allBookmarksGuid,
-    await PlacesUtils.promiseItemGuid(toolbarQueryId),
-    await PlacesUtils.promiseItemGuid(menuQueryId),
-    unfiledQueryGuid,
-  ];
-
-  deepEqual(collection.keys().sort(), [
-    // We always upload roots on the first sync.
-    "menu",
-    "mobile",
-    "toolbar",
-    "unfiled",
-    ...request.ids,
-    bookmarksMenuQueryGuid,
-  ].sort(), "Should upload roots and queries on first sync");
-
-  for (let guid of queryGuids) {
-    let wbo = collection.wbo(guid);
-    if (request.ids.includes(guid) || guid == bookmarksMenuQueryGuid) {
-      // explicitly requested or already on the server, so should have a tombstone.
-      let payload = JSON.parse(JSON.parse(wbo.payload).ciphertext);
-      ok(payload.deleted, `Should upload tombstone for left pane query ${guid}`);
-    } else {
-      // not explicitly requested and not on the server at the start, so should
-      // not be on the server now.
-      ok(!wbo, `Should not upload anything for left pane query ${guid}`);
-    }
-  }
-
-  checkRecordedEvents([
-    { object: "repairResponse",
-      method: "finished",
-      value: undefined,
-      extra: {flowID: request.flowID, numIDs: "3"},
-    },
-  ]);
-
-  await cleanup(server);
-});
-
 add_task(async function test_folder_descendants() {
   let server = await makeServer();
 
   let parentFolder = await PlacesUtils.bookmarks.insert({
     type: PlacesUtils.bookmarks.TYPE_FOLDER,
     parentGuid: PlacesUtils.bookmarks.menuGuid,
     title: "Parent folder",
   });
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -130,32 +130,52 @@ var Bookmarks = Object.freeze({
     IMPORT_REPLACE: Ci.nsINavBookmarksService.SOURCE_IMPORT_REPLACE,
     SYNC_REPARENT_REMOVED_FOLDER_CHILDREN: Ci.nsINavBookmarksService.SOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
   },
 
   /**
    * Special GUIDs associated with bookmark roots.
    * It's guaranteed that the roots will always have these guids.
    */
+  rootGuid:    "root________",
+  menuGuid:    "menu________",
+  toolbarGuid: "toolbar_____",
+  unfiledGuid: "unfiled_____",
+  mobileGuid:  "mobile______",
 
-   rootGuid:    "root________",
-   menuGuid:    "menu________",
-   toolbarGuid: "toolbar_____",
-   unfiledGuid: "unfiled_____",
-   mobileGuid:  "mobile______",
+  // With bug 424160, tags will stop being bookmarks, thus this root will
+  // be removed.  Do not rely on this, rather use the tagging service API.
+  tagsGuid:    "tags________",
+
+  /**
+   * The GUIDs of the user content root folders that we support, for easy access
+   * as a set.
+   */
+  userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
 
-   // With bug 424160, tags will stop being bookmarks, thus this root will
-   // be removed.  Do not rely on this, rather use the tagging service API.
-   tagsGuid:    "tags________",
+  /**
+   * GUIDs associated with virtual queries that are used for display in the left
+   * pane.
+   */
+  virtualMenuGuid: "menu_______v",
+  virtualToolbarGuid: "toolbar____v",
+  virtualUnfiledGuid: "unfiled___v",
+  virtualMobileGuid: "mobile____v",
 
-   /**
-    * The GUIDs of the user content root folders that we support, for easy access
-    * as a set.
-    */
-   userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
+  /**
+   * Checks if a guid is a virtual root.
+   *
+   * @param {String} guid The guid of the item to look for.
+   * @returns {Boolean} true if guid is a virtual root, false otherwise.
+   */
+  isVirtualRootItem(guid) {
+    return guid == PlacesUtils.bookmarks.virtualMenuGuid ||
+           guid == PlacesUtils.bookmarks.virtualToolbarGuid ||
+           guid == PlacesUtils.bookmarks.virtualUnfiledGuid;
+  },
 
   /**
    * Inserts a bookmark-item into the bookmarks tree.
    *
    * For creating a bookmark, the following set of properties is required:
    *  - type
    *  - parentGuid
    *  - url, only for bookmarked URLs
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -44,19 +44,16 @@ var PlacesSyncUtils = {
   },
 };
 
 const { SOURCE_SYNC } = Ci.nsINavBookmarksService;
 
 const MICROSECONDS_PER_SECOND = 1000000;
 const SQLITE_MAX_VARIABLE_NUMBER = 999;
 
-const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
-const ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE = "AllBookmarks";
-const ORGANIZER_MOBILE_QUERY_ANNO_VALUE = "MobileBookmarks";
 const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";
 
 // These are defined as lazy getters to defer initializing the bookmarks
 // service until it's needed.
 XPCOMUtils.defineLazyGetter(this, "ROOT_RECORD_ID_TO_GUID", () => ({
   menu: PlacesUtils.bookmarks.menuGuid,
   places: PlacesUtils.bookmarks.rootGuid,
   tags: PlacesUtils.bookmarks.tagsGuid,
@@ -1056,80 +1053,25 @@ const BookmarkSyncUtils = PlacesSyncUtil
   async ensureMobileQuery() {
     let db = await PlacesUtils.promiseDBConnection();
 
     let mobileChildGuids = await fetchChildGuids(db, PlacesUtils.bookmarks.mobileGuid);
     let hasMobileBookmarks = mobileChildGuids.length > 0;
 
     Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, hasMobileBookmarks);
     if (hasMobileBookmarks) {
-      await this.upsertMobileQuery(db);
-    } else {
-      await this.removeMobileQuery(db);
-    }
-  },
-
-  async upsertMobileQuery(db) {
-    let maybeAllBookmarksGuids = await fetchGuidsWithAnno(db,
-      ORGANIZER_QUERY_ANNO, ORGANIZER_ALL_BOOKMARKS_ANNO_VALUE);
-    if (!maybeAllBookmarksGuids.length) {
-      return;
-    }
+      let mobileTitle = PlacesUtils.getString("MobileBookmarksFolderTitle");
 
-    let allBookmarksGuid = maybeAllBookmarksGuids[0];
-    let mobileTitle = PlacesUtils.getString("MobileBookmarksFolderTitle");
-
-    let maybeMobileQueryGuids = await fetchGuidsWithAnno(db,
-      ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE);
-    if (maybeMobileQueryGuids.length) {
-      let mobileQueryGuid = maybeMobileQueryGuids[0];
-      // We have a left pane query for mobile bookmarks, make sure the
-      // query title is correct.
+      // Make sure the mobile root title matches the query.
       await PlacesUtils.bookmarks.update({
-        guid: mobileQueryGuid,
-        url: "place:folder=MOBILE_BOOKMARKS",
+        guid: PlacesUtils.bookmarks.mobileGuid,
         title: mobileTitle,
         source: SOURCE_SYNC,
       });
-    } else {
-      // We have no left pane query. Create the query.
-      let mobileQuery = await PlacesUtils.bookmarks.insert({
-        parentGuid: allBookmarksGuid,
-        url: "place:folder=MOBILE_BOOKMARKS",
-        title: mobileTitle,
-        source: SOURCE_SYNC,
-      });
-
-      let mobileQueryId = await PlacesUtils.promiseItemId(mobileQuery.guid);
-
-      PlacesUtils.annotations.setItemAnnotation(mobileQueryId,
-        ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE, 0,
-        PlacesUtils.annotations.EXPIRE_NEVER, SOURCE_SYNC);
-      PlacesUtils.annotations.setItemAnnotation(mobileQueryId,
-        PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
-        PlacesUtils.annotations.EXPIRE_NEVER, SOURCE_SYNC);
     }
-
-    // Make sure the mobile root title matches the query.
-    await PlacesUtils.bookmarks.update({
-      guid: PlacesUtils.bookmarks.mobileGuid,
-      title: mobileTitle,
-      source: SOURCE_SYNC,
-    });
-  },
-
-  async removeMobileQuery(db) {
-    let maybeMobileQueryGuids = await fetchGuidsWithAnno(db,
-      ORGANIZER_QUERY_ANNO, ORGANIZER_MOBILE_QUERY_ANNO_VALUE);
-    if (!maybeMobileQueryGuids.length) {
-      BookmarkSyncLog.warn("Trying to remove non-existent mobile query");
-      return;
-    }
-    let mobileQueryGuid = maybeMobileQueryGuids[0];
-    await PlacesUtils.bookmarks.remove(mobileQueryGuid);
   },
 
   /**
    * Fetches an array of GUIDs for items that have an annotation set with the
    * given value.
    */
   async fetchGuidsWithAnno(anno, val) {
     let db = await PlacesUtils.promiseDBConnection();
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -108,17 +108,21 @@ function serializeNode(aNode, aIsLivemar
   data.title = aNode.title;
   data.id = aNode.itemId;
   data.livemark = aIsLivemark;
   // Add an instanceId so we can tell which instance of an FF session the data
   // is coming from.
   data.instanceId = PlacesUtils.instanceId;
 
   let guid = aNode.bookmarkGuid;
-  if (guid) {
+  // Some nodes, e.g. the unfiled/menu/toolbar ones can have a virtual guid, so
+  // we ignore any that are a folder shortcut. These will be handled below.
+  if (guid && !PlacesUtils.bookmarks.isVirtualRootItem(guid)) {
+    // TODO: Really guid should be set on everything, however currently this upsets
+    // the drag 'n' drop / cut/copy/paste operations.
     data.itemGuid = guid;
     if (aNode.parent)
       data.parent = aNode.parent.itemId;
     let grandParent = aNode.parent && aNode.parent.parent;
     if (grandParent)
       data.grandParentId = grandParent.itemId;
 
     data.dateAdded = aNode.dateAdded;
@@ -150,16 +154,17 @@ function serializeNode(aNode, aIsLivemar
     let concreteId = PlacesUtils.getConcreteItemId(aNode);
     if (concreteId != -1) {
       // This is a bookmark or a tag container.
       if (PlacesUtils.nodeIsQuery(aNode) || concreteId != aNode.itemId) {
         // This is a folder shortcut.
         data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
         data.uri = aNode.uri;
         data.concreteId = concreteId;
+        data.concreteGuid = PlacesUtils.getConcreteItemGuid(aNode);
       } else {
         // This is a bookmark folder.
         data.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
       }
     } else {
       // This is a grouped container query, dynamically generated.
       data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
       data.uri = aNode.uri;
@@ -428,16 +433,28 @@ this.PlacesUtils = {
     return str.split(",")
               .map(v => {
                 let bucket = v.split(":");
                 return [ bucket[0].trim().toLowerCase(), Number(bucket[1]) ];
               });
   },
 
   /**
+   * Determines if a folder is generated from a query.
+   * @param aNode a result true.
+   * @returns true if the node is a folder generated from a query.
+   */
+  isQueryGeneratedFolder(node) {
+    if (!node.parent) {
+      return false;
+    }
+    return this.nodeIsFolder(node) && this.nodeIsQuery(node.parent);
+  },
+
+  /**
    * Determines whether or not a ResultNode is a Bookmark folder.
    * @param   aNode
    *          A result node
    * @returns true if the node is a Bookmark folder, false otherwise
    */
   nodeIsFolder: function PU_nodeIsFolder(aNode) {
     return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
             aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT);
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -1069,16 +1069,24 @@ interface nsINavHistoryQueryOptions : ns
    * modified bookmarks for the given tag.
    * Tag folder id must be defined in the query.
    *
    * @note Setting this resultType will force queryType to QUERY_TYPE_BOOKMARKS.
    */
   const unsigned short RESULTS_AS_TAG_CONTENTS = 7;
 
   /**
+   * This returns nsINavHistoryQueryResultNode nodes for each top-level bookmark
+   * root.
+   *
+   * @note Setting this resultType will force queryType to QUERY_TYPE_BOOKMARKS.
+   */
+  const unsigned short RESULTS_AS_ROOTS_QUERY = 8;
+
+  /**
    * The sorting mode to be used for this query.
    * mode is one of SORT_BY_*
    */
   attribute unsigned short sortingMode;
 
   /**
    * The annotation to use in SORT_BY_ANNOTATION_* sorting modes.
    */
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -100,16 +100,20 @@ using namespace mozilla::places;
 #define PREF_FREC_DEFAULT_VISIT_BONUS_DEF       0
 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS      "places.frecency.unvisitedBookmarkBonus"
 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF  140
 #define PREF_FREC_UNVISITED_TYPED_BONUS         "places.frecency.unvisitedTypedBonus"
 #define PREF_FREC_UNVISITED_TYPED_BONUS_DEF     200
 #define PREF_FREC_RELOAD_VISIT_BONUS            "places.frecency.reloadVisitBonus"
 #define PREF_FREC_RELOAD_VISIT_BONUS_DEF        0
 
+// This is a hidden pref to determine when to show the mobile bookmarks folder.
+// Note: the name here matches those used elsewhere in the code, for easier searching.
+#define MOBILE_BOOKMARKS_PREF "browser.bookmarks.showMobileBookmarks"
+
 // This is a 'hidden' pref for the purposes of unit tests.
 #define PREF_FREC_DECAY_RATE     "places.frecency.decayRate"
 #define PREF_FREC_DECAY_RATE_DEF 0.975f
 
 // In order to avoid calling PR_now() too often we use a cached "now" value
 // for repeating stuff.  These are milliseconds between "now" cache refreshes.
 #define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
 
@@ -827,17 +831,19 @@ nsNavHistory::GetUpdateRequirements(cons
         query->Uri() != nullptr)
       nonTimeBasedItems = true;
 
     if (!query->Domain().IsVoid())
       domainBasedItems = true;
   }
 
   if (aOptions->ResultType() ==
-      nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
+        nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
+      aOptions->ResultType() ==
+        nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY)
     return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
 
   // Whenever there is a maximum number of results,
   // and we are not a bookmark query we must requery. This
   // is because we can't generally know if any given addition/change causes
   // the item to be in the top N items in the database.
   if (aOptions->MaxResults() > 0)
     return QUERYUPDATE_COMPLEX;
@@ -1410,16 +1416,17 @@ public:
 private:
   nsresult Select();
 
   nsresult SelectAsURI();
   nsresult SelectAsVisit();
   nsresult SelectAsDay();
   nsresult SelectAsSite();
   nsresult SelectAsTag();
+  nsresult SelectAsRoots();
 
   nsresult Where();
   nsresult GroupBy();
   nsresult OrderBy();
   nsresult Limit();
 
   void OrderByColumnIndexAsc(int32_t aIndex);
   void OrderByColumnIndexDesc(int32_t aIndex);
@@ -1511,16 +1518,21 @@ PlacesSQLQueryBuilder::Select()
       NS_ENSURE_SUCCESS(rv, rv);
       break;
 
     case nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY:
       rv = SelectAsTag();
       NS_ENSURE_SUCCESS(rv, rv);
       break;
 
+    case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
+      rv = SelectAsRoots();
+      NS_ENSURE_SUCCESS(rv, rv);
+      break;
+
     default:
       NS_NOTREACHED("Invalid result type");
   }
   return NS_OK;
 }
 
 nsresult
 PlacesSQLQueryBuilder::SelectAsURI()
@@ -1920,16 +1932,59 @@ PlacesSQLQueryBuilder::SelectAsTag()
     nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS,
     history->GetTagsFolder()
   );
 
   return NS_OK;
 }
 
 nsresult
+PlacesSQLQueryBuilder::SelectAsRoots()
+{
+  nsNavHistory *history = nsNavHistory::GetHistoryService();
+  NS_ENSURE_STATE(history);
+
+  nsAutoCString toolbarTitle;
+  nsAutoCString menuTitle;
+  nsAutoCString unfiledTitle;
+
+  history->GetStringFromName("BookmarksToolbarFolderTitle", toolbarTitle);
+  history->GetStringFromName("BookmarksMenuFolderTitle", menuTitle);
+  history->GetStringFromName("OtherBookmarksFolderTitle", unfiledTitle);
+
+  nsAutoCString mobileString;
+
+  if (Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)) {
+    nsAutoCString mobileTitle;
+    history->GetStringFromName("MobileBookmarksFolderTitle", mobileTitle);
+
+    mobileString = nsPrintfCString(","
+      "(null, 'place:folder=MOBILE_BOOKMARKS', '%s', null, null, null, "
+       "null, null, 0, 0, null, null, null, null, 'mobile____v', null) ",
+      mobileTitle.get());
+  }
+
+  mQueryString = nsPrintfCString(
+    "SELECT * FROM ("
+        "VALUES(null, 'place:folder=TOOLBAR', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
+              "(null, 'place:folder=BOOKMARKS_MENU', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
+              "(null, 'place:folder=UNFILED_BOOKMARKS', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'unfiled___v', null) "
+              " %s "
+    ")",
+    toolbarTitle.get(),
+    menuTitle.get(),
+    unfiledTitle.get(),
+    mobileString.get());
+  return NS_OK;
+}
+
+nsresult
 PlacesSQLQueryBuilder::Where()
 {
 
   // Set query options
   nsAutoCString additionalVisitsConditions;
   nsAutoCString additionalPlacesConditions;
 
   if (!mIncludeHidden) {
@@ -3565,17 +3620,17 @@ nsNavHistory::FilterResultSet(nsNavHisto
       continue;
     }
 
     // RESULTS_AS_TAG_CONTENTS returns a set ordered by place_id and
     // lastModified. The set may contain duplicate, and to remove them we can
     // just retain the first result.
     if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS &&
         (!aSet[nodeIndex]->IsURI() ||
-         nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI)) {
+         (nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI))) {
       continue;
     }
 
     if (aSet[nodeIndex]->mItemId != -1 && aQueryNode &&
         aQueryNode->mItemId == aSet[nodeIndex]->mItemId) {
       continue;
     }
 
@@ -3786,16 +3841,21 @@ nsNavHistory::RowToResult(mozIStorageVal
     }
 
     nsAutoCString guid;
     if (itemId != -1) {
       rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid, guid);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
+    if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
+      rv = aRow->GetUTF8String(kGetInfoIndex_Guid, guid);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
     RefPtr<nsNavHistoryResultNode> resultNode;
     rv = QueryRowToResult(itemId, guid, url, title, accessCount, time,
                           getter_AddRefs(resultNode));
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (itemId != -1 ||
         aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
       // RESULTS_AS_TAG_QUERY has date columns
@@ -3886,18 +3946,21 @@ nsresult
 nsNavHistory::QueryRowToResult(int64_t itemId,
                                const nsACString& aBookmarkGuid,
                                const nsACString& aURI,
                                const nsACString& aTitle,
                                uint32_t aAccessCount,
                                PRTime aTime,
                                nsNavHistoryResultNode** aNode)
 {
-  MOZ_ASSERT((itemId != -1 && !aBookmarkGuid.IsEmpty()) ||
-             (itemId == -1 && aBookmarkGuid.IsEmpty()));
+  // Only assert if the itemId is set. In some cases (e.g. virtual queries), we
+  // have a guid, but not an itemId.
+  if (itemId != -1) {
+    MOZ_ASSERT(!aBookmarkGuid.IsEmpty());
+  }
 
   nsCOMArray<nsNavHistoryQuery> queries;
   nsCOMPtr<nsNavHistoryQueryOptions> options;
   nsresult rv = QueryStringToQueryArray(aURI, &queries,
                                         getter_AddRefs(options));
 
   RefPtr<nsNavHistoryResultNode> resultNode;
   // If this failed the query does not parse correctly, let the error pass and
--- a/toolkit/components/places/nsNavHistoryQuery.cpp
+++ b/toolkit/components/places/nsNavHistoryQuery.cpp
@@ -1354,21 +1354,22 @@ NS_IMETHODIMP
 nsNavHistoryQueryOptions::GetResultType(uint16_t* aType)
 {
   *aType = mResultType;
   return NS_OK;
 }
 NS_IMETHODIMP
 nsNavHistoryQueryOptions::SetResultType(uint16_t aType)
 {
-  if (aType > RESULTS_AS_TAG_CONTENTS)
+  if (aType > RESULTS_AS_ROOTS_QUERY)
     return NS_ERROR_INVALID_ARG;
-  // Tag queries and containers are bookmarks related, so we set the QueryType
-  // accordingly.
-  if (aType == RESULTS_AS_TAG_QUERY || aType == RESULTS_AS_TAG_CONTENTS)
+  // Tag queries, containers and the roots query are bookmarks related, so we
+  // set the QueryType accordingly.
+  if (aType == RESULTS_AS_TAG_QUERY || aType == RESULTS_AS_TAG_CONTENTS ||
+      aType == RESULTS_AS_ROOTS_QUERY)
     mQueryType = QUERY_TYPE_BOOKMARKS;
   mResultType = aType;
   return NS_OK;
 }
 
 // excludeItems
 NS_IMETHODIMP
 nsNavHistoryQueryOptions::GetExcludeItems(bool* aExclude)
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -1823,17 +1823,18 @@ nsNavHistoryQueryResultNode::CanExpand()
  */
 bool
 nsNavHistoryQueryResultNode::IsContainersQuery()
 {
   uint16_t resultType = Options()->ResultType();
   return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
          resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
          resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
-         resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
+         resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY ||
+         resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY;
 }
 
 
 /**
  * Here we do not want to call ContainerResultNode::OnRemoving since our own
  * ClearChildren will do the same thing and more (unregister the observers).
  * The base ResultNode::OnRemoving will clear some regular node stats, so it
  * is OK.
@@ -1894,17 +1895,19 @@ nsNavHistoryQueryResultNode::GetHasChild
 
   if (!CanExpand()) {
     return NS_OK;
   }
 
   uint16_t resultType = mOptions->ResultType();
 
   // Tags are always populated, otherwise they are removed.
-  if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+  if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS ||
+      // AllBookmarks also always has children.
+      resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
     *aHasChildren = true;
     return NS_OK;
   }
 
   // For tag containers query we must check if we have any tag
   if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
     nsCOMPtr<nsITaggingService> tagging =
       do_GetService(NS_TAGGINGSERVICE_CONTRACTID);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/queries/test_results-as-roots.js
@@ -0,0 +1,102 @@
+"use strict";
+
+const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";
+
+const expectedRoots = [{
+  title: "BookmarksToolbarFolderTitle",
+  uri: "place:folder=TOOLBAR",
+  guid: PlacesUtils.bookmarks.virtualToolbarGuid,
+}, {
+  title: "BookmarksMenuFolderTitle",
+  uri: "place:folder=BOOKMARKS_MENU",
+  guid: PlacesUtils.bookmarks.virtualMenuGuid,
+}, {
+  title: "OtherBookmarksFolderTitle",
+  uri: "place:folder=UNFILED_BOOKMARKS",
+  guid: PlacesUtils.bookmarks.virtualUnfiledGuid,
+}];
+
+const expectedRootsWithMobile = [...expectedRoots, {
+  title: "MobileBookmarksFolderTitle",
+  uri: "place:folder=MOBILE_BOOKMARKS",
+  guid: PlacesUtils.bookmarks.virtualMobileGuid,
+}];
+
+const placesStrings = Services.strings.createBundle("chrome://places/locale/places.properties");
+
+function getAllBookmarksQuery() {
+  var query = PlacesUtils.history.getNewQuery();
+
+  // Options
+  var options = PlacesUtils.history.getNewQueryOptions();
+  options.sortingMode = options.SORT_BY_VISITCOUNT_ASCENDING;
+  options.resultType = options.RESULTS_AS_ROOTS_QUERY;
+
+  // Results
+  var result = PlacesUtils.history.executeQuery(query, options);
+  return result.root;
+}
+
+function assertExpectedChildren(root, expectedChildren) {
+  Assert.equal(root.childCount, expectedChildren.length, "Should have the expected number of children.");
+
+  for (let i = 0; i < root.childCount; i++) {
+    Assert.equal(root.getChild(i).uri, expectedChildren[i].uri,
+                 "Should have the correct uri for root ${i}");
+    Assert.equal(root.getChild(i).title, placesStrings.GetStringFromName(expectedChildren[i].title),
+                 "Should have the correct title for root ${i}");
+    Assert.equal(root.getChild(i).bookmarkGuid, expectedChildren[i].guid);
+  }
+}
+
+/**
+ * This test will test the basic RESULTS_AS_ROOTS_QUERY, that simply returns,
+ * the existing bookmark roots.
+ */
+add_task(async function test_results_as_root() {
+  let root = getAllBookmarksQuery();
+  root.containerOpen = true;
+
+  Assert.equal(PlacesUtils.asQuery(root).queryOptions.queryType,
+    Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
+    "Should have a query type of QUERY_TYPE_BOOKMARKS");
+
+  assertExpectedChildren(root, expectedRoots);
+
+  root.containerOpen = false;
+});
+
+add_task(async function test_results_as_root_with_mobile() {
+  Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, true);
+
+  let root = getAllBookmarksQuery();
+  root.containerOpen = true;
+
+  assertExpectedChildren(root, expectedRootsWithMobile);
+
+  root.containerOpen = false;
+  Services.prefs.clearUserPref(MOBILE_BOOKMARKS_PREF);
+});
+
+add_task(async function test_results_as_root_remove_mobile_dynamic() {
+  Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, true);
+
+  let root = getAllBookmarksQuery();
+  root.containerOpen = true;
+
+  // Now un-set the pref, and poke the database to update the query.
+  Services.prefs.clearUserPref(MOBILE_BOOKMARKS_PREF);
+
+  // We've un-set the pref, but we still expect the mobile folder to be there
+  // until the database is poked.
+  assertExpectedChildren(root, expectedRootsWithMobile);
+
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    url: "http://example.com",
+  });
+
+  assertExpectedChildren(root, expectedRoots);
+
+  root.containerOpen = false;
+});
--- a/toolkit/components/places/tests/queries/xpcshell.ini
+++ b/toolkit/components/places/tests/queries/xpcshell.ini
@@ -12,16 +12,17 @@ skip-if = (os == 'win' && ccov) # Bug 14
 [test_excludeQueries.js]
 [test_history_queries_tags_liveUpdate.js]
 [test_history_queries_titles_liveUpdate.js]
 [test_onlyBookmarked.js]
 [test_options_inherit.js]
 [test_queryMultipleFolder.js]
 [test_querySerialization.js]
 [test_redirects.js]
+[test_results-as-roots.js]
 [test_results-as-tag-contents-query.js]
 [test_results-as-visit.js]
 [test_search_tags.js]
 [test_searchterms-domain.js]
 [test_searchterms-uri.js]
 [test_searchterms-bookmarklets.js]
 [test_sort-date-site-grouping.js]
 [test_sorting.js]
--- a/toolkit/components/places/tests/unit/test_sync_utils.js
+++ b/toolkit/components/places/tests/unit/test_sync_utils.js
@@ -2883,100 +2883,46 @@ add_task(async function test_migrateOldT
     dateRemoved: new Date(1479162463976),
   }], "Should write tombstone for nonexistent migrated item");
 
   await PlacesUtils.bookmarks.eraseEverything();
   await PlacesSyncUtils.bookmarks.reset();
 });
 
 add_task(async function test_ensureMobileQuery() {
-  info("Ensure we correctly create the mobile query");
-
-  let PlacesUIUtils;
-  try {
-    PlacesUIUtils = ChromeUtils.import("resource:///modules/PlacesUIUtils.jsm", {}).PlacesUIUtils;
-    PlacesUIUtils.maybeRebuildLeftPane();
-  } catch (ex) {
-    info("Can't build left pane roots; skipping test");
-    return;
-  }
+  info("Ensure we correctly set the showMobileBookmarks preference");
+  const mobilePref = "browser.bookmarks.showMobileBookmarks";
+  Services.prefs.clearUserPref(mobilePref);
 
   await PlacesUtils.bookmarks.insert({
     guid: "bookmarkAAAA",
     parentGuid: PlacesUtils.bookmarks.mobileGuid,
     url: "http://example.com/a",
     title: "A",
   });
 
   await PlacesUtils.bookmarks.insert({
     guid: "bookmarkBBBB",
     parentGuid: PlacesUtils.bookmarks.mobileGuid,
     url: "http://example.com/b",
     title: "B",
   });
 
-  // Creates the organizer queries as a side effect.
-  let leftPaneId = PlacesUIUtils.leftPaneFolderId;
-  info(`Left pane root ID: ${leftPaneId}`);
-
-  let allBookmarksGuids = await PlacesSyncUtils.bookmarks.fetchGuidsWithAnno(
-    "PlacesOrganizer/OrganizerQuery", "AllBookmarks");
-  equal(allBookmarksGuids.length, 1, "Should create folder with all bookmarks queries");
-  let allBookmarkGuid = allBookmarksGuids[0];
-
-  info("Try creating query after organizer is ready");
   await PlacesSyncUtils.bookmarks.ensureMobileQuery();
-  let queryGuids = await PlacesSyncUtils.bookmarks.fetchGuidsWithAnno(
-    "PlacesOrganizer/OrganizerQuery", "MobileBookmarks");
-  equal(queryGuids.length, 1, "Should create query because we have bookmarks A and B");
-
-  let queryGuid = queryGuids[0];
-
-  let queryInfo = await PlacesUtils.bookmarks.fetch(queryGuid);
-  equal(queryInfo.url, `place:folder=MOBILE_BOOKMARKS`, "Query should point to mobile root");
-  equal(queryInfo.title, "Mobile Bookmarks", "Query title should be localized");
-  equal(queryInfo.parentGuid, allBookmarkGuid, "Should append mobile query to all bookmarks queries");
 
-  info("Rename root and query, then recreate");
-  await PlacesUtils.bookmarks.update({
-    guid: PlacesUtils.bookmarks.mobileGuid,
-    title: "renamed root",
-  });
-  await PlacesUtils.bookmarks.update({
-    guid: queryGuid,
-    title: "renamed query",
-  });
-  await PlacesSyncUtils.bookmarks.ensureMobileQuery();
-  let rootInfo = await PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.mobileGuid);
-  equal(rootInfo.title, "Mobile Bookmarks", "Should fix root title");
-  queryInfo = await PlacesUtils.bookmarks.fetch(queryGuid);
-  equal(queryInfo.title, "Mobile Bookmarks", "Should fix query title");
-
-  info("Point query to different folder");
-  await PlacesUtils.bookmarks.update({
-    guid: queryGuid,
-    url: "place:folder=BOOKMARKS_MENU",
-  });
-  await PlacesSyncUtils.bookmarks.ensureMobileQuery();
-  queryInfo = await PlacesUtils.bookmarks.fetch(queryGuid);
-  equal(queryInfo.url.href, `place:folder=MOBILE_BOOKMARKS`,
-    "Should fix query URL to point to mobile root");
-
-  info("We shouldn't track the query or the left pane root");
-
-  let changes = await PlacesSyncUtils.bookmarks.pullChanges();
-  ok(!(queryGuid in changes), "Should not track mobile query");
+  Assert.ok(Services.prefs.getBoolPref(mobilePref),
+            "Pref should be true where there are bookmarks in the folder.");
 
   await PlacesUtils.bookmarks.remove("bookmarkAAAA");
   await PlacesUtils.bookmarks.remove("bookmarkBBBB");
+
   await PlacesSyncUtils.bookmarks.ensureMobileQuery();
 
-  queryGuids = await PlacesSyncUtils.bookmarks.fetchGuidsWithAnno(
-    "PlacesOrganizer/OrganizerQuery", "MobileBookmarks");
-  equal(queryGuids.length, 0, "Should delete query since there are no bookmarks");
+  Assert.ok(!Services.prefs.getBoolPref(mobilePref),
+            "Pref should be false where there are no bookmarks in the folder.");
 
   await PlacesUtils.bookmarks.eraseEverything();
   await PlacesSyncUtils.bookmarks.reset();
 });
 
 add_task(async function test_remove_stale_tombstones() {
   info("Insert and delete synced bookmark");
   {