Bug 1423896 - Make the All Bookmarks folder for the left pane of the Library a virtual query. r=mak
authorMark Banner <standard8@mozilla.com>
Thu, 07 Dec 2017 14:15:39 +0000
changeset 402796 32ec5531af09ef18512b09ea0a4e2b5240e7c236
parent 402795 f2d20b78861e2502159a77ce3b0f9800c98fa9aa
child 402797 c388570c330fd7745f12eca3258cc3ae4b3d5bb2
push id99659
push useraciure@mozilla.com
push dateWed, 07 Feb 2018 22:33:57 +0000
treeherdermozilla-inbound@5ceb1098fef3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1423896
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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");
   {