Bug 1095506 - Serialize the error object when saving to the store. r=paolo
☠☠ backed out by 6808270d3dae ☠ ☠
authorSteven MacLeod <smacleod@mozilla.com>
Fri, 07 Nov 2014 10:00:24 -0500
changeset 226486 da9faa259da1105ad9ea691c2bffc33286426b1a
parent 226485 e908c7528d69a4c06a7edc9a44b45debbb483a1b
child 226487 7bfa96b421ea71013044d238940e3438f2366df6
push id53
push userdglastonbury@mozilla.com
push dateWed, 12 Nov 2014 02:04:58 +0000
reviewerspaolo
bugs1095506
milestone36.0a1
Bug 1095506 - Serialize the error object when saving to the store. r=paolo We now serialize the Download.error property when saving to the store so that data about error conditions will survive a restart. This is important for supporting blocked downloads with partial data that could be unblocked.
toolkit/components/jsdownloads/src/DownloadCore.jsm
--- a/toolkit/components/jsdownloads/src/DownloadCore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm
@@ -472,16 +472,26 @@ this.Download.prototype = {
         // to start.
         if (this._blockedByParentalControls) {
           ex = new DownloadError({ becauseBlockedByParentalControls: true });
         }
 
         // Update the download error, unless a new attempt already started. The
         // change in the status property is notified in the finally block.
         if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
+          if (!(ex instanceof DownloadError)) {
+            let properties = {innerException: ex};
+
+            if (ex.message) {
+              properties.message = ex.message;
+            }
+
+            ex = new DownloadError(properties);
+          }
+
           this.error = ex;
         }
         throw ex;
       } finally {
         // Any cancellation request has now been processed.
         this._saverExecuting = false;
         this._promiseCanceled = null;
 
@@ -905,27 +915,27 @@ this.Download.prototype = {
     // is an object instead of a simple string, we can't simplify it because we
     // need to persist all its properties, not only "type".  This may happen for
     // savers of type "copy" as well as other types.
     let saver = this.saver.toSerializable();
     if (saver !== "copy") {
       serializable.saver = saver;
     }
 
-    if (this.error && ("message" in this.error)) {
-      serializable.error = { message: this.error.message };
+    if (this.error) {
+      serializable.errorObj = this.error.toSerializable();
     }
 
     if (this.startTime) {
       serializable.startTime = this.startTime.toJSON();
     }
 
     // These are serialized unless they are false, null, or empty strings.
     for (let property of kSerializableDownloadProperties) {
-      if (property != "error" && property != "startTime" && this[property]) {
+      if (property != "startTime" && this[property]) {
         serializable[property] = this[property];
       }
     }
 
     serializeUnknownProperties(this, serializable);
 
     return serializable;
   },
@@ -951,17 +961,16 @@ this.Download.prototype = {
 };
 
 /**
  * Defines which properties of the Download object are serializable.
  */
 const kSerializableDownloadProperties = [
   "succeeded",
   "canceled",
-  "error",
   "totalBytes",
   "hasPartialData",
   "tryToKeepPartialData",
   "launcherPath",
   "launchWhenSucceeded",
   "contentType",
 ];
 
@@ -1006,27 +1015,32 @@ Download.fromSerializable = function (aS
 
   if ("startTime" in aSerializable) {
     let time = aSerializable.startTime.getTime
              ? aSerializable.startTime.getTime()
              : aSerializable.startTime;
     download.startTime = new Date(time);
   }
 
+  if ("errorObj" in aSerializable) {
+    download.error = DownloadError.fromSerializable(aSerializable.errorObj);
+  }
+
   for (let property of kSerializableDownloadProperties) {
     if (property in aSerializable) {
       download[property] = aSerializable[property];
     }
   }
 
   deserializeUnknownProperties(download, aSerializable, property =>
     kSerializableDownloadProperties.indexOf(property) == -1 &&
     property != "startTime" &&
     property != "source" &&
     property != "target" &&
+    property != "error" &&
     property != "saver");
 
   return download;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadSource
 
@@ -1258,16 +1272,20 @@ this.DownloadError = function (aProperti
     this.becauseBlockedByParentalControls = true;
   } else if (aProperties.becauseBlockedByReputationCheck) {
     this.becauseBlocked = true;
     this.becauseBlockedByReputationCheck = true;
   } else if (aProperties.becauseBlocked) {
     this.becauseBlocked = true;
   }
 
+  if (aProperties.innerException) {
+    this.innerException = aProperties.innerException;
+  }
+
   this.stack = new Error().stack;
 }
 
 this.DownloadError.prototype = {
   __proto__: Error.prototype,
 
   /**
    * The result code associated with this error.
@@ -1296,16 +1314,66 @@ this.DownloadError.prototype = {
    */
   becauseBlockedByParentalControls: false,
 
   /**
    * Indicates the download was blocked because it failed the reputation check
    * and may be malware.
    */
   becauseBlockedByReputationCheck: false,
+
+  /**
+   * If this DownloadError was caused by an exception this property will
+   * contain the original exception. This will not be serialized when saving
+   * to the store.
+   */
+  innerException: null,
+
+  /**
+   * Returns a static representation of the current object state.
+   *
+   * @return A JavaScript object that can be serialized to JSON.
+   */
+  toSerializable: function ()
+  {
+    let serializable = {
+      result: this.result,
+      message: this.message,
+      becauseSourceFailed: this.becauseSourceFailed,
+      becauseTargetFailed: this.becauseTargetFailed,
+      becauseBlocked: this.becauseBlocked,
+      becauseBlockedByParentalControls: this.becauseBlockedByParentalControls,
+      becauseBlockedByReputationCheck: this.becauseBlockedByReputationCheck,
+    };
+
+    serializeUnknownProperties(this, serializable);
+    return serializable;
+  },
+};
+
+/**
+ * Creates a new DownloadError object from its serializable representation.
+ *
+ * @param aSerializable
+ *        Serializable representation of a DownloadError object.
+ *
+ * @return The newly created DownloadError object.
+ */
+this.DownloadError.fromSerializable = function (aSerializable) {
+  let e = new DownloadError(aSerializable);
+  deserializeUnknownProperties(e, aSerializable, property =>
+    property != "result" &&
+    property != "message" &&
+    property != "becauseSourceFailed" &&
+    property != "becauseTargetFailed" &&
+    property != "becauseBlocked" &&
+    property != "becauseBlockedByParentalControls" &&
+    property != "becauseBlockedByReputationCheck");
+
+  return e;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadSaver
 
 /**
  * Template for an object that actually transfers the data for the download.
  */