Bug 371812 add bookmark id support to bookmarks html import/export (r=sspitzer)
authordietrich@mozilla.com
Sat, 31 Mar 2007 16:34:23 -0700
changeset 248 32454a9197200fb435a992dc49b80aff1ce4118c
parent 247 ba87715db7da1b5c9f07e247bbbb44135e20588d
child 249 c193579186832413f7a5fb9d434e20ba852f6544
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssspitzer
bugs371812
milestone1.9a4pre
Bug 371812 add bookmark id support to bookmarks html import/export (r=sspitzer)
toolkit/components/places/src/nsBookmarksHTML.cpp
--- a/toolkit/components/places/src/nsBookmarksHTML.cpp
+++ b/toolkit/components/places/src/nsBookmarksHTML.cpp
@@ -120,16 +120,17 @@ static NS_DEFINE_CID(kParserCID, NS_PARS
 #define KEY_PLACESROOT_LOWER "places_root"
 #define KEY_HREF_LOWER "href"
 #define KEY_FEEDURL_LOWER "feedurl"
 #define KEY_WEB_PANEL_LOWER "web_panel"
 #define KEY_LASTCHARSET_LOWER "last_charset"
 #define KEY_ICON_LOWER "icon"
 #define KEY_ICON_URI_LOWER "icon_uri"
 #define KEY_SHORTCUTURL_LOWER "shortcuturl"
+#define KEY_ID_LOWER "id"
 
 #define LOAD_IN_SIDEBAR_ANNO NS_LITERAL_CSTRING("bookmarkProperties/loadInSidebar")
 
 #define BOOKMARKS_MENU_ICON_URI "chrome://browser/skin/places/bookmarksMenu.png"
 #define BOOKMARKS_TOOLBAR_ICON_URI "chrome://browser/skin/places/bookmarksToolbar.png"
 
 // define to get debugging messages on console about import
 //#define DEBUG_IMPORT
@@ -166,16 +167,19 @@ public:
   // be nested so this will be 0 or 1.
   PRInt32 mContainerNesting;
 
   // when we find a heading tag, it actually affects the title of the NEXT
   // container in the list. This stores that heading tag and whether it was
   // special. 'ConsumeHeading' resets this.
   ContainerType mLastContainerType;
 
+  // Container Id, see above
+  PRInt64 mLastContainerId;
+
   // this contains the text from the last begin tag until now. It is reset
   // at every begin tag. We can check it when we see a </a>, or </h3>
   // to see what the text content of that node should be.
   nsString mPreviousText;
 
   // true when we hit a <dd>, which contains the description for the preceeding
   // <a> tag. We can't just check for </dd> like we can for </a> or </h3>
   // because if there is a sub-folder, it is actually a child of the <dd>
@@ -196,25 +200,25 @@ public:
   // This is cleared whenever we hit a <h3>, so that we know NOT to save this
   // with a bookmark, but to keep it until 
   nsCOMPtr<nsIURI> mPreviousLink;
 
   // contains the URL of the previous livemark, so that when the link ends,
   // and the livemark title is known, we can create it.
   nsCOMPtr<nsIURI> mPreviousFeed;
 
-  void ConsumeHeading(nsAString* aHeading, ContainerType* aContainerType)
+  void ConsumeHeading(nsAString* aHeading, ContainerType* aContainerType, PRInt64* aContainerId)
   {
     *aHeading = mPreviousText;
     *aContainerType = mLastContainerType;
+    *aContainerId = mLastContainerId;
     mPreviousText.Truncate(0);
   }
 
-  // Contains the id of a newly created bookmark.
-  // XXXDietrich - may also be a pre-existing bookmark once we support importing Places-exported bookmarks.html files.
+  // Contains the id of an imported, or newly created bookmark.
   PRInt64 mPreviousId;
 };
 
 
 /**
  * The content sink stuff is based loosely on 
  */
 class BookmarkContentSink : public nsIHTMLContentSink
@@ -318,16 +322,18 @@ protected:
   }
   nsresult NewFrame();
   nsresult PopFrame();
 
   nsresult SetFaviconForURI(nsIURI* aPageURI, nsIURI* aFaviconURI,
                             const nsCString& aData);
   nsresult SetFaviconForFolder(PRInt64 aFolder, const nsACString& aFavicon);
 
+  PRInt64 ConvertImportedIdToInternalId(const nsCString& aId);
+
 #ifdef DEBUG_IMPORT
   // prints spaces for indenting to the current frame depth
   void PrintNesting()
   {
     for (PRUint32 i = 0; i < mFrames.Length(); i ++)
       printf("  ");
   }
 #endif
@@ -560,16 +566,17 @@ void
 BookmarkContentSink::HandleHeadBegin(const nsIParserNode& node)
 {
   BookmarkImportFrame& frame = CurFrame();
 
   // after a heading, a previous bookmark is not applicable (for example, for
   // the descriptions contained in a <dd>). Neither is any previous head type
   frame.mPreviousLink = nsnull;
   frame.mLastContainerType = BookmarkImportFrame::Container_Normal;
+  frame.mLastContainerId = 0;
 
   // It is syntactically possible for a heading to appear after another heading
   // but before the <dl> that encloses that folder's contents.  This should not
   // happen in practice, as the file will contain "<dl></dl>" sequence for
   // empty containers.
   //
   // Just to be on the safe side, if we encounter
   //   <h3>FOO</h3>
@@ -594,16 +601,19 @@ BookmarkContentSink::HandleHeadBegin(con
         frame.mLastContainerType = BookmarkImportFrame::Container_Toolbar;
         break;
       } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_BOOKMARKSMENU_LOWER)) {
         frame.mLastContainerType = BookmarkImportFrame::Container_Menu;
         break;
       } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) {
         frame.mLastContainerType = BookmarkImportFrame::Container_Places;
         break;
+      } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_ID_LOWER)) {
+        frame.mLastContainerId =
+          ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(node.GetKeyAt(i)));
       }
     }
   }
   CurFrame().mPreviousText.Truncate(0);
 }
 
 
 // BookmarkContentSink::HandleHeadEnd
@@ -623,32 +633,38 @@ BookmarkContentSink::HandleHeadEnd()
 //
 //    Handles "<a" tags by creating a new bookmark. The title of the bookmark
 //    will be the text content, which will be stuffed in mPreviousText for us
 //    and which will be saved by HandleLinkEnd
 
 void
 BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
 {
+  nsresult rv;
+
   BookmarkImportFrame& frame = CurFrame();
 
   // We need to make sure that the feed URIs from previous frames are emptied. 
   frame.mPreviousFeed = nsnull;
 
+  // We need to make sure that the bookmark id from previous frames are emptied. 
+  frame.mPreviousId = 0;
+
   // mPreviousText will hold our link text, clear it so that can be appended to
   frame.mPreviousText.Truncate();
 
   // get the attributes we care about
   nsAutoString href;
   nsAutoString feedUrl;
   nsAutoString icon;
   nsAutoString iconUri;
   nsAutoString lastCharset;
   nsAutoString keyword;
   nsAutoString webPanel;
+  nsAutoString id;
   PRInt32 attrCount = node.GetAttributeCount();
   for (PRInt32 i = 0; i < attrCount; i ++) {
     const nsAString& key = node.GetKeyAt(i);
     if (key.LowerCaseEqualsLiteral(KEY_HREF_LOWER)) {
       href = node.GetValueAt(i);
     } else if (key.LowerCaseEqualsLiteral(KEY_FEEDURL_LOWER)) {
       feedUrl = node.GetValueAt(i);
     } else if (key.LowerCaseEqualsLiteral(KEY_ICON_LOWER)) {
@@ -656,25 +672,28 @@ BookmarkContentSink::HandleLinkBegin(con
     } else if (key.LowerCaseEqualsLiteral(KEY_ICON_URI_LOWER)) {
       iconUri = node.GetValueAt(i);
     } else if (key.LowerCaseEqualsLiteral(KEY_LASTCHARSET_LOWER)) {
       lastCharset = node.GetValueAt(i);
     } else if (key.LowerCaseEqualsLiteral(KEY_SHORTCUTURL_LOWER)) {
       keyword = node.GetValueAt(i);
     } else if (key.LowerCaseEqualsLiteral(KEY_WEB_PANEL_LOWER)) {
       webPanel = node.GetValueAt(i);
+    } else if (key.LowerCaseEqualsLiteral(KEY_ID_LOWER)) {
+      id = node.GetValueAt(i);
     }
   }
   href.Trim(kWhitespace);
   feedUrl.Trim(kWhitespace);
   icon.Trim(kWhitespace);
   iconUri.Trim(kWhitespace);
   lastCharset.Trim(kWhitespace);
   keyword.Trim(kWhitespace);
   webPanel.Trim(kWhitespace);
+  id.Trim(kWhitespace);
 
   // For feeds, get the feed URL. If it is invalid, it will leave mPreviousFeed
   // NULL and we'll continue trying to create it as a normal bookmark.
   if (! feedUrl.IsEmpty()) {
     NS_NewURI(getter_AddRefs(frame.mPreviousFeed),
               NS_ConvertUTF16toUTF8(feedUrl), nsnull);
   }
 
@@ -693,25 +712,31 @@ BookmarkContentSink::HandleLinkBegin(con
     nsresult rv = NS_NewURI(getter_AddRefs(frame.mPreviousLink),
                    href, nsnull);
     if (NS_FAILED(rv) && ! frame.mPreviousFeed) {
       frame.mPreviousLink = nsnull;
       return; // invalid link
     }
   }
 
+  // if there's a pre-existing Places bookmark id, use it
+  frame.mPreviousId = ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(id));
+
   // if there is a feedURL, this is a livemark, which is a special case
-  // that we handle in HandleLinkBegin(): don't create normal bookmarks
+  // that we handle in HandleLinkEnd(): don't create normal bookmarks
   if (frame.mPreviousFeed)
     return;
 
-  // create the bookmark
-  nsresult rv = mBookmarksService->InsertItem(frame.mContainerID, frame.mPreviousLink,
-                                     mBookmarksService->DEFAULT_INDEX, &frame.mPreviousId);
-  NS_ASSERTION(NS_SUCCEEDED(rv), "InsertItem failed");
+  // if no previous id (or a legacy id), create a new bookmark
+  if (frame.mPreviousId == 0) {
+    // create the bookmark
+    rv = mBookmarksService->InsertItem(frame.mContainerID, frame.mPreviousLink,
+                                       mBookmarksService->DEFAULT_INDEX, &frame.mPreviousId);
+    NS_ASSERTION(NS_SUCCEEDED(rv), "InsertItem failed");
+  }
 
   // save the favicon, ignore errors
   if (! icon.IsEmpty() || ! iconUri.IsEmpty()) {
     nsCOMPtr<nsIURI> iconUriObject;
     NS_NewURI(getter_AddRefs(iconUriObject), iconUri);
     if (! icon.IsEmpty() || iconUriObject)
       SetFaviconForURI(frame.mPreviousLink, iconUriObject,
                        NS_ConvertUTF16toUTF8(icon));
@@ -743,39 +768,52 @@ BookmarkContentSink::HandleLinkBegin(con
 //
 //    Saves the title for the given bookmark. This only writes the user title.
 //    Any previous title will be untouched. If this is a new entry, it will have
 //    an empty "official" title until you visit it.
 
 void
 BookmarkContentSink::HandleLinkEnd()
 {
+  nsresult rv;
   BookmarkImportFrame& frame = CurFrame();
   frame.mPreviousText.Trim(kWhitespace);
   if (frame.mPreviousFeed) {
     // The bookmark is actually a livemark.  Create it here.
     // (It gets created here instead of in HandleLinkBegin()
     // because we need to know the title before creating it.)
     PRInt64 folderId;
 
-    if (mIsImportDefaults) {
-      mLivemarkService->CreateLivemarkFolderOnly(mBookmarksService,
-                                                 frame.mContainerID,
-                                                 frame.mPreviousText,
-                                                 frame.mPreviousLink,
-                                                 frame.mPreviousFeed,
-                                                 -1,
-                                                 &folderId);
+    if (frame.mPreviousId > 0) {
+      // It's a pre-existing livemark, so update its properties
+      rv = mLivemarkService->SetSiteURI(frame.mPreviousId, frame.mPreviousLink);
+      NS_ASSERTION(NS_SUCCEEDED(rv), "SetSiteURI failed!");
+      rv = mLivemarkService->SetFeedURI(frame.mPreviousId, frame.mPreviousFeed);
+      NS_ASSERTION(NS_SUCCEEDED(rv), "SetFeedURI failed!");
+      rv = mBookmarksService->SetFolderTitle(frame.mPreviousId, frame.mPreviousText);
+      NS_ASSERTION(NS_SUCCEEDED(rv), "SetFolderTitle failed!");
     } else {
-      mLivemarkService->CreateLivemark(frame.mContainerID,
-                                       frame.mPreviousText,
-                                       frame.mPreviousLink,
-                                       frame.mPreviousFeed,
-                                       -1,
-                                       &folderId);
+      if (mIsImportDefaults) {
+        rv = mLivemarkService->CreateLivemarkFolderOnly(mBookmarksService,
+                                                   frame.mContainerID,
+                                                   frame.mPreviousText,
+                                                   frame.mPreviousLink,
+                                                   frame.mPreviousFeed,
+                                                   -1,
+                                                   &folderId);
+        NS_ASSERTION(NS_SUCCEEDED(rv), "CreateLivemarkFolderOnly failed!");
+      } else {
+        rv = mLivemarkService->CreateLivemark(frame.mContainerID,
+                                         frame.mPreviousText,
+                                         frame.mPreviousLink,
+                                         frame.mPreviousFeed,
+                                         -1,
+                                         &folderId);
+        NS_ASSERTION(NS_SUCCEEDED(rv), "CreateLivemark failed!");
+      }
     }
 #ifdef DEBUG_IMPORT
     PrintNesting();
     printf("Creating livemark '%s'\n",
            NS_ConvertUTF16toUTF8(frame.mPreviousText).get());
 #endif
   }
   else if (frame.mPreviousLink) {
@@ -818,74 +856,78 @@ BookmarkContentSink::HandleSeparator()
 //    This is called when there is a new folder found. The folder takes the
 //    name from the previous frame's heading.
 
 nsresult
 BookmarkContentSink::NewFrame()
 {
   nsresult rv;
 
+  PRInt64 ourID = 0;
   nsString containerName;
   BookmarkImportFrame::ContainerType containerType;
-  CurFrame().ConsumeHeading(&containerName, &containerType);
+  CurFrame().ConsumeHeading(&containerName, &containerType, &ourID);
 
   PRBool updateFolder = PR_FALSE;
-  PRInt64 ourID = 0;
-  switch (containerType) {
-    case BookmarkImportFrame::Container_Normal:
-      // regular folder: use an existing folder if that name already exists
-      rv = mBookmarksService->GetChildFolder(CurFrame().mContainerID,
-                                             containerName, &ourID);
-      NS_ENSURE_SUCCESS(rv, rv);
-      if (! ourID) {
-        // need to append a new folder
-        rv = mBookmarksService->CreateFolder(CurFrame().mContainerID,
-                                            containerName,
-                                            mBookmarksService->DEFAULT_INDEX, &ourID);
+  if (ourID == 0) {
+    switch (containerType) {
+      case BookmarkImportFrame::Container_Normal:
+        // regular folder: use an existing folder if that name already exists
+        rv = mBookmarksService->GetChildFolder(CurFrame().mContainerID,
+                                               containerName, &ourID);
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (ourID == 0) {
+          // need to append a new folder
+          rv = mBookmarksService->CreateFolder(CurFrame().mContainerID,
+                                              containerName,
+                                              mBookmarksService->DEFAULT_INDEX, &ourID);
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        break;
+      case BookmarkImportFrame::Container_Places:
+        // places root, never reparent here, when we're building the initial
+        // hierarchy, it will only be defined at the top level
+        rv = mBookmarksService->GetPlacesRoot(&ourID);
+        NS_ENSURE_SUCCESS(rv, rv);
+        break;
+      case BookmarkImportFrame::Container_Menu:
+        // menu root
+        rv = mBookmarksService->GetBookmarksRoot(&ourID);
         NS_ENSURE_SUCCESS(rv, rv);
-      }
-      break;
-    case BookmarkImportFrame::Container_Places:
-      // places root, never reparent here, when we're building the initial
-      // hierarchy, it will only be defined at the top level
-      rv = mBookmarksService->GetPlacesRoot(&ourID);
-      NS_ENSURE_SUCCESS(rv, rv);
-      break;
-    case BookmarkImportFrame::Container_Menu:
-      // menu root
-      rv = mBookmarksService->GetBookmarksRoot(&ourID);
-      NS_ENSURE_SUCCESS(rv, rv);
-      if (mAllowRootChanges) {
-        updateFolder = PR_TRUE;
-        SetFaviconForFolder(ourID, NS_LITERAL_CSTRING(BOOKMARKS_MENU_ICON_URI));
-      }
-      break;
-    case BookmarkImportFrame::Container_Toolbar:
-      // get toolbar folder
-      PRInt64 btf;
-      rv = mBookmarksService->GetToolbarFolder(&btf);
-      NS_ENSURE_SUCCESS(rv, rv);
-      if (!btf) {
-        // create new folder
-        rv = mBookmarksService->CreateFolder(CurFrame().mContainerID,
-                                            containerName,
-                                            mBookmarksService->DEFAULT_INDEX, &ourID);
+        if (mAllowRootChanges) {
+          updateFolder = PR_TRUE;
+          rv = SetFaviconForFolder(ourID, NS_LITERAL_CSTRING(BOOKMARKS_MENU_ICON_URI));
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        break;
+      case BookmarkImportFrame::Container_Toolbar:
+        // get toolbar folder
+        PRInt64 bookmarkToolbarFolder;
+        rv = mBookmarksService->GetToolbarFolder(&bookmarkToolbarFolder);
         NS_ENSURE_SUCCESS(rv, rv);
-        // there's no toolbar folder, so make us the toolbar folder
-        rv = mBookmarksService->SetToolbarFolder(ourID);
-        NS_ENSURE_SUCCESS(rv, rv);
-        // set favicon
-        SetFaviconForFolder(ourID, NS_LITERAL_CSTRING(BOOKMARKS_TOOLBAR_ICON_URI));
-      }
-      else {
-        ourID = btf;
-      }
-      break;
-    default:
-      NS_NOTREACHED("Unknown container type");
+        if (!bookmarkToolbarFolder) {
+          // create new folder
+          rv = mBookmarksService->CreateFolder(CurFrame().mContainerID,
+                                              containerName,
+                                              mBookmarksService->DEFAULT_INDEX, &ourID);
+          NS_ENSURE_SUCCESS(rv, rv);
+          // there's no toolbar folder, so make us the toolbar folder
+          rv = mBookmarksService->SetToolbarFolder(ourID);
+          NS_ENSURE_SUCCESS(rv, rv);
+          // set favicon
+          rv = SetFaviconForFolder(ourID, NS_LITERAL_CSTRING(BOOKMARKS_TOOLBAR_ICON_URI));
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
+        else {
+          ourID = bookmarkToolbarFolder;
+        }
+        break;
+      default:
+        NS_NOTREACHED("Unknown container type");
+    }
   }
 #ifdef DEBUG_IMPORT
   PrintNesting();
   printf("Folder %lld \'%s\'", ourID, NS_ConvertUTF16toUTF8(containerName).get());
 #endif
 
   if (updateFolder) {
     // move the menu folder to the current position
@@ -1047,16 +1089,29 @@ BookmarkContentSink::SetFaviconForFolder
 
   nsCOMPtr<nsIURI> faviconURI;
   rv = NS_NewURI(getter_AddRefs(faviconURI), aFavicon);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return faviconService->SetFaviconUrlForPage(folderURI, faviconURI);
 }
 
+// Converts a string id (legacy rdf or contemporary) into an int id
+PRInt64
+BookmarkContentSink::ConvertImportedIdToInternalId(const nsCString& aId) {
+  PRInt64 intId = 0;
+  if (aId.IsEmpty() || nsCRT::strncasecmp("rdf:", aId.get(), 4))
+    return intId;
+  PRInt32 rv;
+  intId = aId.ToInteger(&rv);
+  if (NS_FAILED(rv))
+    intId = 0;
+  return intId;
+}
+
 
 // SyncChannelStatus
 //
 //    If a function returns an error, we need to set the channel status to be
 //    the same, but only if the channel doesn't have its own error. This returns
 //    the error code that should be sent to OnStopRequest.
 
 static nsresult
@@ -1198,16 +1253,17 @@ static const char kPlacesRootAttribute[]
 static const char kBookmarksRootAttribute[] = " BOOKMARKS_MENU=\"true\"";
 static const char kToolbarFolderAttribute[] = " PERSONAL_TOOLBAR_FOLDER=\"true\"";
 static const char kIconAttribute[] = " ICON=\"";
 static const char kIconURIAttribute[] = " ICON_URI=\"";
 static const char kHrefAttribute[] = " HREF=\"";
 static const char kFeedURIAttribute[] = " FEEDURL=\"";
 static const char kWebPanelAttribute[] = " WEB_PANEL=\"true\"";
 static const char kKeywordAttribute[] = " SHORTCUTURL=\"";
+static const char kIdAttribute[] = " ID=\"";
 
 // WriteContainerPrologue
 //
 //    <DL><p>
 //
 //    Goes after the container header (<H3...) but before the contents
 
 static nsresult
@@ -1382,16 +1438,26 @@ nsNavBookmarks::WriteContainerHeader(PRI
   } else if (aFolder == mBookmarksRoot) {
     rv = aOutput->Write(kBookmarksRootAttribute, sizeof(kBookmarksRootAttribute)-1, &dummy);
     if (NS_FAILED(rv)) return rv;
   } else if (aFolder == mToolbarFolder) {
     rv = aOutput->Write(kToolbarFolderAttribute, sizeof(kToolbarFolderAttribute)-1, &dummy);
     if (NS_FAILED(rv)) return rv;
   }
 
+  // id
+  rv = aOutput->Write(kIdAttribute, sizeof(kIdAttribute)-1, &dummy);
+  if (NS_FAILED(rv)) return rv;
+  nsCAutoString id;
+  id.AppendInt(aFolder);
+  rv = aOutput->Write(id.get(), id.Length(), &dummy);
+  if (NS_FAILED(rv)) return rv;
+  rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
+  if (NS_FAILED(rv)) return rv;
+
   // favicon (most folders won't have one)
   nsCOMPtr<nsIURI> folderURI;
   rv = GetFolderURI(aFolder, getter_AddRefs(folderURI));
   NS_ENSURE_SUCCESS(rv, rv);
   nsCAutoString folderSpec;
   rv = folderURI->GetSpec(folderSpec);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = WriteFaviconAttribute(folderSpec, aOutput);
@@ -1471,16 +1537,26 @@ nsNavBookmarks::WriteItem(nsNavHistoryRe
   rv = WriteFaviconAttribute(uri, aOutput);
   if (NS_FAILED(rv)) return rv;
 
   // get bookmark id 
   PRInt64 bookmarkId;
   rv = aItem->GetBookmarkId(&bookmarkId);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // write id
+  rv = aOutput->Write(kIdAttribute, sizeof(kIdAttribute)-1, &dummy);
+  if (NS_FAILED(rv)) return rv;
+  nsCAutoString id;
+  id.AppendInt(bookmarkId);
+  rv = aOutput->Write(id.get(), id.Length(), &dummy);
+  if (NS_FAILED(rv)) return rv;
+  rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
+  if (NS_FAILED(rv)) return rv;
+
   // keyword (shortcuturl)
   nsAutoString keyword;
   rv = GetKeywordForBookmark(bookmarkId, keyword);
   if (NS_FAILED(rv)) return rv;
   if (!keyword.IsEmpty()) {
     rv = aOutput->Write(kKeywordAttribute, sizeof(kKeywordAttribute)-1, &dummy);
     if (NS_FAILED(rv)) return rv;
     char* escapedKeyword = nsEscapeHTML(NS_ConvertUTF16toUTF8(keyword).get());
@@ -1585,16 +1661,26 @@ nsNavBookmarks::WriteLivemark(PRInt64 aF
     rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy);
     if (NS_FAILED(rv)) return rv;
     rv = WriteEscapedUrl(siteSpec, aOutput);
     if (NS_FAILED(rv)) return rv;
     rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
     if (NS_FAILED(rv)) return rv;
   }
 
+  // write id
+  rv = aOutput->Write(kIdAttribute, sizeof(kIdAttribute)-1, &dummy);
+  if (NS_FAILED(rv)) return rv;
+  nsCAutoString id;
+  id.AppendInt(aFolderId);
+  rv = aOutput->Write(id.get(), id.Length(), &dummy);
+  if (NS_FAILED(rv)) return rv;
+  rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
+  if (NS_FAILED(rv)) return rv;
+
   // '>'
   rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy);
   if (NS_FAILED(rv)) return rv;
 
   // title
   rv = WriteContainerTitle(aFolderId, aOutput);
   if (NS_FAILED(rv)) return rv;