Bug 1460579 - Replace the 'bookmarkPropertiesDialog/folderLastUsed' annotation with a key/value pair in moz_meta. r=mak
authorMark Banner <standard8@mozilla.com>
Fri, 11 May 2018 07:35:59 +0100
changeset 421381 d31e89c47054194dbdc4c704f029f369277282c3
parent 421380 74fded5d5a6b3e3e8a4e51eea3810e2a0ec53bbc
child 421382 0a4e0a2002effec8e71e3a3060eec39d41ee71ba
push id64874
push usermbanner@mozilla.com
push dateTue, 05 Jun 2018 16:12:24 +0000
treeherderautoland@d31e89c47054 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1460579
milestone62.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 1460579 - Replace the 'bookmarkPropertiesDialog/folderLastUsed' annotation with a key/value pair in moz_meta. r=mak MozReview-Commit-ID: GpEPxOMDret
browser/base/content/browser-places.js
browser/components/places/PlacesUIUtils.jsm
browser/components/places/content/editBookmark.js
browser/components/places/tests/browser/browser.ini
browser/components/places/tests/browser/browser_bookmarkProperties_remember_folders.js
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/head_migration.js
toolkit/components/places/tests/migration/test_current_from_v43.js
toolkit/components/places/tests/migration/test_current_from_v50.js
toolkit/components/places/tests/migration/xpcshell.ini
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -14,16 +14,19 @@ var StarUI = {
   _batching: false,
   _isNewBookmark: false,
   _isComposing: false,
   _autoCloseTimer: 0,
   // The autoclose timer is diasbled if the user interacts with the
   // popup, such as making a change through typing or clicking on
   // the popup.
   _autoCloseTimerEnabled: true,
+  // The autoclose timeout length. 3500ms matches the timeout that Pocket uses
+  // in browser/extensions/pocket/content/panels/js/saved.js.
+  _autoCloseTimeout: 3500,
   _removeBookmarksOnPopupHidden: false,
 
   _element(aID) {
     return document.getElementById(aID);
   },
 
   // Edit-bookmark panel
   get panel() {
@@ -79,18 +82,23 @@ var StarUI = {
       case "mousemove":
         clearTimeout(this._autoCloseTimer);
         // The autoclose timer is not disabled on generic mouseout
         // because the user may not have actually interacted with the popup.
         break;
       case "popuphidden": {
         clearTimeout(this._autoCloseTimer);
         if (aEvent.originalTarget == this.panel) {
-          if (!this._element("editBookmarkPanelContent").hidden)
+          let selectedFolderGuid;
+
+          if (!this._element("editBookmarkPanelContent").hidden) {
+            // Get the folder first, before we uninit the overlay.
+            selectedFolderGuid = gEditItemOverlay.selectedFolderGuid;
             this.quitEditMode();
+          }
 
           if (this._anchorToolbarButton) {
             this._anchorToolbarButton.removeAttribute("open");
             this._anchorToolbarButton = null;
           }
           this._restoreCommandsState();
           let removeBookmarksOnPopupHidden = this._removeBookmarksOnPopupHidden;
           this._removeBookmarksOnPopupHidden = false;
@@ -108,16 +116,20 @@ var StarUI = {
             }
             // Remove all bookmarks for the bookmark's url, this also removes
             // the tags for the url.
             PlacesTransactions.Remove(guidsForRemoval)
                               .transact().catch(Cu.reportError);
           } else if (this._isNewBookmark) {
             LibraryUI.triggerLibraryAnimation("bookmark");
           }
+
+          if (!removeBookmarksOnPopupHidden) {
+            this._storeRecentlyUsedFolder(selectedFolderGuid).catch(console.error);
+          }
         }
         break;
       }
       case "keypress":
         clearTimeout(this._autoCloseTimer);
         this._autoCloseTimerEnabled = false;
 
         if (aEvent.defaultPrevented) {
@@ -181,19 +193,17 @@ var StarUI = {
         // Explicit fall-through
       case "popupshown":
         // Don't handle events for descendent elements.
         if (aEvent.target != aEvent.currentTarget) {
           break;
         }
         // auto-close if new and not interacted with
         if (this._isNewBookmark && !this._isComposing) {
-          // 3500ms matches the timeout that Pocket uses in
-          // browser/extensions/pocket/content/panels/js/saved.js
-          let delay = 3500;
+          let delay = this._autoCloseTimeout;
           if (this._closePanelQuickForTesting) {
             delay /= 10;
           }
           clearTimeout(this._autoCloseTimer);
           this._autoCloseTimer = setTimeout(() => {
             if (!this.panel.mozMatchesSelector(":hover")) {
               this.panel.hidePopup(true);
             }
@@ -335,16 +345,43 @@ var StarUI = {
 
   endBatch() {
     if (!this._batching)
       return;
 
     this._batchBlockingDeferred.resolve();
     this._batchBlockingDeferred = null;
     this._batching = false;
+  },
+
+  async _storeRecentlyUsedFolder(selectedFolderGuid) {
+    // These are displayed by default, so don't save the folder for them.
+    if (!selectedFolderGuid ||
+        PlacesUtils.bookmarks.userContentRoots.includes(selectedFolderGuid)) {
+      return;
+    }
+
+    // List of recently used folders:
+    let lastUsedFolderGuids =
+      await PlacesUtils.metadata.get(PlacesUIUtils.LAST_USED_FOLDERS_META_KEY, []);
+
+    let index = lastUsedFolderGuids.indexOf(selectedFolderGuid);
+    if (index > 1) {
+      // The guid is in the array but not the most recent.
+      lastUsedFolderGuids.splice(index, 1);
+      lastUsedFolderGuids.unshift(selectedFolderGuid);
+    } else if (index == -1) {
+      lastUsedFolderGuids.unshift(selectedFolderGuid);
+    }
+    if (lastUsedFolderGuids.length > 5) {
+      lastUsedFolderGuids.pop();
+    }
+
+    await PlacesUtils.metadata.set(PlacesUIUtils.LAST_USED_FOLDERS_META_KEY,
+                                   lastUsedFolderGuids);
   }
 };
 
 var PlacesCommandHook = {
   /**
    * Adds a bookmark to the page loaded in the given browser.
    *
    * @param aBrowser
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -206,16 +206,17 @@ let InternalFaviconLoader = {
     let loadDataForWindow = gFaviconLoadDataMap.get(win);
     loadDataForWindow.push(loadData);
   },
 };
 
 var PlacesUIUtils = {
   LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
   DESCRIPTION_ANNO: "bookmarkProperties/description",
+  LAST_USED_FOLDERS_META_KEY: "bookmarks/lastusedfolders",
 
   /**
    * Makes a URI from a spec, and do fixup
    * @param   aSpec
    *          The string spec of the URI
    * @return A URI object for the spec.
    */
   createFixedURI: function PUIU_createFixedURI(aSpec) {
@@ -1027,17 +1028,17 @@ var PlacesUIUtils = {
 
   setMouseoverURL(url, win) {
     // When the browser window is closed with an open sidebar, the sidebar
     // unload event happens after the browser's one.  In this case
     // top.XULBrowserWindow has been nullified already.
     if (win.top.XULBrowserWindow) {
       win.top.XULBrowserWindow.setOverLink(url, null);
     }
-  }
+  },
 };
 
 // These are lazy getters to avoid importing PlacesUtils immediately.
 XPCOMUtils.defineLazyGetter(PlacesUIUtils, "PLACES_FLAVORS", () => {
   return [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
           PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
           PlacesUtils.TYPE_X_MOZ_PLACE];
 });
--- a/browser/components/places/content/editBookmark.js
+++ b/browser/components/places/content/editBookmark.js
@@ -1,15 +1,14 @@
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
 const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
 
 var gEditItemOverlay = {
   _observersAdded: false,
   _staticFoldersListBuilt: false,
 
   _paneInfo: null,
   _setPaneInfo(aInitInfo) {
@@ -41,33 +40,31 @@ var gEditItemOverlay = {
     let uri = isURI || isTag ? Services.io.newURI(node.uri) : null;
     let title = node ? node.title : null;
     let isBookmark = isItem && isURI;
     let bulkTagging = !node;
     let uris = bulkTagging ? aInitInfo.uris : null;
     let visibleRows = new Set();
     let isParentReadOnly = false;
     let postData = aInitInfo.postData;
-    let parentId = -1;
     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);
-      parentId = parent.itemId;
       parentGuid = parent.bookmarkGuid;
     }
 
     let focusedElement = aInitInfo.focusedElement;
     let onPanelReady = aInitInfo.onPanelReady;
 
-    return this._paneInfo = { itemId, itemGuid, parentId, parentGuid, isItem,
+    return this._paneInfo = { itemId, itemGuid, parentGuid, isItem,
                               isURI, uri, title,
                               isBookmark, isFolderShortcut, isParentReadOnly,
                               bulkTagging, uris,
                               visibleRows, postData, isTag, focusedElement,
                               onPanelReady, tag };
   },
 
   get initialized() {
@@ -204,17 +201,17 @@ var gEditItemOverlay = {
       }
     }
 
     // For sanity ensure that the implementer has uninited the panel before
     // trying to init it again, or we could end up leaking due to observers.
     if (this.initialized)
       this.uninitPanel(false);
 
-    let { parentId, isItem, isURI,
+    let { parentGuid, isItem, isURI,
           isBookmark, bulkTagging, uris,
           visibleRows, focusedElement,
           onPanelReady } = this._setPaneInfo(aInfo);
 
     let showOrCollapse =
       (rowId, isAppropriateForInput, nameInHiddenRows = null) => {
         let visible = isAppropriateForInput;
         if (visible && "hiddenRows" in aInfo && nameInHiddenRows)
@@ -253,17 +250,17 @@ var gEditItemOverlay = {
       this._initLoadInSidebar();
     }
 
     // Folder picker.
     // Technically we should check that the item is not moveable, but that's
     // not cheap (we don't always have the parent), and there's no use case for
     // this (it's only the Star UI that shows the folderPicker)
     if (showOrCollapse("folderRow", isItem, "folderPicker")) {
-      this._initFolderMenuList(parentId).catch(Cu.reportError);
+      this._initFolderMenuList(parentGuid).catch(Cu.reportError);
     }
 
     // Selection count.
     if (showOrCollapse("selectionCount", bulkTagging)) {
       this._element("itemsCountText").value =
         PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
                                       uris.length,
                                       [uris.length]);
@@ -352,97 +349,84 @@ var gEditItemOverlay = {
       }
     }
   },
 
   /**
    * Appends a menu-item representing a bookmarks folder to a menu-popup.
    * @param aMenupopup
    *        The popup to which the menu-item should be added.
-   * @param aFolderId
+   * @param aFolderGuid
    *        The identifier of the bookmarks folder.
    * @param aTitle
    *        The title to use as a label.
    * @return the new menu item.
    */
-  _appendFolderItemToMenupopup(aMenupopup, aFolderId, aTitle) {
+  _appendFolderItemToMenupopup(aMenupopup, aFolderGuid, aTitle) {
     // First make sure the folders-separator is visible
     this._element("foldersSeparator").hidden = false;
 
     var folderMenuItem = document.createElement("menuitem");
-    var folderTitle = aTitle;
-    folderMenuItem.folderId = aFolderId;
-    folderMenuItem.setAttribute("label", folderTitle);
+    folderMenuItem.folderGuid = aFolderGuid;
+    folderMenuItem.setAttribute("label", aTitle);
     folderMenuItem.className = "menuitem-iconic folder-icon";
     aMenupopup.appendChild(folderMenuItem);
     return folderMenuItem;
   },
 
-  async _initFolderMenuList(aSelectedFolder) {
+  async _initFolderMenuList(aSelectedFolderGuid) {
     // clean up first
     var menupopup = this._folderMenuList.menupopup;
     while (menupopup.childNodes.length > 6)
       menupopup.removeChild(menupopup.lastChild);
 
     // Build the static list
     if (!this._staticFoldersListBuilt) {
       let unfiledItem = this._element("unfiledRootItem");
       unfiledItem.label = PlacesUtils.getString("OtherBookmarksFolderTitle");
-      unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
+      unfiledItem.folderGuid = PlacesUtils.bookmarks.unfiledGuid;
       let bmMenuItem = this._element("bmRootItem");
       bmMenuItem.label = PlacesUtils.getString("BookmarksMenuFolderTitle");
-      bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
+      bmMenuItem.folderGuid = PlacesUtils.bookmarks.menuGuid;
       let toolbarItem = this._element("toolbarFolderItem");
       toolbarItem.label = PlacesUtils.getString("BookmarksToolbarFolderTitle");
-      toolbarItem.folderId = PlacesUtils.toolbarFolderId;
+      toolbarItem.folderGuid = PlacesUtils.bookmarks.toolbarGuid;
       this._staticFoldersListBuilt = true;
     }
 
     // List of recently used folders:
-    var folderIds =
-      PlacesUtils.annotations.getItemsWithAnnotation(LAST_USED_ANNO);
+    let lastUsedFolderGuids =
+      await PlacesUtils.metadata.get(PlacesUIUtils.LAST_USED_FOLDERS_META_KEY, []);
 
     /**
-     * The value of the LAST_USED_ANNO annotation is the time (in the form of
-     * Date.getTime) at which the folder has been last used.
+     * The list of last used folders is sorted in most-recent first order.
      *
      * First we build the annotated folders array, each item has both the
      * folder identifier and the time at which it was last-used by this dialog
      * set. Then we sort it descendingly based on the time field.
      */
     this._recentFolders = [];
-    for (let folderId of folderIds) {
-      var lastUsed =
-        PlacesUtils.annotations.getItemAnnotation(folderId, LAST_USED_ANNO);
-      let guid = await PlacesUtils.promiseItemGuid(folderId);
+    for (let guid of lastUsedFolderGuids) {
       let bm = await PlacesUtils.bookmarks.fetch(guid);
-      // Since this could be a root mobile folder, we should get the proper
-      // title.
-      let title = PlacesUtils.bookmarks.getLocalizedTitle(bm);
-      this._recentFolders.push({ folderId, guid, title, lastUsed });
+      if (bm) {
+        let title = PlacesUtils.bookmarks.getLocalizedTitle(bm);
+        this._recentFolders.push({ guid, title });
+      }
     }
-    this._recentFolders.sort(function(a, b) {
-      if (b.lastUsed < a.lastUsed)
-        return -1;
-      if (b.lastUsed > a.lastUsed)
-        return 1;
-      return 0;
-    });
 
     var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
                                  this._recentFolders.length);
     for (let i = 0; i < numberOfItems; i++) {
       await this._appendFolderItemToMenupopup(menupopup,
-                                              this._recentFolders[i].folderId,
+                                              this._recentFolders[i].guid,
                                               this._recentFolders[i].title);
     }
 
-    let selectedFolderGuid = await PlacesUtils.promiseItemGuid(aSelectedFolder);
-    let title = (await PlacesUtils.bookmarks.fetch(selectedFolderGuid)).title;
-    var defaultItem = this._getFolderMenuItem(aSelectedFolder, title);
+    let title = (await PlacesUtils.bookmarks.fetch(aSelectedFolderGuid)).title;
+    var defaultItem = this._getFolderMenuItem(aSelectedFolderGuid, title);
     this._folderMenuList.selectedItem = defaultItem;
 
     // Set a selectedIndex attribute to show special icons
     this._folderMenuList.setAttribute("selectedIndex",
                                       this._folderMenuList.selectedIndex);
 
     // Hide the folders-separator if no folder is annotated as recently-used
     this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
@@ -473,16 +457,20 @@ var gEditItemOverlay = {
       PlacesUtils.bookmarks.removeObserver(this);
       this._observersAdded = false;
     }
 
     this._setPaneInfo(null);
     this._firstEditedField = "";
   },
 
+  get selectedFolderGuid() {
+    return this._folderMenuList.selectedItem && this._folderMenuList.selectedItem.folderGuid;
+  },
+
   onTagsFieldChange() {
     // Check for _paneInfo existing as the dialog may be closing but receiving
     // async updates from unresolved promises.
     if (this._paneInfo &&
         (this._paneInfo.isURI || this._paneInfo.bulkTagging)) {
       this._updateTags().then(
         anyChanges => {
           // Check _paneInfo here as we might be closing the dialog.
@@ -690,145 +678,102 @@ var gEditItemOverlay = {
     }
   },
 
   /**
    * Get the corresponding menu-item in the folder-menu-list for a bookmarks
    * folder if such an item exists. Otherwise, this creates a menu-item for the
    * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
    * the new item replaces the last menu-item.
-   * @param aFolderId
+   * @param aFolderGuid
    *        The identifier of the bookmarks folder.
    * @param aTitle
    *        The title to use in case of menuitem creation.
    * @return handle to the menuitem.
    */
-  _getFolderMenuItem(aFolderId, aTitle) {
+  _getFolderMenuItem(aFolderGuid, aTitle) {
     let menupopup = this._folderMenuList.menupopup;
     let menuItem = Array.prototype.find.call(
-      menupopup.childNodes, item => item.folderId === aFolderId);
+      menupopup.childNodes, item => item.folderGuid === aFolderGuid);
     if (menuItem !== undefined)
       return menuItem;
 
     // 3 special folders + separator + folder-items-count limit
     if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
       menupopup.removeChild(menupopup.lastChild);
 
-    return this._appendFolderItemToMenupopup(menupopup, aFolderId, aTitle);
+    return this._appendFolderItemToMenupopup(menupopup, aFolderGuid, aTitle);
   },
 
   async onFolderMenuListCommand(aEvent) {
     // Check for _paneInfo existing as the dialog may be closing but receiving
     // async updates from unresolved promises.
     if (!this._paneInfo) {
       return;
     }
     // Set a selectedIndex attribute to show special icons
     this._folderMenuList.setAttribute("selectedIndex",
                                       this._folderMenuList.selectedIndex);
 
     if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
       // reset the selection back to where it was and expand the tree
       // (this menu-item is hidden when the tree is already visible
-      let item = this._getFolderMenuItem(this._paneInfo.parentId,
+      let item = this._getFolderMenuItem(this._paneInfo.parentGuid,
                                          this._paneInfo.title);
       this._folderMenuList.selectedItem = item;
       // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
       // menulist right away
       setTimeout(() => this.toggleFolderTreeVisibility(), 100);
       return;
     }
 
     // Move the item
-    let containerId = this._folderMenuList.selectedItem.folderId;
-    if (this._paneInfo.parentId != containerId &&
-        this._paneInfo.itemId != containerId) {
-      let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
-      let guid = this._paneInfo.itemGuid;
-      await PlacesTransactions.Move({ guid, newParentGuid }).transact();
-
-      // Mark the containing folder as recently-used if it isn't in the
-      // static list
-      if (containerId != PlacesUtils.unfiledBookmarksFolderId &&
-          containerId != PlacesUtils.toolbarFolderId &&
-          containerId != PlacesUtils.bookmarksMenuFolderId) {
-        this._markFolderAsRecentlyUsed(containerId)
-            .catch(Cu.reportError);
-      }
+    let containerGuid = this._folderMenuList.selectedItem.folderGuid;
+    if (this._paneInfo.parentGuid != containerGuid &&
+        this._paneInfo.itemGuid != containerGuid) {
+      await PlacesTransactions.Move({
+        guid: this._paneInfo.itemGuid,
+        newParentGuid: containerGuid
+      }).transact();
 
       // Auto-show the bookmarks toolbar when adding / moving an item there.
-      if (containerId == PlacesUtils.toolbarFolderId) {
+      if (containerGuid == PlacesUtils.bookmarks.toolbarGuid) {
         Services.obs.notifyObservers(null, "autoshow-bookmarks-toolbar");
       }
     }
 
     // Update folder-tree selection
     var folderTreeRow = this._element("folderTreeRow");
     if (!folderTreeRow.collapsed) {
       var selectedNode = this._folderTree.selectedNode;
       if (!selectedNode ||
-          PlacesUtils.getConcreteItemId(selectedNode) != containerId)
-        this._folderTree.selectItems([containerId]);
+          PlacesUtils.getConcreteItemGuid(selectedNode) != containerGuid)
+        this._folderTree.selectItems([containerGuid]);
     }
   },
 
   onFolderTreeSelect() {
     var selectedNode = this._folderTree.selectedNode;
 
     // Disable the "New Folder" button if we cannot create a new folder
     this._element("newFolderButton")
         .disabled = !this._folderTree.insertionPoint || !selectedNode;
 
     if (!selectedNode)
       return;
 
-    var folderId = PlacesUtils.getConcreteItemId(selectedNode);
-    if (this._folderMenuList.selectedItem.folderId == folderId)
+    var folderGuid = PlacesUtils.getConcreteItemGuid(selectedNode);
+    if (this._folderMenuList.selectedItem.folderGuid == folderGuid)
       return;
 
-    var folderItem = this._getFolderMenuItem(folderId, selectedNode.title);
+    var folderItem = this._getFolderMenuItem(folderGuid, selectedNode.title);
     this._folderMenuList.selectedItem = folderItem;
     folderItem.doCommand();
   },
 
-  async _markFolderAsRecentlyUsed(aFolderId) {
-    // Expire old unused recent folders.
-    let guids = [];
-    while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
-      let folderId = this._recentFolders.pop().folderId;
-      let guid = await PlacesUtils.promiseItemGuid(folderId);
-      guids.push(guid);
-    }
-    if (guids.length > 0) {
-      let annotation = this._getLastUsedAnnotationObject(false);
-      PlacesTransactions.Annotate({ guids, annotation  })
-                        .transact().catch(Cu.reportError);
-    }
-
-    // Mark folder as recently used
-    let annotation = this._getLastUsedAnnotationObject(true);
-    let guid = await PlacesUtils.promiseItemGuid(aFolderId);
-    PlacesTransactions.Annotate({ guid, annotation })
-                      .transact().catch(Cu.reportError);
-  },
-
-  /**
-   * Returns an object which could then be used to set/unset the
-   * LAST_USED_ANNO annotation for a folder.
-   *
-   * @param aLastUsed
-   *        Whether to set or unset the LAST_USED_ANNO annotation.
-   * @returns an object representing the annotation which could then be used
-   *          with the transaction manager.
-   */
-  _getLastUsedAnnotationObject(aLastUsed) {
-    return { name: LAST_USED_ANNO,
-             value: aLastUsed ? new Date().getTime() : null };
-  },
-
   _rebuildTagsSelectorList() {
     let tagsSelector = this._element("tagsSelector");
     let tagsSelectorRow = this._element("tagsSelectorRow");
     if (tagsSelectorRow.collapsed)
       return;
 
     // Save the current scroll position and restore it after the rebuild.
     let firstIndex = tagsSelector.getIndexOfFirstVisibleRow();
@@ -1069,28 +1014,27 @@ var gEditItemOverlay = {
   },
 
   onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, type, guid,
               oldParentGuid, newParentGuid) {
     if (!this._paneInfo.isItem || this._paneInfo.itemId != id) {
       return;
     }
 
-    this._paneInfo.parentId = newParentId;
     this._paneInfo.parentGuid = newParentGuid;
 
     if (!this._paneInfo.visibleRows.has("folderRow") ||
-        newParentId == this._folderMenuList.selectedItem.folderId) {
+        newParentGuid == this._folderMenuList.selectedItem.folderGuid) {
       return;
     }
 
     // Just setting selectItem _does not_ trigger oncommand, so we don't
     // recurse.
     PlacesUtils.bookmarks.fetch(newParentGuid).then(bm => {
-      this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentId,
+      this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentGuid,
                                                                   bm.title);
     });
   },
 
   onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
     this._lastNewItem = aItemId;
   },
 
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -30,16 +30,17 @@ support-files =
 [browser_bookmarkProperties_addKeywordForThisSearch.js]
 [browser_bookmarkProperties_addLivemark.js]
 [browser_bookmarkProperties_bookmarkAllTabs.js]
 [browser_bookmarkProperties_cancel.js]
 [browser_bookmarkProperties_editFolder.js]
 [browser_bookmarkProperties_editTagContainer.js]
 [browser_bookmarkProperties_no_user_actions.js]
 [browser_bookmarkProperties_readOnlyRoot.js]
+[browser_bookmarkProperties_remember_folders.js]
 [browser_bookmarksProperties.js]
 [browser_check_correct_controllers.js]
 [browser_click_bookmarks_on_toolbar.js]
 [browser_controller_onDrop_sidebar.js]
 [browser_controller_onDrop_tagFolder.js]
 [browser_controller_onDrop.js]
 [browser_copy_query_without_tree.js]
 subsuite = clipboard
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_remember_folders.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+/**
+ * Tests that multiple tags can be added to a bookmark using the star-shaped button, the library and the sidebar.
+ */
+
+const bookmarkPanel = document.getElementById("editBookmarkPanel");
+let folders;
+
+async function clickBookmarkStar() {
+  let shownPromise = promisePopupShown(bookmarkPanel);
+  BookmarkingUI.star.click();
+  await shownPromise;
+}
+
+async function hideBookmarksPanel() {
+  let hiddenPromise = promisePopupHidden(bookmarkPanel);
+  // Confirm and close the dialog.
+  document.getElementById("editBookmarkPanelDoneButton").click();
+  await hiddenPromise;
+}
+
+async function openPopupAndSelectFolder(guid) {
+  await clickBookmarkStar();
+
+  // Expand the folder tree.
+  document.getElementById("editBMPanel_foldersExpander").click();
+  document.getElementById("editBMPanel_folderTree").selectItems([guid]);
+
+  await hideBookmarksPanel();
+  // Ensure the meta data has had chance to be written to disk.
+  await PlacesTestUtils.promiseAsyncUpdates();
+}
+
+async function assertRecentFolders(expectedGuids, msg) {
+  await clickBookmarkStar();
+
+  let actualGuids = [];
+  function getGuids() {
+    const folderMenuPopup = document.getElementById("editBMPanel_folderMenuList").children[0];
+
+    let separatorFound = false;
+    // The list of folders goes from editBMPanel_foldersSeparator to the end.
+    for (let child of folderMenuPopup.children) {
+      if (separatorFound) {
+        actualGuids.push(child.folderGuid);
+      } else if (child.id == "editBMPanel_foldersSeparator") {
+        separatorFound = true;
+      }
+    }
+  }
+
+  await TestUtils.waitForCondition(() => {
+    getGuids();
+    return actualGuids.length == expectedGuids.length;
+  }, msg);
+
+  Assert.deepEqual(actualGuids, expectedGuids, msg);
+
+  await hideBookmarksPanel();
+}
+
+add_task(async function setup() {
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesUtils.metadata.delete(PlacesUIUtils.LAST_USED_FOLDERS_META_KEY);
+
+  bookmarkPanel.setAttribute("animate", false);
+
+  let oldTimeout = StarUI._autoCloseTimeout;
+  // Make the timeout something big, so it doesn't iteract badly with tests.
+  StarUI._autoCloseTimeout = 6000000;
+
+  let tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    opening: "about:robots",
+    waitForStateStop: true
+  });
+
+  folders = await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    children: [{
+      title: "Bob",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }, {
+      title: "Place",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }, {
+      title: "Delight",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }, {
+      title: "Surprise",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }, {
+      title: "Treble Bob",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }, {
+      title: "Principal",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }]
+  });
+
+  registerCleanupFunction(async () => {
+    StarUI._autoCloseTimeout = oldTimeout;
+    BrowserTestUtils.removeTab(tab);
+    bookmarkPanel.removeAttribute("animate");
+    await PlacesUtils.bookmarks.eraseEverything();
+    await PlacesUtils.metadata.delete(PlacesUIUtils.LAST_USED_FOLDERS_META_KEY);
+  });
+});
+
+add_task(async function test_remember_last_folder() {
+  await assertRecentFolders([], "Should have no recent folders to start with.");
+
+  await openPopupAndSelectFolder(folders[0].guid);
+
+  await assertRecentFolders([folders[0].guid], "Should have one folder in the list.");
+});
+
+add_task(async function test_forget_oldest_folder() {
+  // Add some more folders.
+  let expectedFolders = [folders[0].guid];
+  for (let i = 1; i < folders.length; i++) {
+    await assertRecentFolders(expectedFolders,
+      "Should have only the expected folders in the list");
+
+    await openPopupAndSelectFolder(folders[i].guid);
+
+    expectedFolders.unshift(folders[i].guid);
+    if (expectedFolders.length > 5) {
+      expectedFolders.pop();
+    }
+  }
+
+  await assertRecentFolders(expectedFolders,
+    "Should have expired the original folder");
+});
+
+add_task(async function test_reorder_folders() {
+  let expectedFolders = [
+    folders[2].guid,
+    folders[5].guid,
+    folders[4].guid,
+    folders[3].guid,
+    folders[1].guid,
+  ];
+
+  // Take an old one and put it at the front.
+  await openPopupAndSelectFolder(folders[2].guid);
+
+  await assertRecentFolders(expectedFolders,
+    "Should have correctly re-ordered the list");
+});
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -1,16 +1,17 @@
 /* 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/. */
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ScopeExit.h"
+#include "mozilla/JSONWriter.h"
 
 #include "Database.h"
 
 #include "nsIAnnotationService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIFile.h"
 #include "nsIWritablePropertyBag2.h"
 
@@ -106,16 +107,21 @@
 // Livemarks annotations.
 #define LMANNO_FEEDURI "livemark/feedURI"
 #define LMANNO_SITEURI "livemark/siteURI"
 
 // This is no longer used & obsolete except for during migration.
 // Note: it may still be found in older places databases.
 #define MOBILE_ROOT_ANNO "mobile/bookmarksRoot"
 
+// This annotation is no longer used & is obsolete, but here for migration.
+#define LAST_USED_ANNO NS_LITERAL_CSTRING("bookmarkPropertiesDialog/folderLastUsed")
+// This is key in the meta table that the LAST_USED_ANNO is migrated to.
+#define LAST_USED_FOLDERS_META_KEY NS_LITERAL_CSTRING("places/bookmarks/edit/lastusedfolder")
+
 // We use a fixed title for the mobile root to avoid marking the database as
 // corrupt if we can't look up the localized title in the string bundle. Sync
 // sets the title to the localized version when it creates the left pane query.
 #define MOBILE_ROOT_TITLE "mobile"
 
 using namespace mozilla;
 
 namespace mozilla {
@@ -1295,17 +1301,22 @@ Database::InitSchema(bool* aDatabaseMigr
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       if (currentSchemaVersion < 50) {
         rv = MigrateV50Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
-      // Firefox 62 uses schema version 50.
+      if (currentSchemaVersion < 51) {
+        rv = MigrateV51Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 62 uses schema version 51.
 
       // Schema Upgrades must add migration code here.
       // >>> IMPORTANT! <<<
       // NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
       // CONNECTION AND CAUSE FURTHER STEPS TO FAIL.
       // In case, set a bool and do the async work in the ScopeExit guard just
       // before the migration steps.
     }
@@ -2554,16 +2565,100 @@ Database::MigrateV50Up() {
     rv = syncStmt->Execute();
     if (NS_FAILED(rv)) return rv;
   }
 
   return NS_OK;
 }
 
 
+struct StringWriteFunc : public JSONWriteFunc
+{
+  nsCString& mCString;
+  explicit StringWriteFunc(nsCString& aCString) : mCString(aCString)
+  {
+  }
+  void Write(const char* aStr) override { mCString.Append(aStr); }
+};
+
+nsresult
+Database::MigrateV51Up()
+{
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT b.guid FROM moz_anno_attributes n "
+    "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
+    "JOIN moz_bookmarks b ON a.item_id = b.id "
+    "WHERE n.name = :anno_name ORDER BY a.content DESC"
+  ), getter_AddRefs(stmt));
+  if (NS_FAILED(rv)) {
+    MOZ_ASSERT(false, "Should succeed unless item annotations table has been removed");
+    return NS_OK;
+  };
+
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
+                                  LAST_USED_ANNO);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString json;
+  JSONWriter jw{ MakeUnique<StringWriteFunc>(json) };
+  jw.StartArrayProperty(nullptr, JSONWriter::SingleLineStyle);
+
+  bool hasAtLeastOne = false;
+  bool hasMore = false;
+  uint32_t length;
+  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+    hasAtLeastOne = true;
+    jw.StringElement(stmt->AsSharedUTF8String(0, &length));
+  }
+  jw.EndArray();
+
+  // If we don't have any, just abort early and save the extra work.
+  if (!hasAtLeastOne) {
+    return NS_OK;
+  }
+
+  rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "INSERT OR REPLACE INTO moz_meta "
+    "VALUES (:key, :value) "
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"),
+                                  LAST_USED_FOLDERS_META_KEY);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), json);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Clean up the now redundant annotations.
+  rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_items_annos WHERE anno_attribute_id = "
+      "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) "
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), LAST_USED_ANNO);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+    "DELETE FROM moz_anno_attributes WHERE name = :anno_name "
+  ), getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),  LAST_USED_ANNO);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+
 nsresult
 Database::ConvertOldStyleQuery(nsCString& aURL)
 {
   AutoTArray<QueryKeyValuePair, 8> tokens;
   nsresult rv = TokenizeQueryString(aURL, &tokens);
   NS_ENSURE_SUCCESS(rv, rv);
 
   AutoTArray<QueryKeyValuePair, 8> newTokens;
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -14,17 +14,17 @@
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/Attributes.h"
 #include "nsIEventTarget.h"
 #include "Shutdown.h"
 #include "nsCategoryCache.h"
 
 // This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 50
+#define DATABASE_SCHEMA_VERSION 51
 
 // Fired after Places inited.
 #define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
 // This topic is received when the profile is about to be lost.  Places does
 // initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
 // Any shutdown work that requires the Places APIs should happen here.
 #define TOPIC_PROFILE_CHANGE_TEARDOWN "profile-change-teardown"
 // Fired when Places is shutting down.  Any code should stop accessing Places
@@ -332,16 +332,17 @@ protected:
   nsresult MigrateV43Up();
   nsresult MigrateV44Up();
   nsresult MigrateV45Up();
   nsresult MigrateV46Up();
   nsresult MigrateV47Up();
   nsresult MigrateV48Up();
   nsresult MigrateV49Up();
   nsresult MigrateV50Up();
+  nsresult MigrateV51Up();
 
   void MigrateV48Frecencies();
 
   nsresult UpdateBookmarkRootTitles();
 
   friend class ConnectionShutdownBlocker;
 
   int64_t CreateMobileRoot();
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -1,16 +1,13 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const CURRENT_SCHEMA_VERSION = 50;
-const FIRST_UPGRADABLE_SCHEMA_VERSION = 30;
-
 const NS_APP_USER_PROFILE_50_DIR = "ProfD";
 
 // Shortcuts to transitions type.
 const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
 const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
 const TRANSITION_BOOKMARK = Ci.nsINavHistoryService.TRANSITION_BOOKMARK;
 const TRANSITION_EMBED = Ci.nsINavHistoryService.TRANSITION_EMBED;
 const TRANSITION_FRAMED_LINK = Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK;
--- a/toolkit/components/places/tests/migration/head_migration.js
+++ b/toolkit/components/places/tests/migration/head_migration.js
@@ -9,8 +9,40 @@ ChromeUtils.import("resource://gre/modul
 {
   /* import-globals-from ../head_common.js */
   let commonFile = do_get_file("../head_common.js", false);
   let uri = Services.io.newFileURI(commonFile);
   Services.scriptloader.loadSubScript(uri.spec, this);
 }
 
 // Put any other stuff relative to this test folder below.
+
+const CURRENT_SCHEMA_VERSION = 51;
+const FIRST_UPGRADABLE_SCHEMA_VERSION = 30;
+
+async function assertAnnotationsRemoved(db, expectedAnnos) {
+  for (let anno of expectedAnnos) {
+    let rows = await db.execute(`
+      SELECT id FROM moz_anno_attributes
+      WHERE name = :anno
+    `, {anno});
+
+    Assert.equal(rows.length, 0, `${anno} should not exist in the database`);
+  }
+}
+
+async function assertNoOrphanAnnotations(db) {
+  let rows = await db.execute(`
+    SELECT item_id FROM moz_items_annos
+    WHERE item_id NOT IN (SELECT id from moz_bookmarks)
+  `);
+
+  Assert.equal(rows.length, 0,
+    `Should have no orphan annotations.`);
+
+  rows = await db.execute(`
+    SELECT id FROM moz_anno_attributes
+    WHERE id NOT IN (SELECT id from moz_items_annos)
+  `);
+
+  Assert.equal(rows.length, 0,
+    `Should have no orphan annotation attributes.`);
+}
--- a/toolkit/components/places/tests/migration/test_current_from_v43.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v43.js
@@ -111,24 +111,17 @@ add_task(async function test_tombstones_
 
   Assert.equal(rows.length, EXPECTED_REMOVED_BOOKMARK_GUIDS.length,
     "Should have removed all the expected bookmarks.");
 });
 
 add_task(async function test_annotations_removed() {
   let db = await PlacesUtils.promiseDBConnection();
 
-  for (let anno of EXPECTED_REMOVED_ANNOTATIONS) {
-    let rows = await db.execute(`
-      SELECT id FROM moz_anno_attributes
-      WHERE name = :anno
-    `, {anno});
-
-    Assert.equal(rows.length, 0, `${anno} should not exist in the database`);
-  }
+  await assertAnnotationsRemoved(db, EXPECTED_REMOVED_ANNOTATIONS);
 });
 
 add_task(async function test_check_history_entries() {
   let db = await PlacesUtils.promiseDBConnection();
 
   for (let entry of EXPECTED_REMOVED_PLACES_ENTRIES) {
     let rows = await db.execute(`
       SELECT id FROM moz_places
@@ -163,31 +156,17 @@ add_task(async function test_check_keywo
     Assert.equal(rows.length, 0,
       `Should have removed the expected keyword: ${keyword}.`);
   }
 });
 
 add_task(async function test_no_orphan_annotations() {
   let db = await PlacesUtils.promiseDBConnection();
 
-  let rows = await db.execute(`
-    SELECT item_id FROM moz_items_annos
-    WHERE item_id NOT IN (SELECT id from moz_bookmarks)
-  `);
-
-  Assert.equal(rows.length, 0,
-    `Should have no orphan annotations.`);
-
-  rows = await db.execute(`
-    SELECT id FROM moz_anno_attributes
-    WHERE id NOT IN (SELECT id from moz_items_annos)
-  `);
-
-  Assert.equal(rows.length, 0,
-    `Should have no orphan annotation attributes.`);
+  await assertNoOrphanAnnotations(db);
 });
 
 add_task(async function test_no_orphan_keywords() {
   let db = await PlacesUtils.promiseDBConnection();
 
   let rows = await db.execute(`
     SELECT place_id FROM moz_keywords
     WHERE place_id NOT IN (SELECT id from moz_places)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/migration/test_current_from_v50.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BASE_GUID = "null".padEnd(11, "_");
+const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
+const LAST_USED_META_DATA = "places/bookmarks/edit/lastusedfolder";
+
+let expectedGuids = [];
+
+add_task(async function setup() {
+  await setupPlacesDatabase("places_v43.sqlite");
+
+  // Setup database contents to be migrated.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
+  let db = await Sqlite.openConnection({ path });
+  // We can reuse the same guid, it doesn't matter for this test.
+  await db.execute(`INSERT INTO moz_anno_attributes (name)
+                    VALUES (:last_used_anno)`, { last_used_anno: LAST_USED_ANNO });
+
+  for (let i = 0; i < 3; i++) {
+    let guid = `${BASE_GUID}${i}`;
+    await db.execute(`INSERT INTO moz_bookmarks (guid, type)
+                      VALUES (:guid, :type)
+                      `, { guid, type: PlacesUtils.bookmarks.TYPE_FOLDER });
+    await db.execute(`INSERT INTO moz_items_annos (item_id, anno_attribute_id, content)
+                      VALUES ((SELECT id FROM moz_bookmarks WHERE guid = :guid),
+                              (SELECT id FROM moz_anno_attributes WHERE name = :last_used_anno),
+                              :content)`, {
+      guid,
+      content: new Date(1517318477569) - (3 - i) * 60 * 60 * 1000,
+      last_used_anno: LAST_USED_ANNO,
+    });
+    expectedGuids.unshift(guid);
+  }
+  await db.close();
+});
+
+add_task(async function database_is_valid() {
+  // Accessing the database for the first time triggers migration.
+  Assert.equal(PlacesUtils.history.databaseStatus,
+               PlacesUtils.history.DATABASE_STATUS_UPGRADED);
+
+  let db = await PlacesUtils.promiseDBConnection();
+  Assert.equal((await db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
+});
+
+add_task(async function test_folders_migrated() {
+  let metaData = await PlacesUtils.metadata.get(LAST_USED_META_DATA);
+
+  Assert.deepEqual(JSON.parse(metaData), expectedGuids);
+});
+
+add_task(async function test_annotations_removed() {
+  let db = await PlacesUtils.promiseDBConnection();
+
+  await assertAnnotationsRemoved(db, [LAST_USED_ANNO]);
+});
+
+add_task(async function test_no_orphan_annotations() {
+  let db = await PlacesUtils.promiseDBConnection();
+
+  await assertNoOrphanAnnotations(db);
+});
--- a/toolkit/components/places/tests/migration/xpcshell.ini
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -22,8 +22,9 @@ support-files =
 [test_current_from_v38.js]
 [test_current_from_v41.js]
 [test_current_from_v42.js]
 [test_current_from_v43.js]
 [test_current_from_v45.js]
 [test_current_from_v46.js]
 [test_current_from_v47.js]
 [test_current_from_v48.js]
+[test_current_from_v50.js]