Bug 1370752: Part 2 - Allow fallback serializer when JSON.serialize fails. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 09 Jun 2017 18:19:11 -0700
changeset 602286 48afd290cd835345e097b093cc06c426d98de60c
parent 602264 b686ac9b005edffa54537eb9798ded33f3b8faae
child 602287 461d75980295b5ecc1810f30c6b01de9bb0d8c48
push id66352
push usermaglione.k@gmail.com
push dateThu, 29 Jun 2017 21:19:06 +0000
reviewersaswan
bugs1370752
milestone56.0a1
Bug 1370752: Part 2 - Allow fallback serializer when JSON.serialize fails. r?aswan Currently, we need to be able to handle serializing non-JSON-compatible objects without catastrophically failing to save the storage file. Ideally, we would ensure this in the ordinary toJSON method. However, that would require a unnecessary extra calls to JSON.stringify for each object that needs to be sanitized before returning a JSON-safe value, which is more expensive than we can afford. The fallback toJSONSafe method allows us to do this only when necessary, due to an initial failed JSON serialization. MozReview-Commit-ID: JXQ001dOGtW
toolkit/modules/JSONFile.jsm
--- a/toolkit/modules/JSONFile.jsm
+++ b/toolkit/modules/JSONFile.jsm
@@ -281,18 +281,30 @@ JSONFile.prototype = {
    *
    * If an error occurs, the previous file is not deleted.
    *
    * @return {Promise}
    * @resolves When the operation finished successfully.
    * @rejects JavaScript exception.
    */
   async _save() {
+    let json;
+    try {
+      json = JSON.stringify(this._data);
+    } catch (e) {
+      // If serialization fails, try fallback safe JSON converter.
+      if (typeof this._data.toJSONSafe == "function") {
+        json = JSON.stringify(this._data.toJSONSafe());
+      } else {
+        throw e;
+      }
+    }
+
     // Create or overwrite the file.
-    let bytes = gTextEncoder.encode(JSON.stringify(this._data));
+    let bytes = gTextEncoder.encode(json);
     if (this._beforeSave) {
       await Promise.resolve(this._beforeSave());
     }
     await OS.File.writeAtomic(this.path, bytes,
                               Object.assign(
                                 { tmpPath: this.path + ".tmp" },
                                 this._options));
   },