Bug 1310295 - Make left pane queries virtual in the Places Library window. r?mak,r?kitcambridge draft
authorMark Banner <standard8@mozilla.com>
Fri, 16 Feb 2018 20:30:04 +0000
changeset 767466 4e29f78ae9b3345e86bdc4ec6251b6927aae0039
parent 767347 80b4777a6421d8df4bb27ac23fb607c318a3006c
child 767467 ccb4e30c799479d3a0efabd63db1ab9085e124ad
push id102609
push userbmo:standard8@mozilla.com
push dateWed, 14 Mar 2018 17:33:35 +0000
reviewersmak, kitcambridge
bugs1310295
milestone61.0a1
Bug 1310295 - Make left pane queries virtual in the Places Library window. r?mak,r?kitcambridge 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