Bug 730837 Use asynchronous livemarks r=IanN a=Ratty
authorNeil Rashbrook <neil@parkwaycc.co.uk>
Mon, 04 Jun 2012 16:29:52 +0100
changeset 30798 0eeebd728b9f981f122df77daa68bbd7e617f3f6
parent 30795 b43356d5b1619848626699f636400d6ecb5e0d9b
child 30802 75d3140a0856e63ceec9e00cce3824e1196669fe
push id1
push userclokep@gmail.com
push dateMon, 07 May 2018 22:45:56 +0000
treeherdercomm-esr60@57eacde5ef40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersIanN, Ratty
bugs730837
Bug 730837 Use asynchronous livemarks r=IanN a=Ratty
suite/browser/navigator.js
suite/common/bookmarks/bm-props.js
suite/common/bookmarks/bookmarksManager.js
suite/common/bookmarks/editBookmarkOverlay.js
suite/common/places/browserPlacesViews.js
suite/common/places/controller.js
suite/common/places/treeView.js
suite/smile/src/smileApplication.js
suite/themes/classic/communicator/bookmarks/bookmark-item-updated.png
suite/themes/classic/communicator/bookmarks/bookmarks.css
suite/themes/classic/communicator/bookmarks/livemark-item.png
suite/themes/classic/jar.mn
suite/themes/classic/mac/communicator/bookmarks/bookmarks.css
suite/themes/classic/mac/communicator/bookmarks/livemark-item.png
suite/themes/modern/communicator/bookmarks/bookmark-item-updated.gif
suite/themes/modern/communicator/bookmarks/bookmarks.css
suite/themes/modern/communicator/bookmarks/livemark-item.png
suite/themes/modern/jar.mn
--- a/suite/browser/navigator.js
+++ b/suite/browser/navigator.js
@@ -692,19 +692,16 @@ function Startup()
 
   DownloadTaskbarIntegration.onBrowserWindowLoad(window);
 
   // initialize the sync UI
   gSyncUI.init();
 
   // initialize the session-restore service
   setTimeout(InitSessionStoreCallback, 0);
-
-  // initialize the livemark service
-  setTimeout(function() { PlacesUtils.livemarks.start(); }, 5000);
 }
 
 function UpdateNavBar()
 {
   var elements = getNavToolbox().getElementsByClassName("nav-bar-class");
   for (var i = 0; i < elements.length; i++) {
     var element = elements[i];
     element.classList.remove("nav-bar-last");
--- a/suite/common/bookmarks/bm-props.js
+++ b/suite/common/bookmarks/bm-props.js
@@ -278,24 +278,18 @@ var BookmarkPropertiesPanel = {
                                      .getKeywordForBookmark(this._itemId);
           // Load In Sidebar
           this._loadInSidebar = PlacesUtils.annotations
                                            .itemHasAnnotation(this._itemId,
                                                               PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
           break;
 
         case "folder":
-          if (PlacesUtils.itemIsLivemark(this._itemId)) {
-            this._itemType = LIVEMARK_CONTAINER;
-            this._feedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
-            this._siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-          }
-          else
-            this._itemType = BOOKMARK_FOLDER;
-          break;
+          this._itemType = BOOKMARK_FOLDER;
+          PlacesUtils.livemarks.getLivemark({ id: this._itemId }, this);
       }
 
       // Description
       if (PlacesUtils.annotations
                      .itemHasAnnotation(this._itemId, PlacesUIUtils.DESCRIPTION_ANNO)) {
         this._description = PlacesUtils.annotations
                                        .getItemAnnotation(this._itemId,
                                                           PlacesUIUtils.DESCRIPTION_ANNO);
@@ -338,18 +332,17 @@ var BookmarkPropertiesPanel = {
       case ACTION_EDIT:
         this._fillEditProperties();
         acceptButton.disabled = this._readOnly;
         break;
       case ACTION_ADD:
         this._fillAddProperties();
         // if this is an uri related dialog disable accept button until
         // the user fills an uri value.
-        if (this._itemType == BOOKMARK_ITEM ||
-            this._itemType == LIVEMARK_CONTAINER)
+        if (this._itemType == BOOKMARK_ITEM)
           acceptButton.disabled = !this._inputIsValid();
         break;
     }
 
     // When collapsible elements change their collapsed attribute we must
     // resize the dialog.
     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
@@ -423,16 +416,30 @@ var BookmarkPropertiesPanel = {
           var width = el.boxObject.width;
           window.sizeToContent();
           window.outerWidth -= el.boxObject.width - width;
         }
         break;
     }
   },
 
+  // mozILivemarkCallback
+  onCompletion: function BPP_onCompletion(aStatus, aLivemark) {
+    if (Components.isSuccessCode(aStatus)) {
+      this._itemType = LIVEMARK_CONTAINER;
+      this._feedURI = aLivemark.feedURI;
+      this._siteURI = aLivemark.siteURI;
+      this._fillEditProperties();
+
+      document.documentElement
+              .getButton("accept").disabled = !this._inputIsValid();
+      window.outerHeight += this._element("nameRow").boxObject.height * 2;
+    }
+  },
+
   _beginBatch: function BPP__beginBatch() {
     if (this._batching)
       return;
 
     PlacesUtils.transactionManager.beginBatch();
     this._batching = true;
   },
 
@@ -461,16 +468,17 @@ var BookmarkPropertiesPanel = {
     var locationField = this._element("locationField");
     if (locationField.value == "about:blank")
       locationField.value = "";
   },
 
   // nsISupports
   QueryInterface: function BPP_QueryInterface(aIID) {
     if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
+        aIID.equals(Components.interfaces.mozILivemarkCallback) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
 
     throw Components.results.NS_NOINTERFACE;
   },
 
   _element: function BPP__element(aID) {
     return document.getElementById("editBMPanel_" + aID);
@@ -531,26 +539,16 @@ var BookmarkPropertiesPanel = {
    */
   _inputIsValid: function BPP__inputIsValid() {
     if (this._itemType == BOOKMARK_ITEM &&
         !this._containsValidURI("locationField"))
       return false;
     if (this._isAddKeywordDialog && !this._element("keywordField").value.length)
       return false;
 
-    // Feed Location has to be a valid URI;
-    // Site Location has to be a valid URI or empty
-    if (this._itemType == LIVEMARK_CONTAINER) {
-      if (!this._containsValidURI("feedLocationField"))
-        return false;
-      if (!this._containsValidURI("siteLocationField") &&
-          (this._element("siteLocationField").value.length > 0))
-        return false;
-    }
-
     return true;
   },
 
   /**
    * Determines whether the XUL textbox with the given ID contains a
    * string that can be converted into an nsIURI.
    *
    * @param aTextboxID
--- a/suite/common/bookmarks/bookmarksManager.js
+++ b/suite/common/bookmarks/bookmarksManager.js
@@ -536,19 +536,17 @@ var PlacesOrganizer = {
     var infoBoxExpanderWrapper = document.getElementById("infoBoxExpanderWrapper");
     var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster");
 
     if (!aNode) {
       infoBoxExpanderWrapper.hidden = true;
       return;
     }
     if (aNode.itemId != -1 &&
-        ((PlacesUtils.nodeIsFolder(aNode) &&
-          !PlacesUtils.nodeIsLivemarkContainer(aNode)) ||
-         PlacesUtils.nodeIsLivemarkItem(aNode) ||
+        ((PlacesUtils.nodeIsFolder(aNode) && !aNode._feedURI) ||
          PlacesUtils.nodeIsQuery(aNode))) {
       if (infoBox.getAttribute("minimal") == "true")
         infoBox.setAttribute("wasminimal", "true");
       infoBox.removeAttribute("minimal");
       infoBoxExpanderWrapper.hidden = true;
     }
     else {
       if (infoBox.getAttribute("wasminimal") == "true")
@@ -626,16 +624,17 @@ var PlacesOrganizer = {
       if (concreteId != -1 && useConcreteId)
         itemId = concreteId;
       else if (aSelectedNode.itemId != -1)
         itemId = aSelectedNode.itemId;
       else
         itemId = PlacesUtils._uri(aSelectedNode.uri);
 
       gEditItemOverlay.initPanel(itemId, { hiddenRows: ["folderPicker"],
+                                           titleOverride: aSelectedNode.title,
                                            forceReadOnly: readOnly });
 
       // Dynamically generated queries, like history date containers, have
       // itemId !=0 and do not exist in history.  For them the panel is
       // read-only, but empty, since it can't get a valid title for the object.
       // In such a case we force the title using the selectedNode one, for UI
       // polishness.
       if (aSelectedNode.itemId == -1 &&
--- a/suite/common/bookmarks/editBookmarkOverlay.js
+++ b/suite/common/bookmarks/editBookmarkOverlay.js
@@ -47,16 +47,17 @@ var gEditItemOverlay = {
   _allTags: [],
   _multiEdit: false,
   _itemType: -1,
   _readOnly: false,
   _hiddenRows: [],
   _observersAdded: false,
   _staticFoldersListBuilt: false,
   _initialized: false,
+  _titleOverride: null,
 
   // the first field which was edited after this panel was initialized for
   // a certain item
   _firstEditedField: "",
 
   get itemId() {
     return this._itemId;
   },
@@ -75,16 +76,18 @@ var gEditItemOverlay = {
   _determineInfo: function EIO__determineInfo(aInfo) {
     // hidden rows
     if (aInfo && aInfo.hiddenRows)
       this._hiddenRows = aInfo.hiddenRows;
     else
       this._hiddenRows.splice(0, this._hiddenRows.length);
     // force-read-only
     this._readOnly = aInfo && aInfo.forceReadOnly;
+    // override title
+    this._titleOverride = aInfo && aInfo.titleOverride;
   },
 
   _showHideRows: function EIO__showHideRows() {
     var isBookmark = this._itemId != -1 &&
                      this._itemType == Components.interfaces.nsINavBookmarksService.TYPE_BOOKMARK;
     var isQuery = false;
     if (this._uri)
       isQuery = this._uri.schemeIs("place");
@@ -155,42 +158,35 @@ var gEditItemOverlay = {
     this._determineInfo(aInfo);
     if (aFor instanceof Components.interfaces.nsIURI) {
       this._itemId = -1;
       this._uri = aFor;
       this._readOnly = true;
     }
     else {
       this._itemId = aFor;
+      // We can't store information on livemarks.
+      if (aFor == -1)
+        this._readOnly = true;
       var containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
       this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId);
       if (this._itemType == Components.interfaces.nsINavBookmarksService.TYPE_BOOKMARK) {
         this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
-        if (!this._readOnly) // If readOnly wasn't forced through aInfo
-          this._readOnly = PlacesUtils.itemIsLivemark(containerId);
         this._initTextField("keywordField",
                             PlacesUtils.bookmarks
                                        .getKeywordForBookmark(this._itemId));
         // Load In Sidebar checkbox
         this._element("loadInSidebarCheckbox").checked =
           PlacesUtils.annotations.itemHasAnnotation(this._itemId,
                                                     PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
       }
       else {
-        if (!this._readOnly) // If readOnly wasn't forced through aInfo
-          this._readOnly = false;
-
         this._uri = null;
-        this._isLivemark = PlacesUtils.itemIsLivemark(this._itemId);
-        if (this._isLivemark) {
-          var feedURI = PlacesUtils.livemarks.getFeedURI(this._itemId);
-          var siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-          this._initTextField("feedLocationField", feedURI.spec);
-          this._initTextField("siteLocationField", siteURI ? siteURI.spec : "");
-        }
+        this._isLivemark = false;
+        PlacesUtils.livemarks.getLivemark({ id: this._itemId }, this);
       }
 
       // folder picker
       this._initFolderMenuList(containerId);
 
       // description field
       this._initTextField("descriptionField", 
                           PlacesUIUtils.getItemDescription(this._itemId));
@@ -361,28 +357,32 @@ var gEditItemOverlay = {
 
     // Hide the folders-separator if no folder is annotated as recently-used
     this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
     this._folderMenuList.disabled = this._readOnly;
   },
 
   QueryInterface: function EIO_QueryInterface(aIID) {
     if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
+        aIID.equals(Components.interfaces.mozILivemarkCallback) ||
         aIID.equals(Components.interfaces.nsINavBookmarkObserver) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   },
 
   _element: function EIO__element(aID) {
     return document.getElementById("editBMPanel_" + aID);
   },
 
   _getItemStaticTitle: function EIO__getItemStaticTitle() {
+    if (this._titleOverride)
+      return this._titleOverride;
+
     if (this._itemId == -1)
       return PlacesUtils.history.getPageTitle(this._uri);
 
     return PlacesUtils.bookmarks.getItemTitle(this._itemId);
   },
 
   _initNamePicker: function EIO_initNamePicker() {
     var namePicker = this._element("namePicker");
@@ -420,16 +420,18 @@ var gEditItemOverlay = {
     this._uri = null;
     this._uris = [];
     this._tags = [];
     this._allTags = [];
     this._itemIds = [];
     this._multiEdit = false;
     this._firstEditedField = "";
     this._initialized = false;
+    this._readOnly = false;
+    this._titleOverride = null;
   },
 
   onTagsFieldBlur: function EIO_onTagsFieldBlur() {
     if (this._updateTags()) // if anything has changed
       this._mayUpdateFirstEditField("tagsField");
   },
 
   _updateTags: function EIO__updateTags() {
@@ -885,16 +887,27 @@ var gEditItemOverlay = {
       this._updateTags();
       break;
     case "unload":
       this.uninitPanel(false);
       break;
     }
   },
 
+  // mozILivemarkCallback
+  onCompletion: function EIO_onCompletion(aStatus, aLivemark) {
+    if (Components.isSuccessCode(aStatus)) {
+      this._isLivemark = true;
+      this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
+      this._initTextField("siteLocationField",
+                          aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
+      this._showHideRows();
+    }
+  },
+
   // nsINavBookmarkObserver
   onItemChanged: function EIO_onItemChanged(aItemId, aProperty,
                                             aIsAnnotationProperty, aValue,
                                             aLastModified, aItemType) {
     if (aProperty == "tags") {
       // Tags case is special, since they should be updated if either:
       // - the notification is for the edited bookmark
       // - the notification is for the edited history entry
@@ -982,25 +995,27 @@ var gEditItemOverlay = {
                           PlacesUIUtils.getItemDescription(this._itemId));
       break;
     case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
       this._element("loadInSidebarCheckbox").checked =
         PlacesUtils.annotations.itemHasAnnotation(this._itemId,
                                                   PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
       break;
     case PlacesUtils.LMANNO_FEEDURI:
-      var feedURISpec = PlacesUtils.livemarks.getFeedURI(this._itemId).spec;
-      this._initTextField("feedLocationField", feedURISpec);
+      var feedURISpec = PlacesUtils.annotations.getItemAnnotation(this._itemId,
+                        PlacesUtils.LMANNO_FEEDURI);
+      this._initTextField("feedLocationField", feedURISpec, true);
       break;
     case PlacesUtils.LMANNO_SITEURI:
       var siteURISpec = "";
-      var siteURI = PlacesUtils.livemarks.getSiteURI(this._itemId);
-      if (siteURI)
-        siteURISpec = siteURI.spec;
-      this._initTextField("siteLocationField", siteURISpec);
+      try {
+        siteURISpec = PlacesUtils.annotations.getItemAnnotation(this._itemId,
+                      PlacesUtils.LMANNO_SITEURI);
+      } catch (e) {}
+      this._initTextField("siteLocationField", siteURISpec, true);
       break;
     }
   },
 
   onItemMoved: function EIO_onItemMoved(aItemId, aOldParent, aOldIndex,
                                         aNewParent, aNewIndex, aItemType) {
     if (aItemId != this._itemId ||
         aNewParent == this._getFolderIdFromMenuList())
--- a/suite/common/places/browserPlacesViews.js
+++ b/suite/common/places/browserPlacesViews.js
@@ -164,17 +164,19 @@ PlacesViewBase.prototype = {
     let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
     let container = this._resultNode;
     let orientation = Components.interfaces.nsITreeView.DROP_BEFORE;
     let isTag = false;
 
     let selectedNode = this.selectedNode;
     if (selectedNode) {
       let popup = document.popupNode;
-      if (!popup._placesNode || popup._placesNode == this._resultNode) {
+      if (!popup._placesNode ||
+          popup._placesNode == this._resultNode ||
+          popup._placesNode.itemId == -1) {
         // If a static menuitem is selected, or if the root node is selected,
         // the insertion point is inside the folder, at the end.
         container = selectedNode;
         orientation = Components.interfaces.nsITreeView.DROP_ON;
       }
       else {
         // In all other cases the insertion point is before that node.
         container = selectedNode.parent;
@@ -242,25 +244,29 @@ PlacesViewBase.prototype = {
       if (aPopup._endMarker != -1)
         aPopup._endMarker--;
     }
   },
 
   _rebuildPopup: function PVB__rebuildPopup(aPopup) {
     this._cleanPopup(aPopup);
 
-    // If this is a livemark container check if the status menuitem has
-    // to be added or removed.
-    if (PlacesUtils.nodeIsLivemarkContainer(aPopup._placesNode))
-      this._ensureLivemarkStatusMenuItem(aPopup);
-
     let resultNode = aPopup._placesNode;
     if (!resultNode.containerOpen)
       return;
 
+    if (resultNode._feedURI) {
+      aPopup.removeAttribute("emptyplacesresult");
+      if (aPopup._emptyMenuItem)
+        aPopup._emptyMenuItem.hidden = true;
+      aPopup._built = true;
+      this._populateLivemarkPopup(aPopup);
+      return;
+    }
+
     let cc = resultNode.childCount;
     if (cc > 0) {
       aPopup.removeAttribute("emptyplacesresult");
       if (aPopup._emptyMenuItem)
         aPopup._emptyMenuItem.hidden = true;
 
       for (let i = 0; i < cc; ++i) {
         let child = resultNode.getChild(i);
@@ -297,16 +303,17 @@ PlacesViewBase.prototype = {
     aPopup._emptyMenuItem = document.createElement("menuitem");
     aPopup._emptyMenuItem.setAttribute("label", label);
     aPopup._emptyMenuItem.setAttribute("disabled", true);
     aPopup.appendChild(aPopup._emptyMenuItem);
   },
 
   _createMenuItemForPlacesNode:
   function PVB__createMenuItemForPlacesNode(aPlacesNode) {
+    delete aPlacesNode._DOMElement;
     let element;
     let type = aPlacesNode.type;
     if (type == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
       element = document.createElement("menuseparator");
     else {
       if (PlacesUtils.uriTypes.indexOf(type) != -1) {
         element = document.createElement("menuitem");
         element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
@@ -322,18 +329,27 @@ PlacesViewBase.prototype = {
           if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
             element.setAttribute("tagContainer", "true");
           else if (PlacesUtils.nodeIsDay(aPlacesNode))
             element.setAttribute("dayContainer", "true");
           else if (PlacesUtils.nodeIsHost(aPlacesNode))
             element.setAttribute("hostContainer", "true");
         }
         else if (aPlacesNode.itemId != -1) {
-          if (PlacesUtils.nodeIsLivemarkContainer(aPlacesNode))
-            element.setAttribute("livemark", "true");
+          PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId },
+            function onCompletion(aStatus, aLivemark) {
+              if (Components.isSuccessCode(aStatus)) {
+                element.setAttribute("livemark", "true");
+                // Set an expando on the node, controller
+                // will use it to build its metadata.
+                aPlacesNode._feedURI = aLivemark.feedURI;
+                aPlacesNode._siteURI = aLivemark.siteURI;
+              }
+            }
+          );
         }
 
         let popup = document.createElement("menupopup");
         popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
         if (this._nativeView) {
           popup._startMarker = -1;
           popup._endMarker = -1;
         }
@@ -386,50 +402,91 @@ PlacesViewBase.prototype = {
     }
 
     if (aPopup._endMarker != -1)
       aPopup._endMarker++;
 
     return element;
   },
 
+  _setLivemarkSiteURIMenuItem:
+  function PVB__setLivemarkSiteURIMenuItem(aPopup) {
+    let siteUrl = aPopup._placesNode._siteURI ? aPopup._placesNode._siteURI.spec
+                                              : null;
+    if (!siteUrl && aPopup._siteURIMenuitem) {
+      aPopup.removeChild(aPopup._siteURIMenuitem);
+      aPopup._siteURIMenuitem = null;
+      aPopup._startMarker--;
+      aPopup.removeChild(aPopup._siteURIMenuseparator);
+      aPopup._siteURIMenuseparator = null;
+      aPopup._startMarker--;
+    }
+    else if (siteUrl && !aPopup._siteURIMenuitem) {
+      // Add "Open (Feed Name)" menuitem.
+      aPopup._siteURIMenuitem = document.createElement("menuitem");
+      aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
+      aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
+      aPopup._siteURIMenuitem.setAttribute("oncommand",
+        "openUILink(this.getAttribute('targetURI'), event);");
+
+      // If a user middle-clicks this item we serve the oncommand event.
+      // We are using checkForMiddleClick because of Bug 246720.
+      // Note: stopPropagation is needed to avoid serving middle-click
+      // with BT_onClick that would open all items in tabs.
+      aPopup._siteURIMenuitem.setAttribute("onclick",
+        "checkForMiddleClick(this, event); event.stopPropagation();");
+      let label =
+        PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
+                                         [aPopup.parentNode.getAttribute("label")])
+      aPopup._siteURIMenuitem.setAttribute("label", label);
+      aPopup.insertBefore(aPopup._siteURIMenuitem,
+                          aPopup.childNodes.item(aPopup._startMarker + 1));
+      aPopup._startMarker++;
+
+      aPopup._siteURIMenuseparator = document.createElement("menuseparator");
+      aPopup.insertBefore(aPopup._siteURIMenuseparator,
+                         aPopup.childNodes.item(aPopup._startMarker + 1));
+      aPopup._startMarker++;
+    }
+  },
+
   /**
    * Add, update or remove the livemark status menuitem.
    * @param aPopup
    *        The livemark container popup
+   * @param aStatus
+   *        The livemark status
    */
-  _ensureLivemarkStatusMenuItem:
-  function PVB_ensureLivemarkStatusMenuItem(aPopup) {
+  _setLivemarkStatusMenuItem:
+  function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
     let itemId = aPopup._placesNode.itemId;
-    let as = PlacesUtils.annotations;
-
     let lmStatus = null;
-    if (as.itemHasAnnotation(itemId, "livemark/loadfailed"))
+    if (aStatus == Components.interfaces.mozILivemark.STATUS_LOADING)
+      lmStatus = "bookmarksLivemarkLoading";
+    else if (aStatus == Components.interfaces.mozILivemark.STATUS_FAILED)
       lmStatus = "bookmarksLivemarkFailed";
-    else if (as.itemHasAnnotation(itemId, "livemark/loading"))
-      lmStatus = "bookmarksLivemarkLoading";
 
     let lmStatusElt = aPopup._lmStatusMenuItem;
     if (lmStatus && !lmStatusElt) {
       // Create the status menuitem and cache it in the popup object.
       lmStatusElt = document.createElement("menuitem");
       lmStatusElt.setAttribute("lmStatus", lmStatus);
       lmStatusElt.setAttribute("label", PlacesUIUtils.getString(lmStatus));
       lmStatusElt.setAttribute("disabled", true);
       aPopup.insertBefore(lmStatusElt,
                           aPopup.childNodes.item(aPopup._startMarker + 1));
       aPopup._lmStatusMenuItem = lmStatusElt;
       aPopup._startMarker++;
     }
     else if (lmStatus && lmStatusElt.getAttribute("lmStatus") != lmStatus) {
       // Status has changed, update the cached status menuitem.
-      lmStatusElt.setAttribute("label", this.getString(lmStatus));
+      lmStatusElt.setAttribute("label", PlacesUIUtils.getString(lmStatus));
     }
     else if (!lmStatus && lmStatusElt) {
-      // No status, remove the cached menuitem.
+      // The livemark has finished loading.
       aPopup.removeChild(aPopup._lmStatusMenuItem);
       aPopup._lmStatusMenuItem = null;
       aPopup._startMarker--;
     }
   },
 
   nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) {
     let elt = aPlacesNode._DOMElement;
@@ -471,18 +528,27 @@ PlacesViewBase.prototype = {
       let elt = aPlacesNode._DOMElement;
       if (!elt)
         throw "aPlacesNode must have _DOMElement set";
 
       let menu = elt.parentNode;
       if (!menu.hasAttribute("livemark"))
         menu.setAttribute("livemark", "true");
 
-      // Add or remove the livemark status menuitem.
-      this._ensureLivemarkStatusMenuItem(elt);
+      PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId },
+        function onCompletion(aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            // Set an expando on the node, controller
+            // will use it to build its metadata.
+            aPlacesNode._feedURI = aLivemark.feedURI;
+            aPlacesNode._siteURI = aLivemark.siteURI;
+            this.invalidateContainer(aPlacesNode);
+          }
+        }.bind(this)
+      );
     }
   },
 
   nodeTitleChanged:
   function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
     let elt = aPlacesNode._DOMElement;
     if (!elt)
       throw "aPlacesNode must have _DOMElement set";
@@ -557,17 +623,34 @@ PlacesViewBase.prototype = {
       // No worries: If elt is the last item (i.e. no nextSibling),
       // _insertNewItem/_insertNewItemToPopup will insert the new element as
       // the last item.
       let nextElt = elt.nextSibling;
       this._insertNewItemToPopup(aNewPlacesNode, parentElt, nextElt);
     }
   },
 
-  nodeHistoryDetailsChanged: function() { },
+  nodeHistoryDetailsChanged:
+  function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
+    if (aPlacesNode.parent && aPlacesNode.parent._feedURI) {
+      // Find the node in the parent.
+      let popup = aPlacesNode.parent._DOMElement;
+      for (let i = popup._startMarker; i < popup.childNodes.length; i++) {
+        let child = popup.childNodes[i];
+        if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
+          if (aCount)
+            child.setAttribute("visited", "true");
+          else
+            child.removeAttribute("visited");
+          break;
+        }
+      }
+    }
+  },
+
   nodeTagsChanged: function() { },
   nodeDateAddedChanged: function() { },
   nodeLastModifiedChanged: function() { },
   nodeKeywordChanged: function() { },
   sortingChanged: function() { },
   batching: function() { },
 
   nodeInserted:
@@ -619,20 +702,66 @@ PlacesViewBase.prototype = {
     }
   },
 
   containerStateChanged:
   function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
     if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED ||
         aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_CLOSED) {
       this.invalidateContainer(aPlacesNode);
+
+      if (PlacesUtils.nodeIsFolder(aPlacesNode) &&
+          !PlacesUtils.asQuery(this._result.root).queryOptions.excludeItems) {
+        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId },
+          function onCompletion(aStatus, aLivemark) {
+            if (Components.isSuccessCode(aStatus)) {
+              let shouldInvalidate = !aPlacesNode._feedURI;
+              aPlacesNode._feedURI = aLivemark.feedURI;
+              aPlacesNode._siteURI = aLivemark.siteURI;
+              if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
+                aLivemark.registerForUpdates(aPlacesNode, this);
+                aLivemark.reload();
+                if (shouldInvalidate)
+                  this.invalidateContainer(aPlacesNode);
+              }
+              else {
+                aLivemark.unregisterForUpdates(aPlacesNode);
+              }
+            }
+          }.bind(this)
+        );
+      }
     }
-    else {
-      throw "Unexpected state passed to containerStateChanged";
-    }
+  },
+
+  _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup)
+  {
+    this._setLivemarkSiteURIMenuItem(aPopup);
+    this._setLivemarkStatusMenuItem(aPopup, Components.interfaces.mozILivemark.STATUS_LOADING);
+
+    PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId },
+      function onCompletion(aStatus, aLivemark) {
+        let placesNode = aPopup._placesNode;
+        if (!Components.isSuccessCode(aStatus) || !placesNode.containerOpen)
+          return;
+
+        this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
+        this._cleanPopup(aPopup);
+
+        let children = aLivemark.getNodesForContainer(placesNode);
+        for (let i = 0; i < children.length; i++) {
+          let child = children[i];
+          this.nodeInserted(placesNode, child, i);
+          if (child.accessCount)
+            child._DOMElement.setAttribute("visited", true);
+          else
+            child._DOMElement.removeAttribute("visited");
+        }
+      }.bind(this)
+    );
   },
 
   invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
     let elt = aPlacesNode._DOMElement;
     if (!elt)
       throw "aPlacesNode must have _DOMElement set";
 
     elt._built = false;
@@ -666,96 +795,64 @@ PlacesViewBase.prototype = {
    * @param aPopup
    *        a Places popup.
    */
   _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) {
     // The command items are never added to the root popup.
     if (aPopup == this._rootElt)
       return;
 
-    // Check if the popup contains at least 2 menuitems with places nodes
-    let numURINodes = 0;
-    let currentChild = aPopup.firstChild;
-    while (currentChild) {
-      if (currentChild.localName == "menuitem" && currentChild._placesNode) {
-        if (++numURINodes == 2)
-          break;
+    let hasMultipleURIs = false;
+
+    // Check if the popup contains at least 2 menuitems with places nodes.
+    // We don't currently support opening multiple uri nodes when
+    // they are not populated by the result.
+    if (aPopup._placesNode.childCount > 0) {
+      let currentChild = aPopup.firstChild;
+      let numURINodes = 0;
+      while (currentChild) {
+        if (currentChild.localName == "menuitem" && currentChild._placesNode) {
+          if (++numURINodes == 2)
+            break;
+        }
+        currentChild = currentChild.nextSibling;
       }
-      currentChild = currentChild.nextSibling;
-    }
-
-    let hasMultipleURIs = numURINodes > 1;
-    let itemId = aPopup._placesNode.itemId;
-    let siteURIString = "";
-    if (itemId != -1 && PlacesUtils.itemIsLivemark(itemId)) {
-      let siteURI = PlacesUtils.livemarks.getSiteURI(itemId);
-      if (siteURI)
-        siteURIString = siteURI.spec;
+      hasMultipleURIs = numURINodes > 1;
     }
 
-    if (!siteURIString && aPopup._endOptOpenSiteURI) {
-      aPopup.removeChild(aPopup._endOptOpenSiteURI);
-      aPopup._endOptOpenSiteURI = null;
-    }
+    if (!hasMultipleURIs) {
+      // We don't have to show any option.
+      if (aPopup._endOptOpenAllInTabs) {
+        aPopup.removeChild(aPopup._endOptOpenAllInTabs);
+        aPopup._endOptOpenAllInTabs = null;
+        aPopup._endMarker--;
 
-    if (!hasMultipleURIs && aPopup._endOptOpenAllInTabs) {
-      aPopup.removeChild(aPopup._endOptOpenAllInTabs);
-      aPopup._endOptOpenAllInTabs = null;
-    }
-
-    if (!(hasMultipleURIs || siteURIString)) {
-      // We don't have to show any option.
-      if (aPopup._endOptSeparator) {
         aPopup.removeChild(aPopup._endOptSeparator);
         aPopup._endOptSeparator = null;
-        aPopup._endMarker = -1;
+        aPopup._endMarker--;
       }
-      return;
     }
-
-    if (!aPopup._endOptSeparator) {
+    else if (!aPopup._endOptOpenAllInTabs) {
       // Create a separator before options.
       aPopup._endOptSeparator = document.createElement("menuseparator");
       aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
-      aPopup._endMarker = aPopup.childNodes.length;
       aPopup.appendChild(aPopup._endOptSeparator);
-    }
-
-    if (siteURIString && !aPopup._endOptOpenSiteURI) {
-      // Add "Open (Feed Name)" menuitem if it's a livemark with a siteURI.
-      aPopup._endOptOpenSiteURI = document.createElement("menuitem");
-      aPopup._endOptOpenSiteURI.className = "openlivemarksite-menuitem";
-      aPopup._endOptOpenSiteURI.setAttribute("targetURI", siteURIString);
-      aPopup._endOptOpenSiteURI.setAttribute("oncommand",
-          "openUILink(this.getAttribute('targetURI'), event);");
+      aPopup._endMarker++;
 
-      // If a user middle-clicks this item we serve the oncommand event
-      // We are using checkForMiddleClick because of Bug 246720
-      // Note: stopPropagation is needed to avoid serving middle-click
-      // with BT_onClick that would open all items in tabs.
-      aPopup._endOptOpenSiteURI.setAttribute("onclick",
-          "checkForMiddleClick(this, event); event.stopPropagation();");
-      aPopup._endOptOpenSiteURI.setAttribute("label",
-          PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
-          [aPopup.parentNode.getAttribute("label")]));
-      aPopup.appendChild(aPopup._endOptOpenSiteURI);
-    }
-
-    if (hasMultipleURIs && !aPopup._endOptOpenAllInTabs) {
-      // Add the "Open All in Tabs" menuitem if there are
-      // at least two menuitems with places result nodes.
+      // Add the "Open All in Tabs" menuitem.
       aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
       aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
       aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
         "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event);");
       aPopup._endOptOpenAllInTabs.setAttribute("onclick",
         "checkForMiddleClick(this, event); event.stopPropagation();");
       aPopup._endOptOpenAllInTabs.setAttribute("label",
         gNavigatorBundle.getString("menuOpenAllInTabs.label"));
       aPopup.appendChild(aPopup._endOptOpenAllInTabs);
+      aPopup._endMarker++;
     }
   },
 
   _onPopupShowing: function PVB__onPopupShowing(aEvent) {
     // Avoid handling popupshowing of inner views.
     let popup = aEvent.originalTarget;
     if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
       if (!popup._placesNode.containerOpen)
@@ -866,16 +963,17 @@ PlacesToolbar.prototype = {
       // a rebuild of the toolbar, it has to be rebuilt.
       // Otherwise, it will be initialized when the toolbar overflows.
       this._chevronPopup.place = this.place;
     }
   },
 
   _insertNewItem:
   function PT__insertNewItem(aChild, aBefore) {
+    delete aChild._DOMElement;
     let type = aChild.type;
     let button;
     if (type == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
       button = document.createElement("toolbarseparator");
     }
     else {
       button = document.createElement("toolbarbutton");
       button.className = "bookmark-item";
@@ -888,18 +986,28 @@ PlacesToolbar.prototype = {
         button.setAttribute("type", "menu");
         button.setAttribute("container", "true");
 
         if (PlacesUtils.nodeIsQuery(aChild)) {
           button.setAttribute("query", "true");
           if (PlacesUtils.nodeIsTagQuery(aChild))
             button.setAttribute("tagContainer", "true");
         }
-        else if (PlacesUtils.nodeIsLivemarkContainer(aChild)) {
-          button.setAttribute("livemark", "true");
+        else if (PlacesUtils.nodeIsFolder(aChild)) {
+          PlacesUtils.livemarks.getLivemark({ id: aChild.itemId },
+            function onCompletion(aStatus, aLivemark) {
+              if (Components.isSuccessCode(aStatus)) {
+                button.setAttribute("livemark", "true");
+                // Set an expando on the node, controller
+                // will use it to build its metadata.
+                aChild._feedURI = aLivemark.feedURI;
+                aChild._siteURI = aLivemark.siteURI;
+              }
+            }
+          );
         }
 
         let popup = document.createElement("menupopup");
         popup.setAttribute("placespopup", "true");
         button.appendChild(popup);
         popup._placesNode = PlacesUtils.asContainer(aChild);
 #ifndef XP_MACOSX
         popup.setAttribute("context", "placesContext");
@@ -1149,19 +1257,31 @@ PlacesToolbar.prototype = {
 
     // We're notified for the menupopup, not the containing toolbarbutton.
     if (elt.localName == "menupopup")
       elt = elt.parentNode;
 
     if (elt.parentNode == this._rootElt) {
       // Node is on the toolbar.
       // All livemarks have a feedURI, so use it as our indicator.
-      if (aAnno == PlacesUtils.LMANNO_FEEDURI)
+      if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
         elt.setAttribute("livemark", true);
-      return;
+
+        PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId },
+          function onCompletion(aStatus, aLivemark) {
+            if (Components.isSuccessCode(aStatus)) {
+              // Set an expando on the node, controller
+              // will use it to build its metadata.
+              aPlacesNode._feedURI = aLivemark.feedURI;
+              aPlacesNode._siteURI = aLivemark.siteURI;
+              this.invalidateContainer(aPlacesNode);
+            }
+          }.bind(this)
+        );
+      }
     }
 
     PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
   },
 
   nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
     let elt = aPlacesNode._DOMElement;
     if (!elt)
@@ -1583,23 +1703,25 @@ PlacesToolbar.prototype = {
     if (parent.localName == "toolbarbutton")
       this._openedMenuButton = parent;
 
     PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
   },
 
   _onPopupHidden: function PT__onPopupHidden(aEvent) {
     let popup = aEvent.target;
-
+    let placesNode = popup._placesNode;
     // Avoid handling popuphidden of inner views
-    if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
+    if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
       // UI performance: folder queries are cheap, keep the resultnode open
       // so we don't rebuild its contents whenever the popup is reopened.
-      if (!PlacesUtils.nodeIsFolder(popup._placesNode))
-        popup._placesNode.containerOpen = false;
+      // Though, we want to always close feed containers so their
+      // expiration status will be checked at next opening.
+      if (!PlacesUtils.nodeIsFolder(placesNode) || placesNode._feedURI)
+        placesNode.containerOpen = false;
     }
 
     let parent = popup.parentNode;
     if (parent.localName == "toolbarbutton") {
       this._openedMenuButton = null;
       // Clear the dragover attribute if present, if we are dragging into a
       // folder in the hierachy of current opened popup we don't clear
       // this attribute on clearOverFolder.  See Notify for closeTimer.
--- a/suite/common/places/controller.js
+++ b/suite/common/places/controller.js
@@ -37,17 +37,16 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 // XXXmano: we should move most/all of these constants to PlacesUtils
 const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
-const ORGANIZER_SUBSCRIPTIONS_QUERY = "place:annotation=livemark%2FfeedURI";
 
 // No change to the view, preserve current selection
 const RELOAD_ACTION_NOTHING = 0;
 // Inserting items new to the view, select the inserted rows
 const RELOAD_ACTION_INSERT = 1;
 // Removing items from the view, select the first item after the last selected
 const RELOAD_ACTION_REMOVE = 2;
 // Moving items within a view, don't treat the dropped items as additional
@@ -64,16 +63,29 @@ const REMOVE_PAGES_CHUNKLEN = 300;
 // 10 is a good compromise, since allows the user to delete a little amount of
 // urls for privacy reasons, but does not cause heavy disk access
 const REMOVE_PAGES_MAX_SINGLEREMOVES = 10;
 
 // XXX: should pull in from global definition, see bug 585500
 var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
 
 /**
+ * A completion callback for reloading a live bookmark.
+ * @param   aStatus
+ *          The status code for the async livemark operation
+ * @param   aLivemark
+ *          The live bookmark
+ */
+function onReload(aStatus, aLivemark)
+{
+  if (Components.isSuccessCode(aStatus))
+    aLivemark.reload(true);
+}
+
+/**
  * Represents an insertion point within a container where we can insert
  * items.
  * @param   aItemId
  *          The identifier of the parent container
  * @param   aIndex
  *          The index within the container where we should insert
  * @param   aOrientation
  *          The orientation of the insertion. NOTE: the adjustments to the
@@ -195,25 +207,21 @@ PlacesController.prototype = {
       return this._canInsert();
     case "placesCmd_new:separator":
       return this._canInsert() &&
              !PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems &&
              this._view.result.sortingMode ==
                  Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_NONE;
     case "placesCmd_show:info":
       var selectedNode = this._view.selectedNode;
-      if (selectedNode &&
-          PlacesUtils.getConcreteItemId(selectedNode) != -1  &&
-          !PlacesUtils.nodeIsLivemarkItem(selectedNode))
-        return true;
-      return false;
+      return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1;
     case "placesCmd_reload":
       // Livemark containers
       var selectedNode = this._view.selectedNode;
-      return selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode);
+      return selectedNode && !!selectedNode._feedURI;
     case "placesCmd_sortBy:name":
       var selectedNode = this._view.selectedNode;
       return selectedNode &&
              PlacesUtils.nodeIsFolder(selectedNode) &&
              !PlacesUtils.nodeIsReadOnly(selectedNode) &&
              this._view.result.sortingMode ==
                  Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_NONE;
     case "placesCmd_createBookmark":
@@ -496,17 +504,17 @@ PlacesController.prototype = {
           if (PlacesUtils.nodeIsBookmark(node)) {
             nodeData["bookmark"] = true;
             PlacesUtils.nodeIsTagQuery(node.parent)
 
             var parentNode = node.parent;
             if (parentNode) {
               if (PlacesUtils.nodeIsTagQuery(parentNode))
                 nodeData["tagChild"] = true;
-              else if (PlacesUtils.nodeIsLivemarkContainer(parentNode))
+              else if (parentNode._feedURI)
                 nodeData["livemarkChild"] = true;
             }
           }
           break;
       }
 
       // annotations
       if (uri) {
@@ -713,18 +721,18 @@ PlacesController.prototype = {
            "This method should be passed a URI as a nsIURI object, not as a string.");
   },
 
   /**
    * Reloads the selected livemark if any.
    */
   reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
     var selectedNode = this._view.selectedNode;
-    if (selectedNode && PlacesUtils.nodeIsLivemarkContainer(selectedNode))
-      PlacesUtils.livemarks.reloadLivemarkFolder(selectedNode.itemId);
+    if (selectedNode)
+      PlacesUtils.livemarks.getLivemark({ id: selectedNode.itemId }, onReload);
   },
 
   /**
    * Gives the user a chance to cancel loading lots of tabs at once
    */
   _confirmOpenTabs: function(numTabsToOpen) {
     var pref = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefBranch);
@@ -1121,18 +1129,18 @@ PlacesController.prototype = {
           addData(PlacesUtils.TYPE_HTML, index, overrideURI);
         }
 
         // This order is _important_! It controls how this and other
         // applications select data to be inserted based on type.
         addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
 
         // Drop the feed uri for livemark containers
-        if (PlacesUtils.nodeIsLivemarkContainer(node))
-          addURIData(i, PlacesUtils.livemarks.getFeedURI(node.itemId).spec);
+        if (node._feedURI)
+          addURIData(i, node._feedURI.spec);
         else if (node.uri)
           addURIData(i);
       }
     }
     finally {
       if (!didSuppressNotifications)
         result.suppressNotifications = false;
     }
@@ -1164,18 +1172,18 @@ PlacesController.prototype = {
           continue;
         if (PlacesUtils.nodeIsFolder(node))
           copiedFolders.push(node);
 
         function generateChunk(type, overrideURI) {
           let suffix = i < (nodes.length - 1) ? PlacesUtils.endl : "";
           let uri = overrideURI;
 
-          if (PlacesUtils.nodeIsLivemarkContainer(node))
-            uri = PlacesUtils.livemarks.getFeedURI(node.itemId).spec
+          if (node._feedURI)
+            uri = node._feedURI.spec;
 
           mozURLString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_X_MOZ_URL,
                                                  uri) + suffix);
           unicodeString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_UNICODE,
                                                  uri) + suffix);
           htmlString += (PlacesUtils.wrapNode(node, PlacesUtils.TYPE_HTML,
                                                  uri) + suffix);
 
--- a/suite/common/places/treeView.js
+++ b/suite/common/places/treeView.js
@@ -124,16 +124,20 @@ PlacesTreeView.prototype = {
    *        A container result node.
    *
    * @return true if aContainer is a plain container, false otherwise.
    */
   _isPlainContainer: function PTV__isPlainContainer(aContainer) {
     if (aContainer._plainContainer !== undefined)
       return aContainer._plainContainer;
 
+    // Livemarks are always plain containers.
+    if (aContainer._feedURI)
+      return aContainer._plainContainer = true;
+
     // We don't know enough about non-query containers.
     if (!(aContainer instanceof Components.interfaces.nsINavHistoryQueryResultNode))
       return aContainer._plainContainer = false;
 
     switch (aContainer.queryOptions.resultType) {
       case Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
       case Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
       case Components.interfaces.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
@@ -323,17 +327,18 @@ PlacesTreeView.prototype = {
         }
       }
 
       this._rows[row] = curChild;
       rowsInserted++;
 
       // Recursively do containers.
       if (!this._flatList &&
-          curChild instanceof Components.interfaces.nsINavHistoryContainerResultNode) {
+          curChild instanceof Components.interfaces.nsINavHistoryContainerResultNode &&
+          !curChild._feedURI) {
         let resource = this._getResourceForNode(curChild);
         let isopen = resource != null &&
                      PlacesUIUtils.localStore.HasAssertion(resource,
                                                            openLiteral,
                                                            trueLiteral, true);
         if (isopen != curChild.containerOpen)
           aToOpen.push(curChild);
         else if (curChild.containerOpen && curChild.childCount > 0)
@@ -621,30 +626,30 @@ PlacesTreeView.prototype = {
 
       // Update parent when inserting the first item, since twisty has changed.
       if (aParentNode.childCount == 1)
         this._tree.invalidateRow(parentRow);
     }
 
     // Compute the new row number of the node.
     let row = -1;
-    if (aNewIndex == 0 || this._isPlainContainer(aParentNode)) {
+    let cc = aParentNode.childCount;
+    if (aNewIndex == 0 || this._isPlainContainer(aParentNode) || cc == 0) {
       // We don't need to worry about sub hierarchies of the parent node
       // if it's a plain container, or if the new node is its first child.
       if (aParentNode == this._rootNode)
         row = aNewIndex;
       else
         row = parentRow + aNewIndex + 1;
     }
     else {
       // Here, we try to find the next visible element in the child list so we
       // can set the new visible index to be right before that.  Note that we
       // have to search down instead of up, because some siblings could have
       // children themselves that would be in the way.
-      let cc = aParentNode.childCount;
       let separatorsAreHidden = PlacesUtils.nodeIsSeparator(aNode) &&
                                 this.isSorted();
       for (let i = aNewIndex + 1; i < cc; i++) {
         let node = aParentNode.getChild(i);
         if (!separatorsAreHidden || PlacesUtils.nodeIsSeparator(node)) {
           // The children have not been shifted so the next item will have what
           // should be our index.
           row = this._getRowForNode(node, false, parentRow, i);
@@ -810,64 +815,125 @@ PlacesTreeView.prototype = {
     if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {
       let lastModifiedColumn =
         this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED);
       if (lastModifiedColumn && !lastModifiedColumn.hidden)
         this._tree.invalidateCell(row, lastModifiedColumn);
     }
   },
 
+  _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
+    PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
+      function onCompletion(aStatus, aLivemark) {
+        let placesNode = aNode;
+        // Container may have closed since the call
+        if (!Components.isSuccessCode(aStatus) || !placesNode.containerOpen)
+          return;
+
+        let children = aLivemark.getNodesForContainer(placesNode);
+        for (let i = 0; i < children.length; i++)
+          this.nodeInserted(placesNode, children[i], i);
+      }.bind(this));
+  },
+
   nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
   },
 
   nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI);
   },
 
   nodeIconChanged: function PTV_nodeIconChanged(aNode) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
   },
 
   nodeHistoryDetailsChanged:
   function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate,
                                          aUpdatedVisitCount) {
+    if (aNode.parent && aNode.parent._feedURI) {
+      // Find the node in the parent.
+      let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent);
+      for (let i = parentRow; i < this._rows.length; i++) {
+        let child = this.nodeForTreeIndex(i);
+        if (child.uri == aNode.uri) {
+          delete child._cellProperties;
+          this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE);
+          break;
+        }
+      }
+      return;
+    }
+
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
   },
 
   nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
   },
 
   nodeKeywordChanged: function PTV_nodeKeywordChanged(aNode, aNewKeyword) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_KEYWORD);
   },
 
   nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
     if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) {
       this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
     } else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
-      // The livemark attribute is set as a cell property on the title cell.
-      this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+      PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
+        function onCompletion(aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            aNode._feedURI = aLivemark.feedURI;
+            if (aNode._cellProperties)
+              aNode._cellProperties.push(this._getAtomFor("livemark"));
+            // The livemark attribute is set as a cell property on the title cell.
+            this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+          }
+        }.bind(this)
+      );
     }
   },
 
   nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
   },
 
   nodeLastModifiedChanged:
   function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
     this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
   },
 
   containerStateChanged:
   function PTV_containerStateChanged(aNode, aOldState, aNewState) {
     this.invalidateContainer(aNode);
+
+    if (PlacesUtils.nodeIsFolder(aNode) ||
+        (this._flatList && aNode == this._rootNode)) {
+      if (PlacesUtils.asQuery(this._rootNode).queryOptions.excludeItems)
+        return;
+
+      PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
+        function onCompletion(aStatus, aLivemark) {
+          if (Components.isSuccessCode(aStatus)) {
+            let shouldInvalidate = !aNode._feedURI;
+            aNode._feedURI = aLivemark.feedURI;
+            if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
+              aLivemark.registerForUpdates(aNode, this);
+              aLivemark.reload();
+              if (shouldInvalidate)
+                this.invalidateContainer(aNode);
+            }
+            else {
+              aLivemark.unregisterForUpdates(aNode);
+            }
+          }
+        }.bind(this)
+      );
+    }
   },
 
   invalidateContainer: function PTV_invalidateContainer(aContainer) {
     NS_ASSERT(this._result, "Need to have a result to update");
     if (!this._tree)
       return;
 
     let startReplacement, replaceCount;
@@ -951,16 +1017,20 @@ PlacesTreeView.prototype = {
 
         // If we don't have a parent, we made it all the way to the root
         // and didn't find a match, so we can open our item.
         if (!parent && !item.containerOpen)
           item.containerOpen = true;
       }
     }
 
+    if (aContainer._feedURI &&
+        !PlacesUtils.asQuery(this._result.root).queryOptions.excludeItems)
+      this._populateLivemarkContainer(aContainer);
+
     this._tree.endUpdateBatch();
 
     // Restore selection.
     this._restoreSelection(nodesToReselect, aContainer);
     this.selection.selectEventsSuppressed = false;
   },
 
   _columns: [],
@@ -1108,33 +1178,46 @@ PlacesTreeView.prototype = {
             properties.push(this._getAtomFor("tagContainer"));
           else if (PlacesUtils.nodeIsDay(node))
             properties.push(this._getAtomFor("dayContainer"));
           else if (PlacesUtils.nodeIsHost(node))
             properties.push(this._getAtomFor("hostContainer"));
         }
         else if (nodeType == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
                  nodeType == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
-          if (PlacesUtils.nodeIsLivemarkContainer(node))
+          if (node._feedURI)
             properties.push(this._getAtomFor("livemark"));
+          else {
+            PlacesUtils.livemarks.getLivemark({ id: node.itemId },
+              function onCompletion(aStatus, aLivemark) {
+                if (Components.isSuccessCode(aStatus)) {
+                  node._feedURI = aLivemark.feedURI;
+                  node._cellProperties.push(this._getAtomFor("livemark"));
+                  // The livemark attribute is set as a cell property on the title cell.
+                  this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
+                }
+              }.bind(this)
+            );
+          }
         }
 
         if (itemId != -1) {
           let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
           if (queryName)
             properties.push(this._getAtomFor("OrganizerQuery_" + queryName));
         }
       }
       else if (nodeType == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
         properties.push(this._getAtomFor("separator"));
       else if (PlacesUtils.nodeIsURI(node)) {
         properties.push(this._getAtomFor(PlacesUIUtils.guessUrlSchemeForUI(node.uri)));
-        if (itemId != -1) {
-          if (PlacesUtils.nodeIsLivemarkContainer(node.parent))
-            properties.push(this._getAtomFor("livemarkItem"));
+        if (node.parent._feedURI) {
+          properties.push(this._getAtomFor("livemarkItem"));
+          if (node.accessCount)
+            properties.push(this._getAtomFor("visited"));
         }
       }
 
       node._cellProperties = properties;
     }
     for (let i = 0; i < node._cellProperties.length; i++)
       aProperties.AppendElement(node._cellProperties[i]);
   },
@@ -1153,17 +1236,17 @@ PlacesTreeView.prototype = {
       if (this._flatList)
         return true;
 
       // treat non-expandable childless queries as non-containers
       if (PlacesUtils.nodeIsQuery(node)) {
         let parent = node.parent;
         if ((PlacesUtils.nodeIsQuery(parent) ||
              PlacesUtils.nodeIsFolder(parent)) &&
-            !node.hasChildren)
+            !PlacesUtils.asQuery(node).hasChildren)
           return PlacesUtils.asQuery(parent).queryOptions.expandQueries;
       }
       return true;
     }
     return false;
   },
 
   isContainerOpen: function PTV_isContainerOpen(aRow) {
@@ -1173,16 +1256,19 @@ PlacesTreeView.prototype = {
     // All containers are listed in the rows array.
     return this._rows[aRow].containerOpen;
   },
 
   isContainerEmpty: function PTV_isContainerEmpty(aRow) {
     if (this._flatList)
       return true;
 
+    if (this._rows[aRow]._feedURI)
+      return PlacesUtils.asQuery(this._result.root).queryOptions.excludeItems;
+
     // All containers are listed in the rows array.
     return !this._rows[aRow].hasChildren;
   },
 
   isSeparator: function PTV_isSeparator(aRow) {
     // All separators are listed in the rows array.
     let node = this._rows[aRow];
     return node && PlacesUtils.nodeIsSeparator(node);
@@ -1417,25 +1503,28 @@ PlacesTreeView.prototype = {
       throw Components.results.NS_ERROR_UNEXPECTED;
 
     let node = this._rows[aRow];
     if (this._flatList && this._openContainerCallback) {
       this._openContainerCallback(node);
       return;
     }
 
-    let resource = this._getResourceForNode(node);
-    if (resource) {
-      const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
-      const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+    // Persist container open state, except for livemarks.
+    if (!node._feedURI) {
+      let resource = this._getResourceForNode(node);
+      if (resource) {
+        const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
+        const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
 
-      if (node.containerOpen)
-        PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
-      else
-        PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+        if (node.containerOpen)
+          PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
+        else
+          PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+      }
     }
 
     node.containerOpen = !node.containerOpen;
   },
 
   cycleHeader: function PTV_cycleHeader(aColumn) {
     if (!this._result)
       throw Components.results.NS_ERROR_UNEXPECTED;
@@ -1568,20 +1657,18 @@ PlacesTreeView.prototype = {
     // Only bookmark-nodes are editable, and those are never built lazily
     let node = this._rows[aRow];
     if (!node || node.itemId == -1)
       return false;
 
     // The following items are never editable:
     // * Read-only items.
     // * places-roots
-    // * livemark items
     // * separators
     if (PlacesUtils.nodeIsReadOnly(node) ||
-        PlacesUtils.nodeIsLivemarkItem(node) ||
         PlacesUtils.nodeIsSeparator(node))
       return false;
 
     if (PlacesUtils.nodeIsFolder(node)) {
       let itemId = PlacesUtils.getConcreteItemId(node);
       if (PlacesUtils.isRootItem(itemId))
         return false;
     }
--- a/suite/smile/src/smileApplication.js
+++ b/suite/smile/src/smileApplication.js
@@ -55,17 +55,18 @@ var Utilities = {
     return this._bookmarks;
   },
 
   _livemarks : null,
   get livemarks() {
     if (!this._livemarks) {
       this._livemarks =
         Components.classes["@mozilla.org/browser/livemark-service;2"]
-                  .getService(Components.interfaces.nsILivemarkService);
+                  .getService(Components.interfaces.mozIAsyncLivemarks)
+                  .QueryInterface(Components.interfaces.nsILivemarkService);
     }
     return this._livemarks;
   },
 
   _annotations : null,
   get annotations() {
     if (!this._annotations) {
       this._annotations =
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ac1b3ff0af1e685b756ce1467d6ca1f7fd2b2a00
GIT binary patch
literal 989
zc%17D@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJdx@v7EBhT*Sspz$jibuDfkK=G
z9+AZi3|t>Tn9*sC$qWVtW`+Qt5Lbo=ww!8wqIMFB!HU|+n!1^~KSf0?$}QryB<Jid
z%-vU1vcI<WU{n3!=B{It`cKT9adP3D(<_#r*|7TThV5rJGD-0>jDk@xWP#5P=KDa3
zu_VYZn8D%MjWi%9+0(@_q~ccTxx0J~3LLBnzZ+Lv(fRl{-qblW@^wzkoP0i$Wi0Z=
z+*#}lf>+L+QHr?FtUg~gT!+n&O?{DcBVWG$&U~LOzqh_wX%tl8t<0QTCU)`5lZAiW
iF9#asUXnh;URT5<w2L`8PMm2Q$PJ#ZelF{r5}E);qG7=R
--- a/suite/themes/classic/communicator/bookmarks/bookmarks.css
+++ b/suite/themes/classic/communicator/bookmarks/bookmarks.css
@@ -88,17 +88,22 @@ treechildren::-moz-tree-image(Name, quer
 .bookmark-item[container][livemark],
 treechildren::-moz-tree-image(title, container, livemark) {
   list-style-image: url("chrome://communicator/skin/bookmarks/livemark-folder.png");
   -moz-image-region: auto;
 }
 
 .bookmark-item[container][livemark] .bookmark-item,
 treechildren::-moz-tree-image(title, livemarkItem) {
-  list-style-image: url("chrome://communicator/skin/bookmarks/livemark-item.png");
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-item-updated.png");
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited],
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-item.png");
 }
 
 treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
   list-style-image: url("chrome://communicator/skin/bookmarks/allBookmarks.png");
   -moz-image-region: auto;
 }
 
 #bookmarksToolbarFolderMenu,
deleted file mode 100644
index ec53c96f935dc40b173644806f5e24b7cf3f8fbd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/suite/themes/classic/jar.mn
+++ b/suite/themes/classic/jar.mn
@@ -22,17 +22,16 @@ classic.jar:
   skin/classic/communicator/bookmarks/bookmarksToolbar.css              (mac/communicator/bookmarks/bookmarksToolbar.css)
   skin/classic/communicator/bookmarks/bookmarksToolbar.png              (mac/communicator/bookmarks/bookmarksToolbar.png)
   skin/classic/communicator/bookmarks/editBookmarkOverlay.css           (mac/communicator/bookmarks/editBookmarkOverlay.css)
   skin/classic/communicator/bookmarks/expander-closed.png               (mac/communicator/bookmarks/expander-closed.png)
   skin/classic/communicator/bookmarks/expander-closed-active.png        (mac/communicator/bookmarks/expander-closed-active.png)
   skin/classic/communicator/bookmarks/expander-open.png                 (mac/communicator/bookmarks/expander-open.png)
   skin/classic/communicator/bookmarks/expander-open-active.png          (mac/communicator/bookmarks/expander-open-active.png)
   skin/classic/communicator/bookmarks/livemark-folder.png               (mac/communicator/bookmarks/livemark-folder.png)
-  skin/classic/communicator/bookmarks/livemark-item.png                 (mac/communicator/bookmarks/livemark-item.png)
   skin/classic/communicator/bookmarks/query.png                         (mac/communicator/bookmarks/query.png)
   skin/classic/communicator/bookmarks/toolbarDropMarker.png             (mac/communicator/bookmarks/toolbarDropMarker.png)
   skin/classic/communicator/directory/directory.css                     (mac/communicator/directory/directory.css)
   skin/classic/communicator/downloads/downloadButtons.png               (mac/communicator/downloads/downloadButtons.png)
   skin/classic/communicator/downloads/progressBg.png                    (mac/communicator/downloads/progressBg.png)
   skin/classic/communicator/downloads/downloadmanager.css               (mac/communicator/downloads/downloadmanager.css)
   skin/classic/communicator/search/searchbar.css                        (mac/communicator/search/searchbar.css)
   skin/classic/communicator/search/searchbar-dropmarker.png             (mac/communicator/search/searchbar-dropmarker.png)
@@ -62,17 +61,16 @@ classic.jar:
   skin/classic/communicator/bookmarks/bookmark-folder-open.png          (communicator/bookmarks/bookmark-folder-open.png)
   skin/classic/communicator/bookmarks/bookmarks.css                     (communicator/bookmarks/bookmarks.css)
   skin/classic/communicator/bookmarks/bookmarksManager.css              (communicator/bookmarks/bookmarksManager.css)
   skin/classic/communicator/bookmarks/bookmarksMenu.png                 (communicator/bookmarks/bookmarksMenu.png)
   skin/classic/communicator/bookmarks/bookmarksToolbar.css              (communicator/bookmarks/bookmarksToolbar.css)
   skin/classic/communicator/bookmarks/bookmarksToolbar.png              (communicator/bookmarks/bookmarksToolbar.png)
   skin/classic/communicator/bookmarks/editBookmarkOverlay.css           (communicator/bookmarks/editBookmarkOverlay.css)
   skin/classic/communicator/bookmarks/livemark-folder.png               (communicator/bookmarks/livemark-folder.png)
-  skin/classic/communicator/bookmarks/livemark-item.png                 (communicator/bookmarks/livemark-item.png)
   skin/classic/communicator/bookmarks/query.png                         (communicator/bookmarks/query.png)
   skin/classic/communicator/bookmarks/toolbarDropMarker.png             (communicator/bookmarks/toolbarDropMarker.png)
   skin/classic/communicator/directory/directory.css                     (communicator/directory/directory.css)
   skin/classic/communicator/directory/file.gif                          (communicator/directory/file.gif)
   skin/classic/communicator/directory/folder-clsd.gif                   (communicator/directory/folder-clsd.gif)
   skin/classic/communicator/directory/folder-open.gif                   (communicator/directory/folder-open.gif)
   skin/classic/communicator/downloads/downloadButtons.png               (communicator/downloads/downloadButtons.png)
   skin/classic/communicator/downloads/downloadmanager.css               (communicator/downloads/downloadmanager.css)
@@ -91,16 +89,17 @@ classic.jar:
   skin/classic/communicator/communicatorBindings.xml                    (communicator/communicatorBindings.xml)
   skin/classic/communicator/preferences.css                             (communicator/preferences.css)
   skin/classic/communicator/prefpanels.css                              (communicator/prefpanels.css)
   skin/classic/communicator/smileys.css                                 (communicator/smileys.css)
   skin/classic/communicator/tasksOverlay.css                            (communicator/tasksOverlay.css)
   skin/classic/communicator/bookmarks/allBookmarks.png                  (communicator/bookmarks/allBookmarks.png)
   skin/classic/communicator/bookmarks/bookmark.png                      (communicator/bookmarks/bookmark.png)
   skin/classic/communicator/bookmarks/bookmark-item-dis.png             (communicator/bookmarks/bookmark-item-dis.png)
+  skin/classic/communicator/bookmarks/bookmark-item-updated.png         (communicator/bookmarks/bookmark-item-updated.png)
   skin/classic/communicator/bookmarks/bookmark-item.png                 (communicator/bookmarks/bookmark-item.png)
   skin/classic/communicator/bookmarks/unsortedBookmarks.png             (communicator/bookmarks/unsortedBookmarks.png)
   skin/classic/communicator/brand/throbber-anim.png                     (communicator/brand/throbber-anim.png)
   skin/classic/communicator/brand/throbber-single.png                   (communicator/brand/throbber-single.png)
   skin/classic/communicator/brand/throbber16-anim.png                   (communicator/brand/throbber16-anim.png)
   skin/classic/communicator/brand/throbber16-single.png                 (communicator/brand/throbber16-single.png)
   skin/classic/communicator/dataman/dataman.css                         (communicator/dataman/dataman.css)
   skin/classic/communicator/dataman/datamanIcon-16.png                  (communicator/dataman/datamanIcon-16.png)
--- a/suite/themes/classic/mac/communicator/bookmarks/bookmarks.css
+++ b/suite/themes/classic/mac/communicator/bookmarks/bookmarks.css
@@ -81,17 +81,22 @@ treechildren::-moz-tree-image(Name, quer
 
 .bookmark-item[container][livemark],
 treechildren::-moz-tree-image(title, container, livemark) {
   list-style-image: url("chrome://communicator/skin/bookmarks/livemark-folder.png");
 }
 
 .bookmark-item[container][livemark] .bookmark-item,
 treechildren::-moz-tree-image(title, livemarkItem) {
-  list-style-image: url("chrome://communicator/skin/bookmarks/livemark-item.png");
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-item-updated.png");
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited],
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-item.png");
 }
 
 treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
   list-style-image: url("chrome://communicator/skin/bookmarks/allBookmarks.png");
 }
 
 #bookmarksToolbarFolderMenu,
 #BMB_bookmarksToolbarFolderMenu,
deleted file mode 100644
index 6d86a9a6a89f17cee908037b9722b2bebab2a43b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..19ff744133a2cd28746645fca4b1755cfc407e57
GIT binary patch
literal 911
zc${<hbhEHb6krfw_|Cxa|NnnxKKY=ywAZg+&t19s*qLj44xhes{lWg@XCJ+Mefr96
zTlav?dycQ(vM({GR7h6i>fI;ncOAI<_?1ssf~KJ@!zdU9Lm4RkWMKu-Iv@g+Cm1+#
z7#KNZI1~;zG&2bb`JC9G;K(SzZxzDfaG;@^iH~hTM4>|i6SGmC&JMwa4J@2|3LKu3
zl3kgExFixXFDzwd65ujOxH;hg3zL@ehmsuyjEt;oQarI56POyA1eA?re>60lUa7Lt
OZ||zDtFJdOSOWm^(@i}9
--- a/suite/themes/modern/communicator/bookmarks/bookmarks.css
+++ b/suite/themes/modern/communicator/bookmarks/bookmarks.css
@@ -89,17 +89,22 @@ treechildren::-moz-tree-image(Name, quer
 .bookmark-item[container][livemark],
 treechildren::-moz-tree-image(title, container, livemark) {
   list-style-image: url("chrome://communicator/skin/bookmarks/livemark-folder.png");
   -moz-image-region: auto;
 }
 
 .bookmark-item[container][livemark] .bookmark-item,
 treechildren::-moz-tree-image(title, livemarkItem) {
-  list-style-image: url("chrome://communicator/skin/bookmarks/livemark-item.png");
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-item-updated.gif");
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited],
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+  list-style-image: url("chrome://communicator/skin/bookmarks/bookmark-item.gif");
 }
 
 treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
   list-style-image: url("chrome://communicator/skin/bookmarks/allBookmarks.png");
   -moz-image-region: auto;
 }
 
 #bookmarksToolbarFolderMenu,
deleted file mode 100644
index ec53c96f935dc40b173644806f5e24b7cf3f8fbd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/suite/themes/modern/jar.mn
+++ b/suite/themes/modern/jar.mn
@@ -27,26 +27,26 @@ modern.jar:
   skin/modern/communicator/aboutSessionRestore.css                 (communicator/aboutSessionRestore.css)
   skin/modern/communicator/viewSourceOverlay.css                   (communicator/viewSourceOverlay.css)
   skin/modern/communicator/bookmarks/allBookmarks.png              (communicator/bookmarks/allBookmarks.png)
   skin/modern/communicator/bookmarks/bookmark.png                  (communicator/bookmarks/bookmark.png)
   skin/modern/communicator/bookmarks/bookmark-folder-closed.gif    (communicator/bookmarks/bookmark-folder-closed.gif)
   skin/modern/communicator/bookmarks/bookmark-folder-dis.gif       (communicator/bookmarks/bookmark-folder-dis.gif)
   skin/modern/communicator/bookmarks/bookmark-folder-open.gif      (communicator/bookmarks/bookmark-folder-open.gif)
   skin/modern/communicator/bookmarks/bookmark-item-dis.gif         (communicator/bookmarks/bookmark-item-dis.gif)
+  skin/modern/communicator/bookmarks/bookmark-item-updated.gif     (communicator/bookmarks/bookmark-item-updated.gif)
   skin/modern/communicator/bookmarks/bookmark-item.gif             (communicator/bookmarks/bookmark-item.gif)
   skin/modern/communicator/bookmarks/bookmark-item.png             (communicator/bookmarks/bookmark-item.png)
   skin/modern/communicator/bookmarks/bookmarks.css                 (communicator/bookmarks/bookmarks.css)
   skin/modern/communicator/bookmarks/bookmarksManager.css          (communicator/bookmarks/bookmarksManager.css)
   skin/modern/communicator/bookmarks/bookmarksMenu.png             (communicator/bookmarks/bookmarksMenu.png)
   skin/modern/communicator/bookmarks/bookmarksToolbar.css          (communicator/bookmarks/bookmarksToolbar.css)
   skin/modern/communicator/bookmarks/bookmarksToolbar.png          (communicator/bookmarks/bookmarksToolbar.png)
   skin/modern/communicator/bookmarks/editBookmarkOverlay.css       (communicator/bookmarks/editBookmarkOverlay.css)
   skin/modern/communicator/bookmarks/livemark-folder.png           (communicator/bookmarks/livemark-folder.png)
-  skin/modern/communicator/bookmarks/livemark-item.png             (communicator/bookmarks/livemark-item.png)
   skin/modern/communicator/bookmarks/query.png                     (communicator/bookmarks/query.png)
   skin/modern/communicator/bookmarks/tagContainerIcon.png          (communicator/bookmarks/tagContainerIcon.png)
   skin/modern/communicator/bookmarks/toolbarDropMarker.png         (communicator/bookmarks/toolbarDropMarker.png)
   skin/modern/communicator/bookmarks/unsortedBookmarks.png         (communicator/bookmarks/unsortedBookmarks.png)
   skin/modern/communicator/brand/throbber-anim.png                 (communicator/brand/throbber-anim.png)
   skin/modern/communicator/brand/throbber-single.png               (communicator/brand/throbber-single.png)
   skin/modern/communicator/brand/throbber16-anim.png               (communicator/brand/throbber16-anim.png)
   skin/modern/communicator/brand/throbber16-single.png             (communicator/brand/throbber16-single.png)