Bug 505656 - Bookmarks backup JSON generation should 1) generate valid JSON and 2) handle incremental failures better (omitting information, rather than potentially serializing bad information). r=mak
authorJeff Walden <jwalden@mit.edu>
Mon, 10 May 2010 16:11:03 -0700
changeset 43488 f21132993dc239f3e86512a5f7b8b3742647ddae
parent 43487 ba0db81cc412be0182d83da4bf76f13ebe976223
child 43489 9ec599d9a1f0c6530cecbcfb6cd2bd924d1bcbb3
push idunknown
push userunknown
push dateunknown
reviewersmak
bugs505656
milestone1.9.3a6pre
Bug 505656 - Bookmarks backup JSON generation should 1) generate valid JSON and 2) handle incremental failures better (omitting information, rather than potentially serializing bad information). r=mak
browser/components/places/tests/unit/bookmarks.glue.json
toolkit/components/places/src/PlacesUtils.jsm
--- a/browser/components/places/tests/unit/bookmarks.glue.json
+++ b/browser/components/places/tests/unit/bookmarks.glue.json
@@ -1,1 +1,1 @@
-{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Unsorted Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]},]}
\ No newline at end of file
+{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Unsorted Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]}]}
\ No newline at end of file
--- a/toolkit/components/places/src/PlacesUtils.jsm
+++ b/toolkit/components/places/src/PlacesUtils.jsm
@@ -1503,61 +1503,45 @@ var PlacesUtils = {
       }
       else {
         // This is a grouped container query, generated on the fly.
         aJSNode.type = PlacesUtils.TYPE_X_MOZ_PLACE;
         aJSNode.uri = aPlacesNode.uri;
       }
     }
 
-    function writeScalarNode(aStream, aNode) {
-      // serialize to json
-      var jstr = PlacesUtils.toJSONString(aNode);
-      // write to stream
-      aStream.write(jstr, jstr.length);
-    }
+    function appendConvertedComplexNode(aNode, aSourceNode, aArray) {
+      var repr = {};
 
-    function writeComplexNode(aStream, aNode, aSourceNode) {
-      var escJSONStringRegExp = /(["\\])/g;
-      // write prefix
-      var properties = [];
-      for (let [name, value] in Iterator(aNode)) {
-        if (name == "annos")
-          value = PlacesUtils.toJSONString(value);
-        else if (typeof value == "string")
-          value = "\"" + value.replace(escJSONStringRegExp, '\\$1') + "\"";
-        properties.push("\"" + name.replace(escJSONStringRegExp, '\\$1') + "\":" + value);
-      }
-      var jStr = "{" + properties.join(",") + ",\"children\":[";
-      aStream.write(jStr, jStr.length);
+      for (let [name, value] in Iterator(aNode))
+        repr[name] = value;
 
       // write child nodes
+      var children = repr.children = [];
       if (!aNode.livemark) {
         asContainer(aSourceNode);
         var wasOpen = aSourceNode.containerOpen;
         if (!wasOpen)
           aSourceNode.containerOpen = true;
         var cc = aSourceNode.childCount;
         for (var i = 0; i < cc; ++i) {
           var childNode = aSourceNode.getChild(i);
           if (aExcludeItems && aExcludeItems.indexOf(childNode.itemId) != -1)
             continue;
-          var written = serializeNodeToJSONStream(aSourceNode.getChild(i), i);
-          if (written && i < cc - 1)
-            aStream.write(",", 1);
+          appendConvertedNode(aSourceNode.getChild(i), i, children);
         }
         if (!wasOpen)
           aSourceNode.containerOpen = false;
       }
 
-      // write suffix
-      aStream.write("]}", 2);
+      aArray.push(repr);
+      return true;
     }
 
-    function serializeNodeToJSONStream(bNode, aIndex) {
+    function appendConvertedNode(bNode, aIndex, aArray) {
       var node = {};
 
       // set index in order received
       // XXX handy shortcut, but are there cases where we don't want
       // to export using the sorting provided by the query?
       if (aIndex)
         node.index = aIndex;
 
@@ -1565,51 +1549,61 @@ var PlacesUtils = {
 
       var parent = bNode.parent;
       var grandParent = parent ? parent.parent : null;
 
       if (PlacesUtils.nodeIsURI(bNode)) {
         // Tag root accept only folder nodes
         if (parent && parent.itemId == PlacesUtils.tagsFolderId)
           return false;
+
         // Check for url validity, since we can't halt while writing a backup.
         // This will throw if we try to serialize an invalid url and it does
         // not make sense saving a wrong or corrupt uri node.
         try {
           PlacesUtils._uri(bNode.uri);
         } catch (ex) {
           return false;
         }
+
         addURIProperties(bNode, node);
       }
       else if (PlacesUtils.nodeIsContainer(bNode)) {
         // Tag containers accept only uri nodes
         if (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId)
           return false;
+
         addContainerProperties(bNode, node);
       }
       else if (PlacesUtils.nodeIsSeparator(bNode)) {
         // Tag root accept only folder nodes
         // Tag containers accept only uri nodes
         if ((parent && parent.itemId == PlacesUtils.tagsFolderId) ||
             (grandParent && grandParent.itemId == PlacesUtils.tagsFolderId))
           return false;
 
         addSeparatorProperties(bNode, node);
       }
 
       if (!node.feedURI && node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
-        writeComplexNode(aStream, node, bNode);
-      else
-        writeScalarNode(aStream, node);
+        return appendConvertedComplexNode(node, bNode, aArray);
+
+      aArray.push(node);
       return true;
     }
 
     // serialize to stream
-    serializeNodeToJSONStream(aNode, null);
+    var array = [];
+    if (appendConvertedNode(aNode, null, array)) {
+      var json = JSON.stringify(array[0]);
+      aStream.write(json, json.length);
+    }
+    else {
+      throw Cr.NS_ERROR_UNEXPECTED;
+    }
   },
 
   /**
    * Serialize a JS object to JSON
    */
   toJSONString: function PU_toJSONString(aObj) {
     var JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
     return JSON.encode(aObj);