Bug 1310295 - Make left pane queries virtual in the Places Library window. r=kitcambridge,mak
authorMark Banner <standard8@mozilla.com>
Fri, 16 Feb 2018 20:30:04 +0000
changeset 408165 eb12946f24c7f33fbeb139595256ee7ce423c1f3
parent 408164 e8d02073ed5acfe50ee9568ede049ffa96941096
child 408166 4f96e58f2ece971059e8e9758c52073d78056eca
push id61141
push usermbanner@mozilla.com
push dateWed, 14 Mar 2018 19:46:57 +0000
treeherderautoland@4f96e58f2ece [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskitcambridge, mak
bugs1310295
milestone61.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 1310295 - Make left pane queries virtual in the Places Library window. r=kitcambridge,mak MozReview-Commit-ID: DcEMAlrXu8R
browser/base/content/browser-places.js
browser/components/places/PlacesUIUtils.jsm
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.ini
browser/components/places/tests/browser/browser_copy_query_without_tree.js
browser/components/places/tests/browser/browser_library_left_pane_fixnames.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/head_bookmarks.js
browser/components/places/tests/unit/test_leftpane_corruption_handling.js
browser/components/places/tests/unit/xpcshell.ini
browser/locales/en-US/chrome/browser/places/places.properties
browser/themes/shared/places/tree-icons.inc.css
services/sync/modules/bookmark_validator.js
services/sync/tests/unit/test_bookmark_validator.js
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsINavHistoryService.idl
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistory.h
toolkit/components/places/nsNavHistoryQuery.cpp
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/tests/queries/test_results-as-left-pane.js
toolkit/components/places/tests/queries/xpcshell.ini
toolkit/locales/en-US/chrome/places/places.properties
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1393,28 +1393,17 @@ var BookmarkingUI = {
       insertionPoint: ".panel-subview-footer"
     });
   },
 
   // Set by sync after syncing bookmarks successfully once.
   MOBILE_BOOKMARKS_PREF: "browser.bookmarks.showMobileBookmarks",
 
   _shouldShowMobileBookmarks() {
-    try {
-      return Services.prefs.getBoolPref(this.MOBILE_BOOKMARKS_PREF);
-    } catch (e) {}
-    // No pref set (or invalid pref set), look for a mobile bookmarks left pane query.
-    const organizerQueryAnno = "PlacesOrganizer/OrganizerQuery";
-    const mobileBookmarksAnno = "MobileBookmarks";
-    let shouldShow = PlacesUtils.annotations.getItemsWithAnnotation(organizerQueryAnno, {}).filter(
-      id => PlacesUtils.annotations.getItemAnnotation(id, organizerQueryAnno) == mobileBookmarksAnno
-    ).length > 0;
-    // Sync will change this pref if/when it adds a mobile bookmarks query.
-    Services.prefs.setBoolPref(this.MOBILE_BOOKMARKS_PREF, shouldShow);
-    return shouldShow;
+    return Services.prefs.getBoolPref(this.MOBILE_BOOKMARKS_PREF, false);
   },
 
   _initMobileBookmarks(mobileMenuItem) {
     mobileMenuItem.hidden = !this._shouldShowMobileBookmarks();
   },
 
   _uninitView: function BUI__uninitView() {
     // When an element with a placesView attached is removed and re-inserted,
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -201,20 +201,16 @@ let InternalFaviconLoader = {
       this._removeLoadDataFromWindowMap(win, loadData);
     }, FAVICON_REQUEST_TIMEOUT);
     let loadDataForWindow = gFaviconLoadDataMap.get(win);
     loadDataForWindow.push(loadData);
   },
 };
 
 var PlacesUIUtils = {
-  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
    * @param   aSpec
    *          The string spec of the URI
    * @return A URI object for the spec.
@@ -526,21 +522,26 @@ var 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)) {
+    if (PlacesUtils.nodeIsQuery(parentNode)) {
+      if (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;
+        }
+      } else if (PlacesUtils.isVirtualLeftPaneItem(aNode.bookmarkGuid)) {
+        // If the item is a left-pane top-level item, it can't be removed.
         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
@@ -585,31 +586,17 @@ var 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 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;
+    return false;
   },
 
   /** aItemsToOpen needs to be an array of objects of the form:
     * {uri: string, isBookmark: boolean}
     */
   _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) {
     if (!aItemsToOpen.length)
       return;
@@ -809,271 +796,16 @@ var PlacesUIUtils = {
         title = "";
       }
     } else
       title = aNode.title;
 
     return title || this.getString("noTitle");
   },
 
-  get leftPaneQueries() {
-    // build the map
-    this.leftPaneFolderId;
-    return this.leftPaneQueries;
-  },
-
-  get leftPaneFolderId() {
-    delete this.leftPaneFolderId;
-    return this.leftPaneFolderId = this.maybeRebuildLeftPane();
-  },
-
-  // Get the folder id for the organizer left-pane folder.
-  maybeRebuildLeftPane() {
-    let leftPaneRoot = -1;
-
-    // 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") },
-    };
-    // All queries but PlacesRoot.
-    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.
-          return;
-        }
-        // removeItemAnnotation does not check if item exists, nor the anno,
-        // so this is safe to do.
-        as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
-        as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO);
-        // This will throw if the annotation is an orphan.
-        bs.removeItem(aItemId);
-      } catch (e) { /* orphan anno */ }
-    }
-
-    // Returns true if item really exists, false otherwise.
-    function itemExists(aItemId) {
-      try {
-        bs.getFolderIdForItem(aItemId);
-        return true;
-      } catch (e) {
-        return false;
-      }
-    }
-
-    // Get all items marked as being the left pane folder.
-    let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO);
-    if (items.length > 1) {
-      // Something went wrong, we cannot have more than one left pane folder,
-      // remove all left pane folders and continue.  We will create a new one.
-      items.forEach(safeRemoveItem);
-    } else if (items.length == 1 && items[0] != -1) {
-      leftPaneRoot = items[0];
-      // Check that organizer left pane root is valid.
-      let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO);
-      if (version != this.ORGANIZER_LEFTPANE_VERSION ||
-          !itemExists(leftPaneRoot)) {
-        // Invalid root, we must rebuild the left pane.
-        safeRemoveItem(leftPaneRoot);
-        leftPaneRoot = -1;
-      }
-    }
-
-    if (leftPaneRoot != -1) {
-      // A valid left pane folder has been found.
-      // Build the leftPaneQueries Map.  This is used to quickly access them,
-      // associating a mnemonic name to the real item ids.
-      delete this.leftPaneQueries;
-      this.leftPaneQueries = {};
-
-      let queryItems = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO);
-      // While looping through queries we will also check for their validity.
-      let queriesCount = 0;
-      let corrupt = false;
-      for (let i = 0; i < queryItems.length; i++) {
-        let queryName = as.getItemAnnotation(queryItems[i], this.ORGANIZER_QUERY_ANNO);
-
-        // Some extension did use our annotation to decorate their items
-        // with icons, so we should check only our elements, to avoid dataloss.
-        if (!(queryName in queries))
-          continue;
-
-        let query = queries[queryName];
-        query.itemId = queryItems[i];
-
-        if (!itemExists(query.itemId)) {
-          // Orphan annotation, bail out and create a new left pane root.
-          corrupt = true;
-          break;
-        }
-
-        // Check that all queries have valid parents.
-        let parentId = bs.getFolderIdForItem(query.itemId);
-        if (!queryItems.includes(parentId) && parentId != leftPaneRoot) {
-          // The parent is not part of the left pane, bail out and create a new
-          // left pane root.
-          corrupt = true;
-          break;
-        }
-
-        // Titles could have been corrupted or the user could have changed his
-        // locale.  Check title and eventually fix it.
-        if (bs.getItemTitle(query.itemId) != query.title)
-          bs.setItemTitle(query.itemId, query.title);
-        if ("concreteId" in query) {
-          if (bs.getItemTitle(query.concreteId) != query.concreteTitle)
-            bs.setItemTitle(query.concreteId, query.concreteTitle);
-        }
-
-        // Add the query to our cache.
-        this.leftPaneQueries[queryName] = query.itemId;
-        queriesCount++;
-      }
-
-      // Note: it's not enough to just check for queriesCount, since we may
-      // find an invalid query just after accounting for a sufficient number of
-      // valid ones.  As well as we can't just rely on corrupt since we may find
-      // less valid queries than expected.
-      if (corrupt || queriesCount != EXPECTED_QUERY_COUNT) {
-        // Queries number is wrong, so the left pane must be corrupt.
-        // Note: we can't just remove the leftPaneRoot, because some query could
-        // have a bad parent, so we have to remove all items one by one.
-        queryItems.forEach(safeRemoveItem);
-        safeRemoveItem(leftPaneRoot);
-      } else {
-        // Everything is fine, return the current left pane folder.
-        return leftPaneRoot;
-      }
-    }
-
-    // Create a new left pane folder.
-    var callback = {
-      // Helper to create an organizer special query.
-      create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
-        let itemId = bs.insertBookmark(aParentId,
-                                       Services.io.newURI(aQueryUrl),
-                                       bs.DEFAULT_INDEX,
-                                       queries[aQueryName].title);
-        // Mark as special organizer query.
-        as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
-                             0, as.EXPIRE_NEVER);
-        // We should never backup this, since it changes between profiles.
-        as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
-                             0, as.EXPIRE_NEVER);
-        // Add to the queries map.
-        PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
-        return itemId;
-      },
-
-      // Helper to create an organizer special folder.
-      create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
-              // Left Pane Root Folder.
-        let folderId = bs.createFolder(aParentId,
-                                       queries[aFolderName].title,
-                                       bs.DEFAULT_INDEX);
-        // We should never backup this, since it changes between profiles.
-        as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
-                             0, as.EXPIRE_NEVER);
-
-        if (aIsRoot) {
-          // Mark as special left pane root.
-          as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
-                               PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
-                               0, as.EXPIRE_NEVER);
-        } else {
-          // Mark as special organizer folder.
-          as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName,
-                           0, as.EXPIRE_NEVER);
-          PlacesUIUtils.leftPaneQueries[aFolderName] = folderId;
-        }
-        return folderId;
-      },
-
-      runBatched: function CB_runBatched(aUserData) {
-        delete PlacesUIUtils.leftPaneQueries;
-        PlacesUIUtils.leftPaneQueries = { };
-
-        // Left Pane Root Folder.
-        leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
-
-        // History Query.
-        this.create_query("History", leftPaneRoot,
-                          "place:type=" +
-                          Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
-                          "&sort=" +
-                          Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
-
-        // Downloads.
-        this.create_query("Downloads", leftPaneRoot,
-                          "place:transition=" +
-                          Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
-                          "&sort=" +
-                          Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
-
-        // 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.
-        this.create_query("AllBookmarks", leftPaneRoot,
-                          "place:type=" +
-                          Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY);
-      }
-    };
-    bs.runInBatchMode(callback, null);
-
-    return leftPaneRoot;
-  },
-
-  /**
-   * 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 = "";
-    // If the let pane hasn't been built, use the annotation service
-    // directly, to avoid building the left pane too early.
-    if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) {
-      try {
-        queryName = PlacesUtils.annotations.
-                                getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO);
-      } catch (ex) {
-        // doesn't have the annotation
-        queryName = "";
-      }
-    } else {
-      // If the left pane has already been built, use the name->id map
-      // cached in PlacesUIUtils.
-      for (let [name, id] of Object.entries(this.leftPaneQueries)) {
-        if (aItemId == id)
-          queryName = name;
-      }
-    }
-    return queryName;
-  },
-
   shouldShowTabsFromOtherComputersMenuitem() {
     let weaveOK = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED &&
                   Weave.Svc.Prefs.get("firstSync", "") != "notReady";
     return weaveOK;
   },
 
   /**
    * WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT'S LIKELY TO BE REMOVED IN A
@@ -1349,24 +1081,17 @@ function canMoveUnwrappedNode(unwrappedN
 
   let parentGuid = unwrappedNode.parentGuid;
   // If there's no parent Guid, this was likely a virtual query that returns
   // bookmarks, such as a tags query.
   if (!parentGuid ||
       parentGuid == PlacesUtils.bookmarks.rootGuid) {
     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" &&
-      (unwrappedNode.parent == PlacesUIUtils.leftPaneFolderId)) {
-    return false;
-  }
+
   return true;
 }
 
 /**
  * This gets the most appropriate item for using for batching. In the case of multiple
  * views being related, the method returns the most expensive result to batch.
  * For example, if it detects the left-hand library pane, then it will look for
  * and return the reference to the right-hand pane.
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -51,22 +51,16 @@ var gEditItemOverlay = {
     let parentGuid = null;
 
     if (node && isItem) {
       if (!node.parent || (node.parent.itemId > 0 && !node.parent.bookmarkGuid)) {
         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));
-      }
       parentId = parent.itemId;
       parentGuid = parent.bookmarkGuid;
     }
 
     let focusedElement = aInitInfo.focusedElement;
     let onPanelReady = aInitInfo.onPanelReady;
 
     return this._paneInfo = { itemId, itemGuid, parentId, parentGuid, isItem,
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -31,55 +31,57 @@ var PlacesOrganizer = {
   // observing additionalInfoBroadcaster.
   _additionalInfoFields: [
     "editBMPanel_descriptionRow",
     "editBMPanel_loadInSidebarCheckbox",
     "editBMPanel_keywordRow",
   ],
 
   _initFolderTree() {
-    var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
-    this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
+    this._places.place = `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY}&excludeItems=1&expandQueries=0`;
   },
 
   /**
    * 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":
+        this._places.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
+        PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+        break;
       case "History":
+        this._places.selectItems([PlacesUtils.virtualHistoryGuid]);
+        PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+        break;
       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;
+        this._places.selectItems([PlacesUtils.virtualDownloadsGuid]);
         break;
-      }
+      case "Tags":
+        this._places.selectItems([PlacesUtils.virtualTagsGuid]);
+        break;
       case "BookmarksMenu":
         this.selectLeftPaneContainerByHierarchy([
-          PlacesUIUtils.leftPaneQueries.AllBookmarks,
+          PlacesUtils.virtualAllBookmarksGuid,
           PlacesUtils.bookmarks.virtualMenuGuid
         ]);
         break;
       case "BookmarksToolbar":
         this.selectLeftPaneContainerByHierarchy([
-          PlacesUIUtils.leftPaneQueries.AllBookmarks,
+          PlacesUtils.virtualAllBookmarksGuid,
           PlacesUtils.bookmarks.virtualToolbarGuid
         ]);
         break;
       case "UnfiledBookmarks":
         this.selectLeftPaneContainerByHierarchy([
-          PlacesUIUtils.leftPaneQueries.AllBookmarks,
+          PlacesUtils.virtualAllBookmarksGuid,
           PlacesUtils.bookmarks.virtualUnfiledGuid
         ]);
         break;
       default:
         throw new Error(`Unrecognized item ${item} passed to selectLeftPaneRootItem`);
     }
   },
 
@@ -87,17 +89,16 @@ var PlacesOrganizer = {
    * Opens a given hierarchy in the left pane, stopping at the last reachable
    * 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, like:
    *                   "BookmarksMenu", "BookmarksToolbar", "UnfiledBookmarks", "AllBookmarks".
-   * @see PlacesUIUtils.leftPaneQueries for supported named queries.
    */
   selectLeftPaneContainerByHierarchy(aHierarchy) {
     if (!aHierarchy)
       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;
@@ -307,22 +308,22 @@ var PlacesOrganizer = {
   },
 
   /**
    * Sets the search scope based on aNode's properties.
    * @param   aNode
    *          the node to set up scope from
    */
   _setSearchScopeForNode: function PO__setScopeForNode(aNode) {
-    let itemId = aNode.itemId;
+    let itemGuid = aNode.bookmarkGuid;
 
     if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
-        itemId == PlacesUIUtils.leftPaneQueries.History) {
+        itemGuid == PlacesUtils.virtualHistoryGuid) {
       PlacesQueryBuilder.setScope("history");
-    } else if (itemId == PlacesUIUtils.leftPaneQueries.Downloads) {
+    } else if (itemGuid == PlacesUtils.virtualDownloadsGuid) {
       PlacesQueryBuilder.setScope("downloads");
     } else {
       // Default to All Bookmarks for all other nodes, per bug 469437.
       PlacesQueryBuilder.setScope("bookmarks");
     }
   },
 
   /**
--- a/browser/components/places/content/tree.xml
+++ b/browser/components/places/content/tree.xml
@@ -627,17 +627,17 @@
             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. 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.nodeIsQuery(node) && node.bookmarkGuid == PlacesUIUtils.virtualAllBookmarksGuid));
 
             PlacesUtils.asContainer(node);
             if (!node.containerOpen && !shouldOpen)
               return foundOne;
 
             checkedGuidsSet.add(concreteGuid);
 
             // Remember the beginning state so that we can re-close
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -133,16 +133,17 @@ PlacesTreeView.prototype = {
       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:
+      case Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_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;
   },
@@ -1300,21 +1301,23 @@ PlacesTreeView.prototype = {
             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;
+          case PlacesUtils.virtualAllBookmarksGuid:
+          case PlacesUtils.virtualHistoryGuid:
+          case PlacesUtils.virtualDownloadsGuid:
+          case PlacesUtils.virtualTagsGuid:
+            properties += ` OrganizerQuery_${node.bookmarkGuid}`;
+            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);
 
         if (this._controller.hasCachedLivemarkInfo(node.parent)) {
           properties += " livemarkItem";
@@ -1787,25 +1790,16 @@ PlacesTreeView.prototype = {
     //
     // 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) ||
         PlacesUtils.isQueryGeneratedFolder(itemGuid))
       return false;
 
-    let parentId = PlacesUtils.getConcreteItemId(node.parent);
-    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;
   },
 
   setCellText: function PTV_setCellText(aRow, aColumn, aText) {
     // We may only get here if the cell is editable.
     let node = this._rows[aRow];
     if (node.title != aText) {
       PlacesTransactions.EditTitle({ guid: node.bookmarkGuid, title: aText })
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -74,17 +74,16 @@ skip-if = (os == 'win' && ccov) # Bug 14
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_commands.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_delete_bookmarks_in_tags.js]
 [browser_library_delete_tags.js]
 [browser_library_downloads.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_infoBox.js]
-[browser_library_left_pane_fixnames.js]
 [browser_library_left_pane_middleclick.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_left_pane_select_hierarchy.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_middleclick.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_open_leak.js]
 [browser_library_openFlatContainer.js]
--- a/browser/components/places/tests/browser/browser_copy_query_without_tree.js
+++ b/browser/components/places/tests/browser/browser_copy_query_without_tree.js
@@ -45,17 +45,17 @@ add_task(async function copy_mobile_shor
   let library = await promiseLibrary();
 
   registerCleanupFunction(async () => {
     library.close();
     await PlacesUtils.bookmarks.eraseEverything();
   });
 
   library.PlacesOrganizer.selectLeftPaneContainerByHierarchy([
-    PlacesUIUtils.leftPaneQueries.AllBookmarks,
+    PlacesUtils.virtualAllBookmarksGuid,
     PlacesUtils.bookmarks.virtualMobileGuid,
   ]);
 
   await promiseClipboard(function() { library.PlacesOrganizer._places.controller.copy(); },
                          PlacesUtils.TYPE_X_MOZ_PLACE);
 
   library.PlacesOrganizer.selectLeftPaneBuiltIn("UnfiledBookmarks");
 
deleted file mode 100644
--- a/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-/**
- *  Test we correctly fix broken Library left pane queries names.
- */
-
-// Array of left pane queries objects, each one has the following properties:
-// name: query's identifier got from annotations,
-// itemId: query's itemId,
-// correctTitle: original and correct query's title.
-var leftPaneQueries = [];
-
-function onLibraryReady(organizer) {
-      // Check titles have been fixed.
-      for (var i = 0; i < leftPaneQueries.length; i++) {
-        var query = leftPaneQueries[i];
-        if ("concreteId" in query) {
-          is(PlacesUtils.bookmarks.getItemTitle(query.concreteId),
-           query.concreteTitle, "Concrete title is correct for query " + query.name);
-        }
-      }
-
-      // Close Library window.
-      organizer.close();
-      // No need to cleanup anything, we have a correct left pane now.
-      finish();
-}
-
-function test() {
-  waitForExplicitFinish();
-  // Ensure left pane is initialized.
-  ok(PlacesUIUtils.leftPaneFolderId > 0, "left pane folder is initialized");
-
-  // Get the left pane folder.
-  var leftPaneItems = PlacesUtils.annotations
-                                 .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
-
-  is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
-  // Check version.
-  var version = PlacesUtils.annotations
-                           .getItemAnnotation(leftPaneItems[0],
-                                              PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
-  is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual");
-
-  // Get all left pane queries.
-  var items = PlacesUtils.annotations
-                         .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_QUERY_ANNO);
-  // Get current queries names.
-  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) };
-
-    leftPaneQueries.push(query);
-    // Rename to a bad title.
-    PlacesUtils.bookmarks.setItemTitle(query.itemId, "badName");
-  }
-
-  restoreLeftPaneGetters();
-
-  // Open Library, this will kick-off left pane code.
-  openLibrary(onLibraryReady);
-}
--- a/browser/components/places/tests/browser/browser_library_search.js
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -25,124 +25,60 @@
  *      remains selected.
  */
 
 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.leftPaneQueries.AllBookmarks);
-    search(PlacesUIUtils.allBookmarksFolderId, "dummy", defScope);
-  },
-
-  function historyScope() {
-    let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.History);
-    search(PlacesUIUtils.leftPaneQueries.History, "dummy", defScope);
-  },
-
-  function downloadsScope() {
-    let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries.Downloads);
-    search(PlacesUIUtils.leftPaneQueries.Downloads, "dummy", defScope);
-  },
-];
-
-/**
- * Returns the default search scope for a given folder.
- *
- * @param  aFolderId
- *         the item ID of a node in the left pane's tree
- * @return the default scope when the folder is newly selected
- */
-function getDefaultScope(aFolderId) {
-  switch (aFolderId) {
-    case PlacesUIUtils.leftPaneQueries.History:
-      return "scopeBarHistory";
-    case PlacesUIUtils.leftPaneQueries.Downloads:
-      return "scopeBarDownloads";
-    default:
-      return "scopeBarAll";
-  }
-}
-
-/**
- * Returns the single nsINavHistoryQuery represented by a given place URI.
- *
- * @param  aPlaceURI
- *         a URI that represents a single query
- * @return an nsINavHistoryQuery object
- */
-function queryStringToQuery(aPlaceURI) {
-  let queries = {};
-  PlacesUtils.history.queryStringToQueries(aPlaceURI, queries, {}, {});
-  return queries.value[0];
-}
-
-/**
- * Resets the search by clearing the search box's text and ensures that the
- * search scope remains as expected.
- *
- * @param  aExpectedScopeButtonId
- *         this button should be selected after the reset
- */
-function resetSearch(aExpectedScopeButtonId) {
-  search(null, "", aExpectedScopeButtonId);
-}
-
 /**
  * Performs a search for a given folder and search string and ensures that the
  * URI of the right pane's content tree is as expected for the folder and search
  * string.  Also ensures that the search scope button is as expected after the
  * search.
  *
- * @param  aFolderId
- *         the item ID of a node in the left pane's tree
+ * @param  aFolderGuid
+ *         the item guid of a node in the left pane's tree
  * @param  aSearchStr
  *         the search text; may be empty to reset the search
- * @param  aExpectedScopeButtonId
- *         after searching the selected scope button should be this
  */
-function search(aFolderId, aSearchStr, aExpectedScopeButtonId) {
+async function search(aFolderGuid, aSearchStr) {
   let doc = gLibrary.document;
   let folderTree = doc.getElementById("placesList");
   let contentTree = doc.getElementById("placeContent");
 
   // First, ensure that selecting the folder in the left pane updates the
   // content tree properly.
-  if (aFolderId) {
-    folderTree.selectItems([aFolderId]);
-    isnot(folderTree.selectedNode, null,
+  if (aFolderGuid) {
+    folderTree.selectItems([aFolderGuid]);
+    Assert.notEqual(folderTree.selectedNode, null,
        "Sanity check: left pane tree should have selection after selecting!");
 
-    // getFolders() on a History query returns an empty array, so no use
-    // comparing against aFolderId in that case.
-    if (aFolderId !== PlacesUIUtils.leftPaneQueries.History &&
-        aFolderId !== PlacesUIUtils.leftPaneQueries.Downloads) {
-      // contentTree.place should be equal to contentTree.result.root.uri,
-      // but it's not until bug 476952 is fixed.
-      let query = queryStringToQuery(contentTree.result.root.uri);
-      is(query.getFolders()[0], aFolderId,
+    // The downloads folder never quite matches the url of the contentTree,
+    // probably due to the way downloads are loaded.
+    if (aFolderGuid !== PlacesUtils.virtualDownloadsGuid) {
+      Assert.equal(folderTree.selectedNode.uri, contentTree.place,
          "Content tree's folder should be what was selected in the left pane");
     }
   }
 
   // Second, ensure that searching updates the content tree and search UI
   // properly.
   let searchBox = doc.getElementById("searchFilter");
   searchBox.value = aSearchStr;
   gLibrary.PlacesSearchBox.search(searchBox.value);
-  let query = queryStringToQuery(contentTree.result.root.uri);
+  let queries = {};
+  PlacesUtils.history.queryStringToQueries(contentTree.result.root.uri, queries, {}, {});
   if (aSearchStr) {
-    is(query.searchTerms, aSearchStr,
-       "Content tree's searchTerms should be text in search box");
+    Assert.equal(queries.value[0].searchTerms, aSearchStr,
+      "Content tree's searchTerms should be text in search box");
   } else {
-    is(query.hasSearchTerms, false,
-       "Content tree's searchTerms should not exist after search reset");
+    Assert.equal(queries.value[0].hasSearchTerms, false,
+      "Content tree's searchTerms should not exist after search reset");
   }
 }
 
 add_task(async function test() {
   // Add visits, a bookmark and a tag.
   await PlacesTestUtils.addVisits(
     [{ uri: Services.io.newURI(TEST_URL), visitDate: Date.now() * 1000,
        transition: PlacesUtils.history.TRANSITION_TYPED },
@@ -155,17 +91,25 @@ add_task(async function test() {
     title: "dummy",
     url: TEST_URL,
   });
 
   PlacesUtils.tagging.tagURI(Services.io.newURI(TEST_URL), ["dummyTag"]);
 
   gLibrary = await promiseLibrary();
 
-  testCases.forEach(aTest => aTest());
+  const rootsToTest = [
+    PlacesUtils.virtualAllBookmarksGuid,
+    PlacesUtils.virtualHistoryGuid,
+    PlacesUtils.virtualDownloadsGuid,
+  ];
+
+  for (let root of rootsToTest) {
+    await search(root, "dummy");
+  }
 
   await promiseLibraryClosed(gLibrary);
 
   // Cleanup.
   PlacesUtils.tagging.untagURI(Services.io.newURI(TEST_URL), ["dummyTag"]);
 
   await PlacesUtils.bookmarks.eraseEverything();
   await PlacesUtils.history.clear();
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -1,36 +1,15 @@
 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]]);
-for (let [key, val] of leftPaneGetters) {
-  if (!val) {
-    let getter = Object.getOwnPropertyDescriptor(PlacesUIUtils, key).get;
-    if (typeof getter == "function") {
-      leftPaneGetters.set(key, getter);
-    }
-  }
-}
-
-// ...And restore them when test ends.
-function restoreLeftPaneGetters() {
-  for (let [key, getter] of leftPaneGetters) {
-    Object.defineProperty(PlacesUIUtils, key, {
-      enumerable: true, configurable: true, get: getter
-    });
-  }
-}
-registerCleanupFunction(restoreLeftPaneGetters);
-
 function openLibrary(callback, aLeftPaneRoot) {
   let library = window.openDialog("chrome://browser/content/places/places.xul",
                                   "", "chrome,toolbar=yes,dialog=no,resizable",
                                   aLeftPaneRoot);
   waitForFocus(function() {
     callback(library);
   }, library);
 
--- a/browser/components/places/tests/chrome/test_0_bug510634.xul
+++ b/browser/components/places/tests/chrome/test_0_bug510634.xul
@@ -32,89 +32,69 @@
 
   <script type="application/javascript">
   <![CDATA[
 
     /**
      * Bug 510634 -  Wrong icons on bookmarks sidebar
      * https://bugzilla.mozilla.org/show_bug.cgi?id=510634
      *
-     * Ensures that properties for special queries are set on their tree nodes,
-     * even if PlacesUIUtils.leftPaneFolderId was not initialized.
+     * Ensures that properties for special queries are set on their tree nodes.
      */
 
     SimpleTest.waitForExplicitFinish();
 
     function runTest() {
-      // We need to cache and restore the getters in order to simulate
-      // Bug 510634.
-      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);
-          }
-        }
-      }
-
-      function restoreLeftPaneGetters() {
-        for (let [key, getter] of leftPaneGetters) {
-          Object.defineProperty(PlacesUIUtils, key, {
-            enumerable: true, configurable: true, get: getter
-          });
-        }
-      }
-
-      let leftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
-      restoreLeftPaneGetters();
-
       // Setup the places tree contents.
       let tree = document.getElementById("tree");
-      tree.place = "place:queryType=1&folder=" + leftPaneFolderId;
+      tree.place = `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_LEFT_PANE_QUERY}&excludeItems=1&expandQueries=0`;
 
       // 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"]]);
+      tree.selectItems([PlacesUtils.virtualAllBookmarksGuid]);
       PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
       is(tree.selectedNode.uri,
          "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY +
          "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS,
          "Opened All Bookmarks");
 
-      for (let queryName of ["History", "Downloads", "Tags", "AllBookmarks"]) {
+      const topLevelGuids = [
+        PlacesUtils.virtualHistoryGuid,
+        PlacesUtils.virtualDownloadsGuid,
+        PlacesUtils.virtualTagsGuid,
+        PlacesUtils.virtualAllBookmarksGuid
+      ];
+
+      for (let queryName of topLevelGuids) {
         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");
+        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");
+        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();
     }
 
   ]]>
   </script>
 </window>
--- a/browser/components/places/tests/unit/head_bookmarks.js
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -15,19 +15,16 @@ if (commonFile) {
 
 // Put any other stuff relative to this test folder below.
 
 XPCOMUtils.defineLazyGetter(this, "PlacesUIUtils", function() {
   ChromeUtils.import("resource:///modules/PlacesUIUtils.jsm");
   return PlacesUIUtils;
 });
 
-const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder";
-const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
-
 // Needed by some test that relies on having an app registered.
 ChromeUtils.import("resource://testing-common/AppInfo.jsm", this);
 updateAppInfo({
   name: "PlacesTest",
   ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
   version: "1",
   platformVersion: "",
 });
deleted file mode 100644
--- a/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-/**
- * Tests that we build a working leftpane in various corruption situations.
- */
-
-// Used to store the original leftPaneFolderId getter.
-var gLeftPaneFolderIdGetter;
-// 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");
-
-  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,
-    title: "test",
-    index: PlacesUtils.bookmarks.DEFAULT_INDEX,
-    type: PlacesUtils.bookmarks.TYPE_FOLDER
-  });
-
-  let folderId = await PlacesUtils.promiseItemId(folder.guid);
-  PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO,
-                                            "test", 0,
-                                            PlacesUtils.annotations.EXPIRE_NEVER);
-
-  // 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()();
-
-    // Regenerate getters.
-    Object.defineProperty(PlacesUIUtils, "leftPaneFolderId", gLeftPaneFolderIdGetter);
-    gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
-
-    // 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");
-  }
-});
-
-// Corruption cases.
-var gTests = [
-
-  function test1() {
-    print("1. Do nothing, checks test calibration.");
-  },
-
-  async function test2() {
-    print("2. Delete the left pane folder.");
-    let guid = await PlacesUtils.promiseItemGuid(gLeftPaneFolderId);
-    await PlacesUtils.bookmarks.remove(guid);
-  },
-
-  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. 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 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 test6() {
-    print("6. Remove the left pane folder annotation.");
-    PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId,
-                                                 ORGANIZER_FOLDER_ANNO);
-  },
-];
-
-/**
- * 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));
-  root.containerOpen = false;
-  return hier;
-}
-
-function hierarchyToObj(aNode) {
-  let o = {};
-  o.title = aNode.title;
-  o.annos = PlacesUtils.getAnnotationsForItem(aNode.itemId);
-  if (PlacesUtils.nodeIsURI(aNode)) {
-    o.uri = aNode.uri;
-  } else if (PlacesUtils.nodeIsFolder(aNode)) {
-    o.children = [];
-    PlacesUtils.asContainer(aNode).containerOpen = true;
-    for (let i = 0; i < aNode.childCount; ++i) {
-      o.children.push(hierarchyToObj(aNode.getChild(i)));
-    }
-    aNode.containerOpen = false;
-  }
-  return o;
-}
--- a/browser/components/places/tests/unit/xpcshell.ini
+++ b/browser/components/places/tests/unit/xpcshell.ini
@@ -15,10 +15,9 @@ support-files =
 [test_browserGlue_corrupt_nobackup_default.js]
 [test_browserGlue_distribution.js]
 [test_browserGlue_migrate.js]
 [test_browserGlue_prefs.js]
 [test_browserGlue_restore.js]
 [test_browserGlue_smartBookmarks.js]
 [test_browserGlue_urlbar_defaultbehavior_migration.js]
 [test_clearHistory_shutdown.js]
-[test_leftpane_corruption_handling.js]
 [test_PUIU_batchUpdatesForNode.js]
--- a/browser/locales/en-US/chrome/browser/places/places.properties
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -57,21 +57,16 @@ detailsPane.noItems=No items
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of items
 # example: 111 items
 detailsPane.itemsCountLabel=One item;#1 items
 
 mostVisitedTitle=Most Visited
 recentTagsTitle=Recent Tags
 
-OrganizerQueryHistory=History
-OrganizerQueryDownloads=Downloads
-OrganizerQueryAllBookmarks=All Bookmarks
-OrganizerQueryTags=Tags
-
 # LOCALIZATION NOTE (tagResultLabel, bookmarkResultLabel, switchtabResultLabel,
 # keywordResultLabel, searchengineResultLabel)
 # Noun used to describe the location bar autocomplete result type
 # to users with screen readers
 # See createResultLabel() in urlbarBindings.xml
 tagResultLabel=Tag
 bookmarkResultLabel=Bookmark
 switchtabResultLabel=Tab
--- a/browser/themes/shared/places/tree-icons.inc.css
+++ b/browser/themes/shared/places/tree-icons.inc.css
@@ -53,39 +53,39 @@ treechildren::-moz-tree-image(container,
   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) {
+treechildren::-moz-tree-image(query, OrganizerQuery_allbms_____v) {
   list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
 }
 
-treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+treechildren::-moz-tree-image(query, OrganizerQuery_downloads__v) {
   list-style-image: url("chrome://browser/skin/places/downloads.png");
 }
 
 treechildren::-moz-tree-image(title, query, tagContainer),
-treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+treechildren::-moz-tree-image(query, OrganizerQuery_tags_______v) {
   list-style-image: url("chrome://browser/skin/places/tag.png");
 }
 
 /* calendar icon for folders grouping items by date */
 treechildren::-moz-tree-image(title, query, dayContainer) {
   list-style-image: url("chrome://browser/skin/places/history.svg");
 }
 
 treechildren::-moz-tree-image(title, query, hostContainer) {
   list-style-image: url("chrome://browser/skin/places/folder.svg");
 }
 
-treechildren::-moz-tree-image(query, OrganizerQuery_History) {
+treechildren::-moz-tree-image(query, OrganizerQuery_history____v) {
   list-style-image: url("chrome://browser/skin/places/history.svg");
 }
 
 /* We want some queries to look like ordinary folders. This must come
    after the (title, query) selector, or it would get overridden. */
 treechildren::-moz-tree-image(title, query, folder) {
   list-style-image: url("chrome://browser/skin/places/folder.svg");
 }
--- a/services/sync/modules/bookmark_validator.js
+++ b/services/sync/modules/bookmark_validator.js
@@ -17,26 +17,18 @@ ChromeUtils.defineModuleGetter(this, "Pl
 
 ChromeUtils.defineModuleGetter(this, "PlacesSyncUtils",
                                "resource://gre/modules/PlacesSyncUtils.jsm");
 
 Cu.importGlobalProperties(["URLSearchParams"]);
 
 var EXPORTED_SYMBOLS = ["BookmarkValidator", "BookmarkProblemData"];
 
-const LEFT_PANE_ROOT_ANNO = "PlacesOrganizer/OrganizerFolder";
-const LEFT_PANE_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
 const QUERY_PROTOCOL = "place:";
 
-// Indicates if a local bookmark tree node should be excluded from syncing.
-function isNodeIgnored(treeNode) {
-  return treeNode.annos && treeNode.annos.some(anno => anno.name == LEFT_PANE_ROOT_ANNO ||
-                                                       anno.name == LEFT_PANE_QUERY_ANNO);
-}
-
 function areURLsEqual(a, b) {
   if (a === b) {
     return true;
   }
   if (a.startsWith(QUERY_PROTOCOL) != b.startsWith(QUERY_PROTOCOL)) {
     return false;
   }
   // Tag queries are special because we rewrite them to point to the
@@ -648,18 +640,16 @@ class BookmarkValidator {
     // still use local IDs. We use this mapping to parse `place:` queries that
     // refer to folders via their local IDs.
     let recordsByQueryId = new Map();
     let syncedRoots = SYNCED_ROOTS;
     const traverse = async (treeNode, synced) => {
       await this.maybeYield();
       if (!synced) {
         synced = syncedRoots.includes(treeNode.guid);
-      } else if (isNodeIgnored(treeNode)) {
-        synced = false;
       }
       let localId = treeNode.id;
       let guid = PlacesSyncUtils.bookmarks.guidToRecordId(treeNode.guid);
       let itemType = "item";
       treeNode.ignored = !synced;
       treeNode.id = guid;
       switch (treeNode.type) {
         case PlacesUtils.TYPE_X_MOZ_PLACE:
--- a/services/sync/tests/unit/test_bookmark_validator.js
+++ b/services/sync/tests/unit/test_bookmark_validator.js
@@ -280,36 +280,26 @@ add_task(async function test_cswc_server
     "guid": "dddddddddddd",
     "title": "",
     "id": 2000,
     "annos": [{
       "name": "places/excludeFromBackup",
       "flags": 0,
       "expires": 4,
       "value": 1
-    }, {
-      "name": "PlacesOrganizer/OrganizerFolder",
-      "flags": 0,
-      "expires": 4,
-      "value": 7
     }],
     "type": "text/x-moz-place-container",
     "children": [{
       "guid": "eeeeeeeeeeee",
       "title": "History",
       "annos": [{
         "name": "places/excludeFromBackup",
         "flags": 0,
         "expires": 4,
         "value": 1
-      }, {
-        "name": "PlacesOrganizer/OrganizerQuery",
-        "flags": 0,
-        "expires": 4,
-        "value": "History"
       }],
       "type": "text/x-moz-place",
       "uri": "place:type=3&sort=4"
     }]
   });
   server.push({
     id: "dddddddddddd",
     parentid: "places",
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -147,18 +147,18 @@ var Bookmarks = Object.freeze({
 
   /**
    * The GUIDs of the user content root folders that we support, for easy access
    * as a set.
    */
   userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
 
   /**
-   * GUIDs associated with virtual queries that are used for display in the left
-   * pane.
+   * GUIDs associated with virtual queries that are used for displaying bookmark
+   * folders in the left pane.
    */
   virtualMenuGuid: "menu_______v",
   virtualToolbarGuid: "toolbar____v",
   virtualUnfiledGuid: "unfiled___v",
   virtualMobileGuid: "mobile____v",
 
   /**
    * Checks if a guid is a virtual root.
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -113,17 +113,18 @@ function serializeNode(aNode, aIsLivemar
   // is coming from.
   data.instanceId = PlacesUtils.instanceId;
 
   let guid = aNode.bookmarkGuid;
   let grandParentId;
 
   // 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)) {
+  if (guid && !PlacesUtils.bookmarks.isVirtualRootItem(guid) &&
+      !PlacesUtils.isVirtualLeftPaneItem(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;
       data.parentGuid = aNode.parent.bookmarkGuid;
     }
 
@@ -338,16 +339,39 @@ var PlacesUtils = {
   TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
   TOPIC_VACUUM_STARTING: "places-vacuum-starting",
   TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin",
   TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success",
   TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed",
 
   ACTION_SCHEME: "moz-action:",
 
+  /**
+    * GUIDs associated with virtual queries that are used for displaying the
+    * top-level folders in the left pane.
+    */
+  virtualAllBookmarksGuid: "allbms_____v",
+  virtualHistoryGuid: "history____v",
+  virtualDownloadsGuid: "downloads__v",
+  virtualTagsGuid: "tags_______v",
+
+  /**
+   * Checks if a guid is a virtual left-pane root.
+   *
+   * @param {String} guid The guid of the item to look for.
+   * @returns {Boolean} true if guid is a virtual root, false otherwise.
+   */
+  isVirtualLeftPaneItem(guid) {
+    return guid == PlacesUtils.virtualAllBookmarksGuid ||
+           guid == PlacesUtils.virtualHistoryGuid ||
+           guid == PlacesUtils.virtualDownloadsGuid ||
+           guid == PlacesUtils.virtualTagsGuid;
+  },
+
+
   asContainer: aNode => asContainer(aNode),
   asQuery: aNode => asQuery(aNode),
 
   endl: NEWLINE,
 
   /**
    * Is a string a valid GUID?
    *
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -1075,16 +1075,21 @@ interface nsINavHistoryQueryOptions : ns
    * 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;
 
   /**
+   * This returns nsINavHistoryQueryResultNode for each left-pane root.
+   */
+  const unsigned short RESULTS_AS_LEFT_PANE_QUERY = 9;
+
+  /**
    * 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.
    */
@@ -1134,17 +1139,17 @@ interface nsINavHistoryQueryOptions : ns
    * Some pages in history are marked "hidden" and thus don't appear by default
    * in queries.  These include automatic framed visits and redirects.  Setting
    * this attribute will return all pages, even hidden ones.  Does nothing for
    * bookmark queries. Defaults to false.
    */
   attribute boolean includeHidden;
 
   /**
-   * This is the maximum number of results that you want. The query is exeucted,
+   * This is the maximum number of results that you want. The query is executed,
    * the results are sorted, and then the top 'maxResults' results are taken
    * and returned. Set to 0 (the default) to get all results.
    *
    * THIS DOES NOT WORK IN CONJUNCTION WITH SORTING BY TITLE. This is because
    * sorting by title requires us to sort after using locale-sensetive sorting
    * (as opposed to letting the database do it for us).
    *
    * Instead, we get the result ordered by date, pick the maxResult most recent
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -775,16 +775,20 @@ nsNavHistory::NormalizeTime(uint32_t aRe
 //      This query is evaluatable using EvaluateQueryForNode to do live
 //      updating.
 //    QUERYUPDATE_COMPLEX:
 //      This query is not evaluatable using EvaluateQueryForNode. When something
 //      happens that this query updates, you will need to re-run the query.
 //    QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
 //      A complex query that additionally has dependence on bookmarks. All
 //      bookmark-dependent queries fall under this category.
+//    QUERYUPDATE_MOBILEPREF:
+//      A complex query but only updates when the mobile preference changes.
+//    QUERYUPDATE_NONE:
+//      A query that never updates, e.g. the left-pane root query.
 //
 //    aHasSearchTerms will be set to true if the query has any dependence on
 //    keywords. When there is no dependence on keywords, we can handle title
 //    change operations as simple instead of complex.
 
 uint32_t
 nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQueries,
                                     nsNavHistoryQueryOptions* aOptions,
@@ -830,16 +834,20 @@ nsNavHistory::GetUpdateRequirements(cons
   if (aOptions->ResultType() ==
         nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
       return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
 
   if (aOptions->ResultType() ==
         nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY)
       return QUERYUPDATE_MOBILEPREF;
 
+  if (aOptions->ResultType() ==
+        nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY)
+      return QUERYUPDATE_NONE;
+
   // 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;
 
   if (aQueries.Count() == 1 && domainBasedItems)
@@ -1411,16 +1419,17 @@ private:
   nsresult Select();
 
   nsresult SelectAsURI();
   nsresult SelectAsVisit();
   nsresult SelectAsDay();
   nsresult SelectAsSite();
   nsresult SelectAsTag();
   nsresult SelectAsRoots();
+  nsresult SelectAsLeftPane();
 
   nsresult Where();
   nsresult GroupBy();
   nsresult OrderBy();
   nsresult Limit();
 
   void OrderByColumnIndexAsc(int32_t aIndex);
   void OrderByColumnIndexDesc(int32_t aIndex);
@@ -1517,16 +1526,21 @@ PlacesSQLQueryBuilder::Select()
       NS_ENSURE_SUCCESS(rv, rv);
       break;
 
     case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
       rv = SelectAsRoots();
       NS_ENSURE_SUCCESS(rv, rv);
       break;
 
+    case nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY:
+      rv = SelectAsLeftPane();
+      NS_ENSURE_SUCCESS(rv, rv);
+      break;
+
     default:
       NS_NOTREACHED("Invalid result type");
   }
   return NS_OK;
 }
 
 nsresult
 PlacesSQLQueryBuilder::SelectAsURI()
@@ -1971,16 +1985,58 @@ PlacesSQLQueryBuilder::SelectAsRoots()
     toolbarTitle.get(),
     menuTitle.get(),
     unfiledTitle.get(),
     mobileString.get());
   return NS_OK;
 }
 
 nsresult
+PlacesSQLQueryBuilder::SelectAsLeftPane()
+{
+  nsNavHistory *history = nsNavHistory::GetHistoryService();
+  NS_ENSURE_STATE(history);
+
+  nsAutoCString historyTitle;
+  nsAutoCString downloadsTitle;
+  nsAutoCString tagsTitle;
+  nsAutoCString allBookmarksTitle;
+
+  history->GetStringFromName("OrganizerQueryHistory", historyTitle);
+  history->GetStringFromName("OrganizerQueryDownloads", downloadsTitle);
+  history->GetStringFromName("TagsFolderTitle", tagsTitle);
+  history->GetStringFromName("OrganizerQueryAllBookmarks", allBookmarksTitle);
+
+  mQueryString = nsPrintfCString(
+    "SELECT * FROM ("
+        "VALUES"
+              "(null, 'place:type=%d&sort=%d', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'history____v', null), "
+              "(null, 'place:transition=%d&sort=%d', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'downloads__v', null), "
+              "(null, 'place:type=%d&sort=%d', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'tags_______v', null), "
+              "(null, 'place:type=%d', '%s', null, null, null, "
+               "null, null, 0, 0, null, null, null, null, 'allbms_____v', null) "
+    ")",
+    nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY,
+    nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
+    historyTitle.get(),
+    nsINavHistoryService::TRANSITION_DOWNLOAD,
+    nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
+    downloadsTitle.get(),
+    nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY,
+    nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING,
+    tagsTitle.get(),
+    nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY,
+    allBookmarksTitle.get());
+  return NS_OK;
+}
+
+nsresult
 PlacesSQLQueryBuilder::Where()
 {
 
   // Set query options
   nsAutoCString additionalVisitsConditions;
   nsAutoCString additionalPlacesConditions;
 
   if (!mIncludeHidden) {
@@ -3829,17 +3885,18 @@ 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) {
+    if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
+        aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_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);
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -35,16 +35,17 @@
 #endif
 
 #define QUERYUPDATE_TIME 0
 #define QUERYUPDATE_SIMPLE 1
 #define QUERYUPDATE_COMPLEX 2
 #define QUERYUPDATE_COMPLEX_WITH_BOOKMARKS 3
 #define QUERYUPDATE_HOST 4
 #define QUERYUPDATE_MOBILEPREF 5
+#define QUERYUPDATE_NONE 6
 
 // Clamp title and URL to generously large, but not too large, length.
 // See bug 319004 for details.
 #define URI_LENGTH_MAX 65536
 #define TITLE_LENGTH_MAX 4096
 
 // Microsecond timeout for "recent" events such as typed and bookmark following.
 // If you typed it more than this time ago, it's not recent.
--- a/toolkit/components/places/nsNavHistoryQuery.cpp
+++ b/toolkit/components/places/nsNavHistoryQuery.cpp
@@ -1354,22 +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_ROOTS_QUERY)
+  if (aType > RESULTS_AS_LEFT_PANE_QUERY)
     return NS_ERROR_INVALID_ARG;
   // 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)
+      aType == RESULTS_AS_ROOTS_QUERY || aType == RESULTS_AS_LEFT_PANE_QUERY)
     mQueryType = QUERY_TYPE_BOOKMARKS;
   mResultType = aType;
   return NS_OK;
 }
 
 // excludeItems
 NS_IMETHODIMP
 nsNavHistoryQueryOptions::GetExcludeItems(bool* aExclude)
@@ -1462,17 +1462,19 @@ nsNavHistoryQueryOptions::GetQueryType(u
   return NS_OK;
 }
 NS_IMETHODIMP
 nsNavHistoryQueryOptions::SetQueryType(uint16_t aQueryType)
 {
   // Tag query and containers are forced to QUERY_TYPE_BOOKMARKS when the
   // resultType is set.
   if (mResultType == RESULTS_AS_TAG_CONTENTS ||
-      mResultType == RESULTS_AS_TAG_QUERY)
+      mResultType == RESULTS_AS_TAG_QUERY ||
+      mResultType == RESULTS_AS_LEFT_PANE_QUERY ||
+      mResultType == RESULTS_AS_ROOTS_QUERY)
    return NS_OK;
   mQueryType = aQueryType;
   return NS_OK;
 }
 
 // asyncEnabled
 NS_IMETHODIMP
 nsNavHistoryQueryOptions::GetAsyncEnabled(bool* _asyncEnabled)
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -1808,17 +1808,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_ROOTS_QUERY;
+         resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
+         resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_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.
@@ -1881,17 +1882,18 @@ nsNavHistoryQueryResultNode::GetHasChild
     return NS_OK;
   }
 
   uint16_t resultType = mOptions->ResultType();
 
   // Tags are always populated, otherwise they are removed.
   if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS ||
       // AllBookmarks also always has children.
-      resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY) {
+      resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
+      resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_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);
@@ -2107,16 +2109,23 @@ nsNavHistoryQueryResultNode::FillChildre
   // if we are limiting our results remove items from the end of the
   // mChildren array after sorting. This is done for root node only.
   // note, if count < max results, we won't do anything.
   if (!mParent && mOptions->MaxResults()) {
     while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
       mChildren.RemoveObjectAt(mChildren.Count() - 1);
   }
 
+  // If we're not updating the query, we don't need to add listeners, so bail
+  // out early.
+  if (mLiveUpdate == QUERYUPDATE_NONE) {
+    mContentsValid = true;
+    return NS_OK;
+  }
+
   nsNavHistoryResult* result = GetResult();
   NS_ENSURE_STATE(result);
 
   if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
       mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED) {
     // Date containers that contain site containers have no reason to observe
     // history, if the inside site container is expanded it will update,
     // otherwise we are going to refresh the parent query.
@@ -2191,22 +2200,31 @@ nsNavHistoryQueryResultNode::Refresh()
   // left in a local copy of the observers array.
   if (mIndentLevel > -1 && !mParent)
     return NS_OK;
 
   // Do not refresh if we are not expanded or if we are child of a query
   // containing other queries.  In this case calling Refresh for each child
   // query could cause a major slowdown.  We should not refresh nested
   // queries, since we will already refresh the parent one.
-  if (!mExpanded ||
-      (mParent && mParent->IsQuery() &&
-       mParent->GetAsQuery()->IsContainersQuery())) {
-    // Don't update, just invalidate and unhook
+  // The only exception to this, is if the parent query is of QUERYUPDATE_NONE,
+  // this can be the case for the RESULTS_AS_TAG_QUERY
+  // under RESULTS_AS_LEFT_PANE_QUERY.
+  if (!mExpanded) {
     ClearChildren(true);
-    return NS_OK; // no updates in tree state
+    return NS_OK;
+  }
+
+  if (mParent && mParent->IsQuery()) {
+    nsNavHistoryQueryResultNode* parent = mParent->GetAsQuery();
+    if (parent->IsContainersQuery() && parent->mLiveUpdate != QUERYUPDATE_NONE) {
+      // Don't update, just invalidate and unhook
+      ClearChildren(true);
+      return NS_OK; // no updates in tree state
+    }
   }
 
   if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
     ClearChildren(true);
   else
     ClearChildren(false);
 
   // Ignore errors from FillChildren, since we will still want to refresh
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/queries/test_results-as-left-pane.js
@@ -0,0 +1,64 @@
+"use strict";
+
+const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";
+
+const expectedRoots = [{
+  title: "OrganizerQueryHistory",
+  uri: `place:sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING}&type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY}`,
+  guid: "history____v",
+}, {
+  title: "OrganizerQueryDownloads",
+  uri: `place:transition=${Ci.nsINavHistoryService.TRANSITION_DOWNLOAD}&sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING}`,
+  guid: "downloads__v",
+}, {
+  title: "TagsFolderTitle",
+  uri: `place:sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING}&type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY}&queryType=1`,
+  guid: "tags_______v",
+}, {
+  title: "OrganizerQueryAllBookmarks",
+  uri: `place:type=${Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY}&queryType=1`,
+  guid: "allbms_____v",
+}];
+
+const placesStrings = Services.strings.createBundle("chrome://places/locale/places.properties");
+
+function getLeftPaneQuery() {
+  var query = PlacesUtils.history.getNewQuery();
+
+  // Options
+  var options = PlacesUtils.history.getNewQueryOptions();
+  options.resultType = options.RESULTS_AS_LEFT_PANE_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 = getLeftPaneQuery();
+  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;
+});
--- 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-left-pane.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]
--- a/toolkit/locales/en-US/chrome/places/places.properties
+++ b/toolkit/locales/en-US/chrome/places/places.properties
@@ -2,16 +2,19 @@
 # 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/.
 
 BookmarksMenuFolderTitle=Bookmarks Menu
 BookmarksToolbarFolderTitle=Bookmarks Toolbar
 OtherBookmarksFolderTitle=Other Bookmarks
 TagsFolderTitle=Tags
 MobileBookmarksFolderTitle=Mobile Bookmarks
+OrganizerQueryHistory=History
+OrganizerQueryDownloads=Downloads
+OrganizerQueryAllBookmarks=All Bookmarks
 
 # LOCALIZATION NOTE (dateName):
 # These are used to generate history containers when history is grouped by date
 finduri-AgeInDays-is-0=Today
 finduri-AgeInDays-is-1=Yesterday
 finduri-AgeInDays-is=%S days ago
 finduri-AgeInDays-last-is=Last %S days
 finduri-AgeInDays-isgreater=Older than %S days