Bug 1379611 - Avoid GetItemTitle() synchronous API in editBookmarkOverlay.js. r=standard8 draft
authorMarco Bonardo <mbonardo@mozilla.com>
Mon, 10 Jul 2017 16:13:55 +0200
changeset 608649 d83dbda79ddaf143a35442bc79babd30f40ed628
parent 608648 002a8dfc9cd7304078bf293b751ffe865f0e6fe0
child 637380 a8bc22744c3b1738573d6e3d3d0bf7897c6379b4
push id68361
push usermak77@bonardo.net
push dateThu, 13 Jul 2017 23:35:33 +0000
reviewersstandard8
bugs1379611
milestone56.0a1
Bug 1379611 - Avoid GetItemTitle() synchronous API in editBookmarkOverlay.js. r=standard8 MozReview-Commit-ID: 9Ig63XHcEIE
browser/base/content/browser-places.js
browser/base/content/nsContextMenu.js
browser/base/content/test/general/browser_star_hsts.js
browser/components/places/content/editBookmarkOverlay.js
browser/components/places/content/editBookmarkOverlay.xul
browser/components/places/tests/browser/browser_addBookmarkForFrame.js
browser/components/places/tests/browser/browser_library_infoBox.js
browser/components/places/tests/chrome/head.js
browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
npm-shrinkwrap.json
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -211,17 +211,17 @@ var StarUI = {
 
     this._isNewBookmark = aIsNewBookmark;
     this._uriForRemoval = "";
     // TODO (bug 1131491): Deprecate this once async transactions are enabled
     // and the legacy transactions code is gone.
     if (typeof(aNode) == "number") {
       let itemId = aNode;
       let guid = await PlacesUtils.promiseItemGuid(itemId);
-      aNode = await PlacesUIUtils.promiseNodeLike(guid);
+      aNode = await PlacesUIUtils.fetchNodeLike(guid);
     }
 
     // Performance: load the overlay the first time the panel is opened
     // (see bug 392443).
     if (this._overlayLoading)
       return;
 
     if (this._overlayLoaded) {
@@ -413,17 +413,17 @@ var PlacesCommandHook = {
       try {
         title = docInfo.isErrorPage ? PlacesUtils.history.getPageTitle(uri)
                                     : aBrowser.contentTitle;
         title = title || uri.spec;
         description = docInfo.description;
         charset = aBrowser.characterSet;
       } catch (e) { }
 
-      if (aShowEditUI && isNewBookmark) {
+      if (aShowEditUI) {
         // If we bookmark the page here but open right into a cancelable
         // state (i.e. new bookmark in Library), start batching here so
         // all of the actions can be undone in a single undo step.
         StarUI.beginBatch();
       }
 
       var parent = aParent !== undefined ?
                    aParent : PlacesUtils.unfiledBookmarksFolderId;
@@ -445,27 +445,27 @@ var PlacesCommandHook = {
     if (!aShowEditUI)
       return;
 
     // Try to dock the panel to:
     // 1. the bookmarks menu button
     // 2. the identity icon
     // 3. the content area
     if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
-      StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
-                                   "bottomcenter topright", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
+                                        "bottomcenter topright", isNewBookmark);
       return;
     }
 
     let identityIcon = document.getElementById("identity-icon");
     if (isVisible(identityIcon)) {
-      StarUI.showEditBookmarkPopup(itemId, identityIcon,
-                                   "bottomcenter topright", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(itemId, identityIcon,
+                                        "bottomcenter topright", isNewBookmark);
     } else {
-      StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
     }
   },
 
   // TODO: Replace bookmarkPage code with this function once legacy
   // transactions are removed.
   async _bookmarkPagePT(aBrowser, aParentId, aShowEditUI) {
     let url = new URL(aBrowser.currentURI.spec);
     let info = await PlacesUtils.bookmarks.fetch({ url });
@@ -520,27 +520,27 @@ var PlacesCommandHook = {
 
     let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(info);
 
     // Try to dock the panel to:
     // 1. the bookmarks menu button
     // 2. the identity icon
     // 3. the content area
     if (BookmarkingUI.anchor && isVisible(BookmarkingUI.anchor)) {
-      StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
+      await StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
                                    "bottomcenter topright", isNewBookmark);
       return;
     }
 
     let identityIcon = document.getElementById("identity-icon");
     if (isVisible(identityIcon)) {
-      StarUI.showEditBookmarkPopup(node, identityIcon,
+      await StarUI.showEditBookmarkPopup(node, identityIcon,
                                    "bottomcenter topright", isNewBookmark);
     } else {
-      StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
+      await StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
     }
   },
 
   _getPageDetails(browser) {
     return new Promise(resolve => {
       let mm = browser.messageManager;
       mm.addMessageListener("Bookmarks:GetPageDetails:Result", function listener(msg) {
         mm.removeMessageListener("Bookmarks:GetPageDetails:Result", listener);
@@ -550,17 +550,18 @@ var PlacesCommandHook = {
       mm.sendAsyncMessage("Bookmarks:GetPageDetails", { })
     });
   },
 
   /**
    * Adds a bookmark to the page loaded in the current tab.
    */
   bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
-    this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
+    this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI)
+        .catch(Components.utils.reportError);
   },
 
   /**
    * Adds a bookmark to the page targeted by a link.
    * @param aParent
    *        The folder in which to create a new bookmark if aURL isn't
    *        bookmarked.
    * @param aURL (string)
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1803,17 +1803,19 @@ nsContextMenu.prototype = {
 
     var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
     var where = newWindowPref == 3 ? "tab" : "window";
 
     openUILinkIn(uri, where);
   },
 
   bookmarkThisPage: function CM_bookmarkThisPage() {
-    window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
+    window.top.PlacesCommandHook
+              .bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true)
+              .catch(Components.utils.reportError);
   },
 
   bookmarkLink: function CM_bookmarkLink() {
     window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId,
                                               this.linkURL, this.linkTextStr);
   },
 
   addBookmarkForFrame: function CM_addBookmarkForFrame() {
--- a/browser/base/content/test/general/browser_star_hsts.js
+++ b/browser/base/content/test/general/browser_star_hsts.js
@@ -19,21 +19,20 @@ add_task(async function test_star_redire
   let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   // This will add the page to the HSTS cache.
   await promiseTabLoadEvent(tab, secureURL, secureURL);
   // This should transparently be redirected to the secure page.
   await promiseTabLoadEvent(tab, unsecureURL, secureURL);
 
   await promiseStarState(BookmarkingUI.STATUS_UNSTARRED);
 
-  let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI);
+  let bookmarkPanel = document.getElementById("editBookmarkPanel");
+  let shownPromise = promisePopupShown(bookmarkPanel);
   BookmarkingUI.star.click();
-  // This resolves on the next tick, so the star should have already been
-  // updated at that point.
-  await promiseBookmark;
+  await shownPromise;
 
   is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred");
 });
 
 /**
  * Waits for the star to reflect the expected state.
  */
 function promiseStarState(aValue) {
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -16,27 +16,28 @@ var gEditItemOverlay = {
     if (!aInitInfo)
       return this._paneInfo = null;
 
     if ("uris" in aInitInfo && "node" in aInitInfo)
       throw new Error("ambiguous pane info");
     if (!("uris" in aInitInfo) && !("node" in aInitInfo))
       throw new Error("Neither node nor uris set for pane info");
 
+    // Once we stop supporting legacy add-ons the code should throw if a node is
+    // not passed.
     let node = "node" in aInitInfo ? aInitInfo.node : null;
 
     // Since there's no true UI for folder shortcuts (they show up just as their target
     // folders), when the pane shows for them it's opened in read-only mode, showing the
     // properties of the target folder.
     let itemId = node ? node.itemId : -1;
-    let itemGuid = PlacesUIUtils.useAsyncTransactions && node ?
-                     PlacesUtils.getConcreteItemGuid(node) : null;
+    let itemGuid = node ? PlacesUtils.getConcreteItemGuid(node) : null;
     let isItem = itemId != -1;
     let isFolderShortcut = isItem &&
-      node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+                           node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
     let isTag = node && PlacesUtils.nodeIsTagQuery(node);
     if (isTag) {
       itemId = PlacesUtils.getConcreteItemId(node);
       // For now we don't have access to the item guid synchronously for tags,
       // so we'll need to fetch it later.
     }
     let isURI = node && PlacesUtils.nodeIsURI(node);
     let uri = isURI ? NetUtil.newURI(node.uri) : null;
@@ -45,20 +46,18 @@ var gEditItemOverlay = {
     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;
 
-    // This should go away once we stop supporting legacy add-ons, and the code
-    // should throw if a node is not passed.
-    if (node) {
-      if (!node.parent || !node.parent.bookmarkGuid) {
+    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) ||
                           PlacesUIUtils.isContentsReadOnly(parent);
       parentId = parent.itemId;
       parentGuid = parent.bookmarkGuid;
     }
@@ -272,17 +271,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);
+      this._initFolderMenuList(parentId).catch(Components.utils.reportError);
     }
 
     // Selection count.
     if (showOrCollapse("selectionCount", bulkTagging)) {
       this._element("itemsCountText").value =
         PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
                                       uris.length,
                                       [uris.length]);
@@ -377,86 +376,92 @@ 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
    *        The identifier of the bookmarks folder.
+   * @param aTitle
+   *        The title to use as a label.
    * @return the new menu item.
    */
-  _appendFolderItemToMenupopup(aMenupopup, aFolderId) {
+  _appendFolderItemToMenupopup(aMenupopup, aFolderId, aTitle) {
     // First make sure the folders-separator is visible
     this._element("foldersSeparator").hidden = false;
 
     var folderMenuItem = document.createElement("menuitem");
-    var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
+    var folderTitle = aTitle;
     folderMenuItem.folderId = aFolderId;
     folderMenuItem.setAttribute("label", folderTitle);
     folderMenuItem.className = "menuitem-iconic folder-icon";
     aMenupopup.appendChild(folderMenuItem);
     return folderMenuItem;
   },
 
-  _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
+  async _initFolderMenuList(aSelectedFolder) {
     // clean up first
     var menupopup = this._folderMenuList.menupopup;
     while (menupopup.childNodes.length > 6)
       menupopup.removeChild(menupopup.lastChild);
 
-    const bms = PlacesUtils.bookmarks;
-    const annos = PlacesUtils.annotations;
-
     // Build the static list
-    var unfiledItem = this._element("unfiledRootItem");
     if (!this._staticFoldersListBuilt) {
-      unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
+      let unfiledItem = this._element("unfiledRootItem");
+      unfiledItem.label = PlacesUtils.getString("OtherBookmarksFolderTitle");
       unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
-      var bmMenuItem = this._element("bmRootItem");
-      bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
+      let bmMenuItem = this._element("bmRootItem");
+      bmMenuItem.label = PlacesUtils.getString("BookmarksMenuFolderTitle");
       bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
-      var toolbarItem = this._element("toolbarFolderItem");
-      toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
+      let toolbarItem = this._element("toolbarFolderItem");
+      toolbarItem.label = PlacesUtils.getString("BookmarksToolbarFolderTitle");
       toolbarItem.folderId = PlacesUtils.toolbarFolderId;
       this._staticFoldersListBuilt = true;
     }
 
     // List of recently used folders:
-    var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO);
+    var folderIds =
+      PlacesUtils.annotations.getItemsWithAnnotation(LAST_USED_ANNO);
 
     /**
      * 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.
      *
      * 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 i = 0; i < folderIds.length; i++) {
-      var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
-      this._recentFolders.push({ folderId: folderIds[i], lastUsed });
+    for (let folderId of folderIds) {
+      var lastUsed =
+        PlacesUtils.annotations.getItemAnnotation(folderId, LAST_USED_ANNO);
+      let guid = await PlacesUtils.promiseItemGuid(folderId);
+      let title = (await PlacesUtils.bookmarks.fetch(guid)).title;
+      this._recentFolders.push({ folderId, guid, title, lastUsed });
     }
     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++) {
-      this._appendFolderItemToMenupopup(menupopup,
-                                        this._recentFolders[i].folderId);
+      await this._appendFolderItemToMenupopup(menupopup,
+                                              this._recentFolders[i].folderId,
+                                              this._recentFolders[i].title);
     }
 
-    var defaultItem = this._getFolderMenuItem(aSelectedFolder);
+    let selectedFolderGuid = await PlacesUtils.promiseItemGuid(aSelectedFolder);
+    let title = (await PlacesUtils.bookmarks.fetch(selectedFolderGuid)).title;
+    var defaultItem = this._getFolderMenuItem(aSelectedFolder, 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);
@@ -610,40 +615,38 @@ var gEditItemOverlay = {
     this._firstEditedField = aNewField;
 
     // set the pref
     var prefs = Cc["@mozilla.org/preferences-service;1"].
                 getService(Ci.nsIPrefBranch);
     prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
   },
 
-  onNamePickerChange() {
+  async onNamePickerChange() {
     if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag))
       return;
 
     // Here we update either the item title or its cached static title
     let newTitle = this._namePicker.value;
     if (!newTitle && this._paneInfo.parentGuid == PlacesUtils.bookmarks.tagsGuid) {
       // We don't allow setting an empty title for a tag, restore the old one.
       this._initNamePicker();
     } else {
       this._mayUpdateFirstEditField("namePicker");
       if (!PlacesUIUtils.useAsyncTransactions) {
         let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId,
                                                      newTitle);
         PlacesUtils.transactionManager.doTransaction(txn);
         return;
       }
-      (async () => {
-        let guid = this._paneInfo.isTag
-                    ? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
-                    : this._paneInfo.itemGuid;
-        PlacesTransactions.EditTitle({ guid, title: newTitle })
-                          .transact().catch(Components.utils.reportError);
-      })().catch(Components.utils.reportError);
+
+      let guid = this._paneInfo.isTag
+                  ? (await PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
+                  : this._paneInfo.itemGuid;
+      await PlacesTransactions.EditTitle({ guid, title: newTitle }).transact();
     }
   },
 
   onDescriptionFieldChange() {
     if (this.readOnly || !this._paneInfo.isItem)
       return;
 
     let itemId = this._paneInfo.itemId;
@@ -750,82 +753,76 @@ var gEditItemOverlay = {
       // breaks the view.
       const FOLDER_TREE_PLACE_URI =
         "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
         PlacesUIUtils.allBookmarksFolderId;
       this._folderTree.place = FOLDER_TREE_PLACE_URI;
 
       this._element("chooseFolderSeparator").hidden =
         this._element("chooseFolderMenuItem").hidden = true;
-      var currentFolder = this._getFolderIdFromMenuList();
-      this._folderTree.selectItems([currentFolder]);
+      this._folderTree.selectItems([this._paneInfo.parentId]);
       this._folderTree.focus();
     }
   },
 
-  _getFolderIdFromMenuList() {
-    var selectedItem = this._folderMenuList.selectedItem;
-    NS_ASSERT("folderId" in selectedItem,
-              "Invalid menuitem in the folders-menulist");
-    return selectedItem.folderId;
-  },
-
   /**
    * 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
    *        The identifier of the bookmarks folder.
+   * @param aTitle
+   *        The title to use in case of menuitem creation.
+   * @return handle to the menuitem.
    */
-  _getFolderMenuItem(aFolderId) {
+  _getFolderMenuItem(aFolderId, aTitle) {
     let menupopup = this._folderMenuList.menupopup;
     let menuItem = Array.prototype.find.call(
       menupopup.childNodes, item => item.folderId === aFolderId);
     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);
+    return this._appendFolderItemToMenupopup(menupopup, aFolderId, aTitle);
   },
 
-  onFolderMenuListCommand(aEvent) {
+  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.parentId,
+                                         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._getFolderIdFromMenuList();
+    let containerId = this._folderMenuList.selectedItem.folderId;
     if (this._paneInfo.parentId != containerId &&
         this._paneInfo.itemId != containerId) {
       if (PlacesUIUtils.useAsyncTransactions) {
-        (async () => {
-          let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
-          let guid = this._paneInfo.itemGuid;
-          await PlacesTransactions.Move({ guid, newParentGuid }).transact();
-        })();
+        let newParentGuid = await PlacesUtils.promiseItemGuid(containerId);
+        let guid = this._paneInfo.itemGuid;
+        await PlacesTransactions.Move({ guid, newParentGuid }).transact();
       } else {
         let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId,
                                                 containerId,
                                                 PlacesUtils.bookmarks.DEFAULT_INDEX);
         PlacesUtils.transactionManager.doTransaction(txn);
       }
 
       // Mark the containing folder as recently-used if it isn't in the
@@ -859,20 +856,20 @@ var gEditItemOverlay = {
     // 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._getFolderIdFromMenuList() == folderId)
+    if (this._folderMenuList.selectedItem.folderId == folderId)
       return;
 
-    var folderItem = this._getFolderMenuItem(folderId);
+    var folderItem = this._getFolderMenuItem(folderId, selectedNode.title);
     this._folderMenuList.selectedItem = folderItem;
     folderItem.doCommand();
   },
 
   async _markFolderAsRecentlyUsed(aFolderId) {
     if (!PlacesUIUtils.useAsyncTransactions) {
       let txns = [];
 
@@ -1070,40 +1067,49 @@ var gEditItemOverlay = {
     else if (this._paneInfo.bulkTagging)
       tags = this._getCommonTags();
     else
       throw new Error("_promiseTagsStr called unexpectedly");
 
     this._initTextField(this._tagsField, tags.join(", "));
   },
 
-  _onTagsChange(aItemId, aChangedURI = null) {
+  async _onTagsChange(guid, changedURI = null) {
     let paneInfo = this._paneInfo;
     let updateTagsField = false;
     if (paneInfo.isURI) {
-      if (paneInfo.isBookmark && aItemId == paneInfo.itemId) {
+      if (paneInfo.isBookmark && guid == paneInfo.itemGuid) {
         updateTagsField = true;
       } else if (!paneInfo.isBookmark) {
-        updateTagsField = aChangedURI && aChangedURI.equals(paneInfo.uri);
+        if (!changedURI) {
+          let href = (await PlacesUtils.bookmarks.fetch(guid)).url.href;
+          changedURI = Services.io.newURI(href);
+        }
+        updateTagsField = changedURI.equals(paneInfo.uri);
       }
     } else if (paneInfo.bulkTagging) {
-      if (aChangedURI && paneInfo.uris.some(uri => uri.equals(aChangedURI))) {
+      if (!changedURI) {
+        let href = (await PlacesUtils.bookmarks.fetch(guid)).url.href;
+        changedURI = Services.io.newURI(href);
+      }
+      if (paneInfo.uris.some(uri => uri.equals(changedURI))) {
         updateTagsField = true;
         delete this._paneInfo._cachedCommonTags;
       }
     } else {
       throw new Error("_onTagsChange called unexpectedly");
     }
 
-    if (updateTagsField)
-      this._initTagsField().catch(Components.utils.reportError);
-
-    // Any tags change should be reflected in the tags selector.
-    if (this._element("tagsSelector"))
-      this._rebuildTagsSelectorList().catch(Components.utils.reportError);
+    if (updateTagsField) {
+      await this._initTagsField();
+      // Any tags change should be reflected in the tags selector.
+      if (this._element("tagsSelector")) {
+        await this._rebuildTagsSelectorList();
+      }
+    }
   },
 
   _onItemTitleChange(aItemId, aNewTitle) {
     if (!this._paneInfo.isBookmark)
       return;
     if (aItemId == this._paneInfo.itemId) {
       this._paneInfo.title = aNewTitle;
       this._initTextField(this._namePicker, aNewTitle);
@@ -1114,41 +1120,53 @@ var gEditItemOverlay = {
       let menupopup = this._folderMenuList.menupopup;
       for (let menuitem of menupopup.childNodes) {
         if ("folderId" in menuitem && menuitem.folderId == aItemId) {
           menuitem.label = aNewTitle;
           break;
         }
       }
     }
+    // We need to also update title of recent folders.
+    for (let folder of this._recentFolders) {
+      if (folder.folderId == aItemId) {
+        folder.title = aNewTitle;
+        break;
+      }
+    }
   },
 
   // nsINavBookmarkObserver
   onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
-                aLastModified, aItemType) {
+                aLastModified, aItemType, aParentId, aGuid) {
     if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) {
-      this._onTagsChange(aItemId);
-    } else if (aProperty == "title" && this._paneInfo.isItem) {
+      this._onTagsChange(aGuid).catch(Components.utils.reportError);
+      return;
+    }
+    if (aProperty == "title" && this._paneInfo.isItem) {
       // This also updates titles of folders in the folder menu list.
       this._onItemTitleChange(aItemId, aValue);
-    } else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
+      return;
+    }
+
+    if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
       return;
     }
 
     switch (aProperty) {
     case "uri":
       let newURI = NetUtil.newURI(aValue);
       if (!newURI.equals(this._paneInfo.uri)) {
         this._paneInfo.uri = newURI;
         if (this._paneInfo.visibleRows.has("locationRow"))
           this._initLocationField();
 
         if (this._paneInfo.visibleRows.has("tagsRow")) {
           delete this._paneInfo._cachedCommonTags;
-          this._onTagsChange(aItemId, newURI);
+          this._onTagsChange(aGuid, newURI).catch(Components.utils.reportError);
         }
       }
       break;
     case "keyword":
       if (this._paneInfo.visibleRows.has("keywordRow"))
         this._initKeywordField(aValue).catch(Components.utils.reportError);
       break;
     case PlacesUIUtils.DESCRIPTION_ANNO:
@@ -1167,23 +1185,26 @@ var gEditItemOverlay = {
     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._getFolderIdFromMenuList()) {
+        newParentId == this._folderMenuList.selectedItem.folderId) {
       return;
     }
 
     // Just setting selectItem _does not_ trigger oncommand, so we don't
     // recurse.
-    this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentId);
+    PlacesUtils.bookmarks.fetch(newParentGuid).then(bm => {
+      this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentId,
+                                                                  bm.title);
+    });
   },
 
   onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
     this._lastNewItem = aItemId;
   },
 
   onItemRemoved() { },
   onBeginUpdateBatch() { },
--- a/browser/components/places/content/editBookmarkOverlay.xul
+++ b/browser/components/places/content/editBookmarkOverlay.xul
@@ -27,17 +27,17 @@
         <row id="editBMPanel_nameRow"
              align="center"
              collapsed="true">
           <label value="&editBookmarkOverlay.name.label;"
                  class="editBMPanel_rowLabel"
                  accesskey="&editBookmarkOverlay.name.accesskey;"
                  control="editBMPanel_namePicker"/>
           <textbox id="editBMPanel_namePicker"
-                   onchange="gEditItemOverlay.onNamePickerChange();"/>
+                   onchange="gEditItemOverlay.onNamePickerChange().catch(Components.utils.reportError);"/>
         </row>
 
         <row id="editBMPanel_locationRow"
              align="center"
              collapsed="true">
           <label value="&editBookmarkOverlay.location.label;"
                  class="editBMPanel_rowLabel"
                  accesskey="&editBookmarkOverlay.location.accesskey;"
@@ -52,17 +52,17 @@
              collapsed="true">
           <label value="&editBookmarkOverlay.folder.label;"
                  class="editBMPanel_rowLabel"
                  control="editBMPanel_folderMenuList"/>
           <hbox flex="1" align="center">
             <menulist id="editBMPanel_folderMenuList"
                       class="folder-icon"
                       flex="1"
-                      oncommand="gEditItemOverlay.onFolderMenuListCommand(event);">
+                      oncommand="gEditItemOverlay.onFolderMenuListCommand(event).catch(Components.utils.reportError);">
               <menupopup>
                 <!-- Static item for special folders -->
                 <menuitem id="editBMPanel_toolbarFolderItem"
                           class="menuitem-iconic folder-icon"/>
                 <menuitem id="editBMPanel_bmRootItem"
                           class="menuitem-iconic folder-icon"/>
                 <menuitem id="editBMPanel_unfiledRootItem"
                           class="menuitem-iconic folder-icon"/>
--- a/browser/components/places/tests/browser/browser_addBookmarkForFrame.js
+++ b/browser/components/places/tests/browser/browser_addBookmarkForFrame.js
@@ -26,56 +26,58 @@ async function withAddBookmarkForFrame(t
     bookmarkFrame.click();
   }, taskFn);
 
   await BrowserTestUtils.removeTab(tab);
 }
 
 add_task(async function test_open_add_bookmark_for_frame() {
   info("Test basic opening of the add bookmark for frame dialog.");
-  await withAddBookmarkForFrame(function test(dialogWin) {
+  await withAddBookmarkForFrame(async dialogWin => {
     let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
     Assert.ok(!namepicker.readOnly, "Name field is writable");
     Assert.equal(namepicker.value, "Left frame", "Name field is correct.");
 
     let expectedFolderName =
       PlacesUtils.getString("BookmarksMenuFolderTitle");
 
     let folderPicker = dialogWin.document.getElementById("editBMPanel_folderMenuList");
-
-    Assert.equal(folderPicker.selectedItem.label,
-                 expectedFolderName, "The folder is the expected one.");
+    await BrowserTestUtils.waitForCondition(
+      () => folderPicker.selectedItem.label == expectedFolderName,
+      "The folder is the expected one.");
 
     let tagsField = dialogWin.document.getElementById("editBMPanel_tagsField");
     Assert.equal(tagsField.value, "", "The tags field should be empty");
   });
 });
 
 add_task(async function test_move_bookmark_whilst_add_bookmark_open() {
   info("Test moving a bookmark whilst the add bookmark for frame dialog is open.");
-  await withAddBookmarkForFrame(async function test(dialogWin) {
+  await withAddBookmarkForFrame(async dialogWin => {
     let bookmarksMenuFolderName = PlacesUtils.getString("BookmarksMenuFolderTitle");
     let toolbarFolderName = PlacesUtils.getString("BookmarksToolbarFolderTitle");
 
     let url = makeURI(LEFT_URL);
     let folderPicker = dialogWin.document.getElementById("editBMPanel_folderMenuList");
 
     // Check the initial state of the folder picker.
-    Assert.equal(folderPicker.selectedItem.label,
-                 bookmarksMenuFolderName, "The folder is the expected one.");
+    await BrowserTestUtils.waitForCondition(
+      () => folderPicker.selectedItem.label == bookmarksMenuFolderName,
+      "The folder is the expected one.");
 
     // Check the bookmark has been created as expected.
     let bookmark = await PlacesUtils.bookmarks.fetch({url});
 
     Assert.equal(bookmark.parentGuid,
                  PlacesUtils.bookmarks.menuGuid,
                  "The bookmark should be in the menuGuid folder.");
 
     // Now move the bookmark and check the folder picker is updated correctly.
     bookmark.parentGuid = PlacesUtils.bookmarks.toolbarGuid;
     bookmark.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
 
     await PlacesUtils.bookmarks.update(bookmark);
 
-    Assert.equal(folderPicker.selectedItem.label,
-                 toolbarFolderName, "The folder picker has changed to the new folder");
+    await BrowserTestUtils.waitForCondition(
+      () => folderPicker.selectedItem.label == toolbarFolderName,
+      "The folder picker has changed to the new folder");
   });
 });
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -2,147 +2,121 @@
  * 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 appropriate visibility of infoBoxExpanderWrapper and
  *  additionalInfoFields in infoBox section of library
  */
 
-const TEST_URI = "http://www.mozilla.org/";
-
-var gTests = [];
-var gLibrary;
-
-// ------------------------------------------------------------------------------
+let gLibrary;
+add_task(async function() {
+  // Open Library.
+  gLibrary = await promiseLibrary();
+  registerCleanupFunction(async () => {
+    gLibrary.close();
+    await PlacesTestUtils.clearHistory();
+  });
+  gLibrary.PlacesOrganizer._places.focus();
 
-gTests.push({
-  desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
-  run() {
-    var PO = gLibrary.PlacesOrganizer;
-    let ContentTree = gLibrary.ContentTree;
-    var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
+  info("Bug 430148 - Remove or hide the more/less button in details pane...");
+  let PO = gLibrary.PlacesOrganizer;
+  let ContentTree = gLibrary.ContentTree;
+  let infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
 
-    function addVisitsCallback() {
-      // open all bookmarks node
-      PO.selectLeftPaneQuery("AllBookmarks");
-      isnot(PO._places.selectedNode, null,
-            "Correctly selected all bookmarks node.");
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for all bookmarks node.");
-      checkAddInfoFieldsCollapsed(PO);
+  await PlacesTestUtils.addVisits("http://www.mozilla.org/");
 
-      // open history node
-      PO.selectLeftPaneQuery("History");
-      isnot(PO._places.selectedNode, null, "Correctly selected history node.");
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for history node.");
-      checkAddInfoFieldsCollapsed(PO);
+  // open all bookmarks node
+  PO.selectLeftPaneQuery("AllBookmarks");
+  isnot(PO._places.selectedNode, null,
+        "Correctly selected all bookmarks node.");
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for all bookmarks node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open history child node
-      var historyNode = PO._places.selectedNode.
-                        QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      historyNode.containerOpen = true;
-      var childNode = historyNode.getChild(0);
-      isnot(childNode, null, "History node first child is not null.");
-      PO._places.selectNode(childNode);
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for history child node.");
-      checkAddInfoFieldsCollapsed(PO);
+  // open history node
+  PO.selectLeftPaneQuery("History");
+  isnot(PO._places.selectedNode, null, "Correctly selected history node.");
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for history node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open history item
-      var view = ContentTree.view.view;
-      ok(view.rowCount > 0, "History item exists.");
-      view.selection.select(0);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for history item.");
-      checkAddInfoFieldsCollapsed(PO);
-
-      historyNode.containerOpen = false;
-
-      // open bookmarks menu node
-      PO.selectLeftPaneQuery("BookmarksMenu");
-      isnot(PO._places.selectedNode, null,
-            "Correctly selected bookmarks menu node.");
-      checkInfoBoxSelected(PO);
-      ok(infoBoxExpanderWrapper.hidden,
-         "Expander button is hidden for bookmarks menu node.");
-      checkAddInfoFieldsCollapsed(PO);
+  // open history child node
+  var historyNode = PO._places.selectedNode.
+                    QueryInterface(Ci.nsINavHistoryContainerResultNode);
+  historyNode.containerOpen = true;
+  var childNode = historyNode.getChild(0);
+  isnot(childNode, null, "History node first child is not null.");
+  PO._places.selectNode(childNode);
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for history child node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open recently bookmarked node
-      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
-                                           NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
-                                                          "&folder=UNFILED_BOOKMARKS" +
-                                                          "&folder=TOOLBAR" +
-                                                          "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
-                                                          "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
-                                                          "&maxResults=10" +
-                                                          "&excludeQueries=1"),
-                                           0, "Recent Bookmarks");
-      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
-                                           NetUtil.newURI("http://mozilla.org/"),
-                                           1, "Mozilla");
-      var menuNode = PO._places.selectedNode.
-                     QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      menuNode.containerOpen = true;
-      childNode = menuNode.getChild(0);
-      isnot(childNode, null, "Bookmarks menu child node exists.");
-      is(childNode.title, "Recent Bookmarks",
-         "Correctly selected recently bookmarked node.");
-      PO._places.selectNode(childNode);
-      checkInfoBoxSelected(PO);
-      ok(!infoBoxExpanderWrapper.hidden,
-         "Expander button is not hidden for recently bookmarked node.");
-      checkAddInfoFieldsNotCollapsed(PO);
+  // open history item
+  var view = ContentTree.view.view;
+  ok(view.rowCount > 0, "History item exists.");
+  view.selection.select(0);
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for history item.");
+  checkAddInfoFieldsCollapsed(PO);
+
+  historyNode.containerOpen = false;
+
+  // open bookmarks menu node
+  PO.selectLeftPaneQuery("BookmarksMenu");
+  isnot(PO._places.selectedNode, null,
+        "Correctly selected bookmarks menu node.");
+  checkInfoBoxSelected();
+  ok(infoBoxExpanderWrapper.hidden,
+      "Expander button is hidden for bookmarks menu node.");
+  checkAddInfoFieldsCollapsed(PO);
 
-      // open first bookmark
-      view = ContentTree.view.view;
-      ok(view.rowCount > 0, "Bookmark item exists.");
-      view.selection.select(0);
-      checkInfoBoxSelected(PO);
-      ok(!infoBoxExpanderWrapper.hidden,
-         "Expander button is not hidden for bookmark item.");
-      checkAddInfoFieldsNotCollapsed(PO);
-      checkAddInfoFields(PO, "bookmark item");
-
-      menuNode.containerOpen = false;
+  // open recently bookmarked node
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    url: "place:folder=BOOKMARKS_MENU&folder=UNFILED_BOOKMARKS&folder=TOOLBAR" +
+         "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+         "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+         "&maxResults=10" +
+         "&excludeQueries=1",
+    title: "Recent Bookmarks",
+    index: 0
+  });
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    url: "http://mozilla.org/",
+    title: "Mozilla",
+    index: 1
+  });
+  var menuNode = PO._places.selectedNode.
+                  QueryInterface(Ci.nsINavHistoryContainerResultNode);
+  menuNode.containerOpen = true;
+  childNode = menuNode.getChild(0);
+  isnot(childNode, null, "Bookmarks menu child node exists.");
+  is(childNode.title, "Recent Bookmarks",
+      "Correctly selected recently bookmarked node.");
+  PO._places.selectNode(childNode);
+  checkInfoBoxSelected();
+  ok(!infoBoxExpanderWrapper.hidden,
+      "Expander button is not hidden for recently bookmarked node.");
+  checkAddInfoFieldsNotCollapsed(PO);
 
-      PlacesTestUtils.clearHistory().then(nextTest);
-    }
-    // add a visit to browser history
-    PlacesTestUtils.addVisits(
-      { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
-        transition: PlacesUtils.history.TRANSITION_TYPED }
-      ).then(addVisitsCallback);
-  }
-});
-
-function checkInfoBoxSelected(PO) {
-  is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
-     "Selected element in detailsDeck is infoBox.");
-}
+  // open first bookmark
+  view = ContentTree.view.view;
+  ok(view.rowCount > 0, "Bookmark item exists.");
+  view.selection.select(0);
+  checkInfoBoxSelected();
+  ok(!infoBoxExpanderWrapper.hidden,
+      "Expander button is not hidden for bookmark item.");
+  checkAddInfoFieldsNotCollapsed(PO);
 
-function checkAddInfoFieldsCollapsed(PO) {
-  PO._additionalInfoFields.forEach(function(id) {
-    ok(getAndCheckElmtById(id).collapsed,
-       "Additional info field correctly collapsed: #" + id);
-  });
-}
-
-function checkAddInfoFieldsNotCollapsed(PO) {
-  ok(PO._additionalInfoFields.some(function(id) {
-      return !getAndCheckElmtById(id).collapsed;
-     }), "Some additional info field correctly not collapsed");
-}
-
-function checkAddInfoFields(PO, nodeName) {
-  ok(true, "Checking additional info fields visibiity for node: " + nodeName);
+  ok(true, "Checking additional info fields visibiity for bookmark item");
   var expanderButton = getAndCheckElmtById("infoBoxExpander");
 
   // make sure additional fields are hidden by default
   PO._additionalInfoFields.forEach(function(id) {
     ok(getAndCheckElmtById(id).hidden,
        "Additional info field correctly hidden by default: #" + id);
   });
 
@@ -152,45 +126,35 @@ function checkAddInfoFields(PO, nodeName
     ok(!getAndCheckElmtById(id).hidden,
        "Additional info field correctly unhidden after toggle: #" + id);
   });
   expanderButton.click();
   PO._additionalInfoFields.forEach(function(id) {
     ok(getAndCheckElmtById(id).hidden,
        "Additional info field correctly hidden after toggle: #" + id);
   });
+
+  menuNode.containerOpen = false;
+});
+
+function checkInfoBoxSelected() {
+  is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
+     "Selected element in detailsDeck is infoBox.");
+}
+
+function checkAddInfoFieldsCollapsed(PO) {
+  PO._additionalInfoFields.forEach(id => {
+    ok(getAndCheckElmtById(id).collapsed,
+       `Additional info field should be collapsed: #${id}`);
+  });
+}
+
+function checkAddInfoFieldsNotCollapsed(PO) {
+  ok(PO._additionalInfoFields.some(id => !getAndCheckElmtById(id).collapsed),
+     `Some additional info field should not be collapsed.`);
 }
 
 function getAndCheckElmtById(id) {
   var elmt = gLibrary.document.getElementById(id);
   isnot(elmt, null, "Correctly got element: #" + id);
   return elmt;
 }
 
-// ------------------------------------------------------------------------------
-
-function nextTest() {
-  if (gTests.length) {
-    var testCase = gTests.shift();
-    ok(true, "TEST: " + testCase.desc);
-    dump("TEST: " + testCase.desc + "\n");
-    testCase.run();
-  } else {
-    // Close Library window.
-    gLibrary.close();
-    // No need to cleanup anything, we have a correct left pane now.
-    finish();
-  }
-}
-
-function test() {
-  waitForExplicitFinish();
-  // Sanity checks.
-  ok(PlacesUtils, "PlacesUtils is running in chrome context");
-  ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
-
-  // Open Library.
-  openLibrary(function(library) {
-    gLibrary = library;
-    gLibrary.PlacesOrganizer._places.focus();
-    nextTest(gLibrary);
-  });
-}
--- a/browser/components/places/tests/chrome/head.js
+++ b/browser/components/places/tests/chrome/head.js
@@ -1,7 +1,9 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+  "resource://testing-common/BrowserTestUtils.jsm");
--- a/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
+++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
@@ -22,16 +22,17 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <vbox id="editBookmarkPanelContent"/>
 
   <script type="application/javascript">
   <![CDATA[
     function checkTagsSelector(aAvailableTags, aCheckedTags) {
@@ -78,26 +79,28 @@
         let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
         gEditItemOverlay.initPanel({ node });
 
         // Add a tag.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
 
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Correctly added tag to a single bookmark");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing a single bookmark shows the added tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing a single bookmark shows the added tag.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "The tag has been removed");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing a single bookmark should not show any tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing a single bookmark should not show any tag");
         checkTagsSelector([], []);
 
         // Add a second bookmark.
         let bm2 = await PlacesUtils.bookmarks.insert({
           parentGuid: PlacesUtils.bookmarks.unfiledGuid,
           index: PlacesUtils.bookmarks.DEFAULT_INDEX,
           type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
           title: "test.again.me",
@@ -106,96 +109,106 @@
 
         // Init panel with multiple uris.
         gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
 
         // Add a tag to the first uri.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Correctly added a tag to the first bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple bookmarks without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple bookmarks without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Add a tag to the second uri.
         PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
            "Correctly added a tag to the second bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing multiple bookmarks should show matching tags.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing multiple bookmarks should show matching tags.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag from the first bookmark.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "Correctly removed tag from the first bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple bookmarks without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple bookmarks without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Remove tag from the second bookmark.
         PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
            "Correctly removed tag from the second bookmark.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple bookmarks without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple bookmarks without matching tags should not show any tag.");
         checkTagsSelector([], []);
 
         // Init panel with a nsIURI entry.
         gEditItemOverlay.initPanel({ uris: [TEST_URI] });
 
         // Add a tag.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Correctly added tag to the first entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing a single nsIURI entry shows the added tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing a single nsIURI entry shows the added tag.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "Correctly removed tag from the nsIURI entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing a single nsIURI entry should not show any tag");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing a single nsIURI entry should not show any tag.");
         checkTagsSelector([], []);
 
         // Init panel with multiple nsIURI entries.
         gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
 
         // Add a tag to the first entry.
         PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
            "Tag correctly added.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple nsIURIs without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple nsIURIs without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Add a tag to the second entry.
         PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
            "Tag correctly added.");
-        is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
-           "Editing multiple nsIURIs should show matching tags");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+          "Editing multiple nsIURIs should show matching tags.");
         checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
         // Remove tag from the first entry.
         PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
            "Correctly removed tag from the first entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple nsIURIs without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple nsIURIs without matching tags should not show any tag.");
         checkTagsSelector([TEST_TAG], []);
 
         // Remove tag from the second entry.
         PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
         is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
            "Correctly removed tag from the second entry.");
-        is(document.getElementById("editBMPanel_tagsField").value, "",
-           "Editing multiple nsIURIs without matching tags should not show any tag.");
+        await BrowserTestUtils.waitForCondition(
+          () => document.getElementById("editBMPanel_tagsField").value == "",
+          "Editing multiple nsIURIs without matching tags should not show any tag.");
         checkTagsSelector([], []);
 
         // Cleanup.
         await PlacesUtils.bookmarks.remove(bm.guid);
         await PlacesUtils.bookmarks.remove(bm2.guid);
       })().then(SimpleTest.finish);
     }
   ]]>
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -762,17 +762,17 @@
     "lodash": {
       "version": "4.17.4",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
       "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
     },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
       "requires": {
         "brace-expansion": "1.1.8"
       }
     },
     "minimist": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
       "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="