toolkit/components/jsdownloads/src/DownloadCore.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /**
     8  * This file includes the following constructors and global objects:
     9  *
    10  * Download
    11  * Represents a single download, with associated state and actions.  This object
    12  * is transient, though it can be included in a DownloadList so that it can be
    13  * managed by the user interface and persisted across sessions.
    14  *
    15  * DownloadSource
    16  * Represents the source of a download, for example a document or an URI.
    17  *
    18  * DownloadTarget
    19  * Represents the target of a download, for example a file in the global
    20  * downloads directory, or a file in the system temporary directory.
    21  *
    22  * DownloadError
    23  * Provides detailed information about a download failure.
    24  *
    25  * DownloadSaver
    26  * Template for an object that actually transfers the data for the download.
    27  *
    28  * DownloadCopySaver
    29  * Saver object that simply copies the entire source file to the target.
    30  *
    31  * DownloadLegacySaver
    32  * Saver object that integrates with the legacy nsITransfer interface.
    33  */
    35 "use strict";
    37 this.EXPORTED_SYMBOLS = [
    38   "Download",
    39   "DownloadSource",
    40   "DownloadTarget",
    41   "DownloadError",
    42   "DownloadSaver",
    43   "DownloadCopySaver",
    44   "DownloadLegacySaver",
    45 ];
    47 ////////////////////////////////////////////////////////////////////////////////
    48 //// Globals
    50 const Cc = Components.classes;
    51 const Ci = Components.interfaces;
    52 const Cu = Components.utils;
    53 const Cr = Components.results;
    55 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    57 XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
    58                                   "resource://gre/modules/DownloadIntegration.jsm");
    59 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    60                                   "resource://gre/modules/FileUtils.jsm");
    61 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    62                                   "resource://gre/modules/NetUtil.jsm");
    63 XPCOMUtils.defineLazyModuleGetter(this, "OS",
    64                                   "resource://gre/modules/osfile.jsm")
    65 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    66                                   "resource://gre/modules/Promise.jsm");
    67 XPCOMUtils.defineLazyModuleGetter(this, "Services",
    68                                   "resource://gre/modules/Services.jsm");
    69 XPCOMUtils.defineLazyModuleGetter(this, "Task",
    70                                   "resource://gre/modules/Task.jsm");
    72 XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
    73            "@mozilla.org/browser/download-history;1",
    74            Ci.nsIDownloadHistory);
    75 XPCOMUtils.defineLazyServiceGetter(this, "gExternalAppLauncher",
    76            "@mozilla.org/uriloader/external-helper-app-service;1",
    77            Ci.nsPIExternalAppLauncher);
    78 XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
    79            "@mozilla.org/uriloader/external-helper-app-service;1",
    80            Ci.nsIExternalHelperAppService);
    82 const BackgroundFileSaverStreamListener = Components.Constructor(
    83       "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
    84       "nsIBackgroundFileSaver");
    86 /**
    87  * Returns true if the given value is a primitive string or a String object.
    88  */
    89 function isString(aValue) {
    90   // We cannot use the "instanceof" operator reliably across module boundaries.
    91   return (typeof aValue == "string") ||
    92          (typeof aValue == "object" && "charAt" in aValue);
    93 }
    95 /**
    96  * Serialize the unknown properties of aObject into aSerializable.
    97  */
    98 function serializeUnknownProperties(aObject, aSerializable)
    99 {
   100   if (aObject._unknownProperties) {
   101     for (let property in aObject._unknownProperties) {
   102       aSerializable[property] = aObject._unknownProperties[property];
   103     }
   104   }
   105 }
   107 /**
   108  * Check for any unknown properties in aSerializable and preserve those in the
   109  * _unknownProperties field of aObject. aFilterFn is called for each property
   110  * name of aObject and should return true only for unknown properties.
   111  */
   112 function deserializeUnknownProperties(aObject, aSerializable, aFilterFn)
   113 {
   114   for (let property in aSerializable) {
   115     if (aFilterFn(property)) {
   116       if (!aObject._unknownProperties) {
   117         aObject._unknownProperties = { };
   118       }
   120       aObject._unknownProperties[property] = aSerializable[property];
   121     }
   122   }
   123 }
   125 /**
   126  * This determines the minimum time interval between updates to the number of
   127  * bytes transferred, and is a limiting factor to the sequence of readings used
   128  * in calculating the speed of the download.
   129  */
   130 const kProgressUpdateIntervalMs = 400;
   132 ////////////////////////////////////////////////////////////////////////////////
   133 //// Download
   135 /**
   136  * Represents a single download, with associated state and actions.  This object
   137  * is transient, though it can be included in a DownloadList so that it can be
   138  * managed by the user interface and persisted across sessions.
   139  */
   140 this.Download = function ()
   141 {
   142   this._deferSucceeded = Promise.defer();
   143 }
   145 this.Download.prototype = {
   146   /**
   147    * DownloadSource object associated with this download.
   148    */
   149   source: null,
   151   /**
   152    * DownloadTarget object associated with this download.
   153    */
   154   target: null,
   156   /**
   157    * DownloadSaver object associated with this download.
   158    */
   159   saver: null,
   161   /**
   162    * Indicates that the download never started, has been completed successfully,
   163    * failed, or has been canceled.  This property becomes false when a download
   164    * is started for the first time, or when a failed or canceled download is
   165    * restarted.
   166    */
   167   stopped: true,
   169   /**
   170    * Indicates that the download has been completed successfully.
   171    */
   172   succeeded: false,
   174   /**
   175    * Indicates that the download has been canceled.  This property can become
   176    * true, then it can be reset to false when a canceled download is restarted.
   177    *
   178    * This property becomes true as soon as the "cancel" method is called, though
   179    * the "stopped" property might remain false until the cancellation request
   180    * has been processed.  Temporary files or part files may still exist even if
   181    * they are expected to be deleted, until the "stopped" property becomes true.
   182    */
   183   canceled: false,
   185   /**
   186    * When the download fails, this is set to a DownloadError instance indicating
   187    * the cause of the failure.  If the download has been completed successfully
   188    * or has been canceled, this property is null.  This property is reset to
   189    * null when a failed download is restarted.
   190    */
   191   error: null,
   193   /**
   194    * Indicates the start time of the download.  When the download starts,
   195    * this property is set to a valid Date object.  The default value is null
   196    * before the download starts.
   197    */
   198   startTime: null,
   200   /**
   201    * Indicates whether this download's "progress" property is able to report
   202    * partial progress while the download proceeds, and whether the value in
   203    * totalBytes is relevant.  This depends on the saver and the download source.
   204    */
   205   hasProgress: false,
   207   /**
   208    * Progress percent, from 0 to 100.  Intermediate values are reported only if
   209    * hasProgress is true.
   210    *
   211    * @note You shouldn't rely on this property being equal to 100 to determine
   212    *       whether the download is completed.  You should use the individual
   213    *       state properties instead.
   214    */
   215   progress: 0,
   217   /**
   218    * When hasProgress is true, indicates the total number of bytes to be
   219    * transferred before the download finishes, that can be zero for empty files.
   220    *
   221    * When hasProgress is false, this property is always zero.
   222    */
   223   totalBytes: 0,
   225   /**
   226    * Number of bytes currently transferred.  This value starts at zero, and may
   227    * be updated regardless of the value of hasProgress.
   228    *
   229    * @note You shouldn't rely on this property being equal to totalBytes to
   230    *       determine whether the download is completed.  You should use the
   231    *       individual state properties instead.
   232    */
   233   currentBytes: 0,
   235   /**
   236    * Fractional number representing the speed of the download, in bytes per
   237    * second.  This value is zero when the download is stopped, and may be
   238    * updated regardless of the value of hasProgress.
   239    */
   240   speed: 0,
   242   /**
   243    * Indicates whether, at this time, there is any partially downloaded data
   244    * that can be used when restarting a failed or canceled download.
   245    *
   246    * This property is relevant while the download is in progress, and also if it
   247    * failed or has been canceled.  If the download has been completed
   248    * successfully, this property is always false.
   249    *
   250    * Whether partial data can actually be retained depends on the saver and the
   251    * download source, and may not be known before the download is started.
   252    */
   253   hasPartialData: false,
   255   /**
   256    * This can be set to a function that is called after other properties change.
   257    */
   258   onchange: null,
   260   /**
   261    * This tells if the user has chosen to open/run the downloaded file after
   262    * download has completed.
   263    */
   264   launchWhenSucceeded: false,
   266   /**
   267    * This represents the MIME type of the download.
   268    */
   269   contentType: null,
   271   /**
   272    * This indicates the path of the application to be used to launch the file,
   273    * or null if the file should be launched with the default application.
   274    */
   275   launcherPath: null,
   277   /**
   278    * Raises the onchange notification.
   279    */
   280   _notifyChange: function D_notifyChange() {
   281     try {
   282       if (this.onchange) {
   283         this.onchange();
   284       }
   285     } catch (ex) {
   286       Cu.reportError(ex);
   287     }
   288   },
   290   /**
   291    * The download may be stopped and restarted multiple times before it
   292    * completes successfully. This may happen if any of the download attempts is
   293    * canceled or fails.
   294    *
   295    * This property contains a promise that is linked to the current attempt, or
   296    * null if the download is either stopped or in the process of being canceled.
   297    * If the download restarts, this property is replaced with a new promise.
   298    *
   299    * The promise is resolved if the attempt it represents finishes successfully,
   300    * and rejected if the attempt fails.
   301    */
   302   _currentAttempt: null,
   304   /**
   305    * Starts the download for the first time, or restarts a download that failed
   306    * or has been canceled.
   307    *
   308    * Calling this method when the download has been completed successfully has
   309    * no effect, and the method returns a resolved promise.  If the download is
   310    * in progress, the method returns the same promise as the previous call.
   311    *
   312    * If the "cancel" method was called but the cancellation process has not
   313    * finished yet, this method waits for the cancellation to finish, then
   314    * restarts the download immediately.
   315    *
   316    * @note If you need to start a new download from the same source, rather than
   317    *       restarting a failed or canceled one, you should create a separate
   318    *       Download object with the same source as the current one.
   319    *
   320    * @return {Promise}
   321    * @resolves When the download has finished successfully.
   322    * @rejects JavaScript exception if the download failed.
   323    */
   324   start: function D_start()
   325   {
   326     // If the download succeeded, it's the final state, we have nothing to do.
   327     if (this.succeeded) {
   328       return Promise.resolve();
   329     }
   331     // If the download already started and hasn't failed or hasn't been
   332     // canceled, return the same promise as the previous call, allowing the
   333     // caller to wait for the current attempt to finish.
   334     if (this._currentAttempt) {
   335       return this._currentAttempt;
   336     }
   338     // While shutting down or disposing of this object, we prevent the download
   339     // from returning to be in progress.
   340     if (this._finalized) {
   341       return Promise.reject(new DownloadError({
   342                                 message: "Cannot start after finalization."}));
   343     }
   345     // Initialize all the status properties for a new or restarted download.
   346     this.stopped = false;
   347     this.canceled = false;
   348     this.error = null;
   349     this.hasProgress = false;
   350     this.progress = 0;
   351     this.totalBytes = 0;
   352     this.currentBytes = 0;
   353     this.startTime = new Date();
   355     // Create a new deferred object and an associated promise before starting
   356     // the actual download.  We store it on the download as the current attempt.
   357     let deferAttempt = Promise.defer();
   358     let currentAttempt = deferAttempt.promise;
   359     this._currentAttempt = currentAttempt;
   361     // Restart the progress and speed calculations from scratch.
   362     this._lastProgressTimeMs = 0;
   364     // This function propagates progress from the DownloadSaver object, unless
   365     // it comes in late from a download attempt that was replaced by a new one.
   366     // If the cancellation process for the download has started, then the update
   367     // is ignored.
   368     function DS_setProgressBytes(aCurrentBytes, aTotalBytes, aHasPartialData)
   369     {
   370       if (this._currentAttempt == currentAttempt) {
   371         this._setBytes(aCurrentBytes, aTotalBytes, aHasPartialData);
   372       }
   373     }
   375     // This function propagates download properties from the DownloadSaver
   376     // object, unless it comes in late from a download attempt that was
   377     // replaced by a new one.  If the cancellation process for the download has
   378     // started, then the update is ignored.
   379     function DS_setProperties(aOptions)
   380     {
   381       if (this._currentAttempt != currentAttempt) {
   382         return;
   383       }
   385       let changeMade = false;
   387       if ("contentType" in aOptions &&
   388           this.contentType != aOptions.contentType) {
   389         this.contentType = aOptions.contentType;
   390         changeMade = true;
   391       }
   393       if (changeMade) {
   394         this._notifyChange();
   395       }
   396     }
   398     // Now that we stored the promise in the download object, we can start the
   399     // task that will actually execute the download.
   400     deferAttempt.resolve(Task.spawn(function task_D_start() {
   401       // Wait upon any pending operation before restarting.
   402       if (this._promiseCanceled) {
   403         yield this._promiseCanceled;
   404       }
   405       if (this._promiseRemovePartialData) {
   406         try {
   407           yield this._promiseRemovePartialData;
   408         } catch (ex) {
   409           // Ignore any errors, which are already reported by the original
   410           // caller of the removePartialData method.
   411         }
   412       }
   414       // In case the download was restarted while cancellation was in progress,
   415       // but the previous attempt actually succeeded before cancellation could
   416       // be processed, it is possible that the download has already finished.
   417       if (this.succeeded) {
   418         return;
   419       }
   421       try {
   422         // Disallow download if parental controls service restricts it.
   423         if (yield DownloadIntegration.shouldBlockForParentalControls(this)) {
   424           throw new DownloadError({ becauseBlockedByParentalControls: true });
   425         }
   427         // We should check if we have been canceled in the meantime, after all
   428         // the previous asynchronous operations have been executed and just
   429         // before we call the "execute" method of the saver.
   430         if (this._promiseCanceled) {
   431           // The exception will become a cancellation in the "catch" block.
   432           throw undefined;
   433         }
   435         // Execute the actual download through the saver object.
   436         this._saverExecuting = true;
   437         yield this.saver.execute(DS_setProgressBytes.bind(this),
   438                                  DS_setProperties.bind(this));
   440         // Check for application reputation, which requires the entire file to
   441         // be downloaded.  After that, check for the last time if the download
   442         // has been canceled.  Both cases require the target file to be deleted,
   443         // thus we process both in the same block of code.
   444         if ((yield DownloadIntegration.shouldBlockForReputationCheck(this)) ||
   445             this._promiseCanceled) {
   446           try {
   447             yield OS.File.remove(this.target.path);
   448           } catch (ex) {
   449             Cu.reportError(ex);
   450           }
   451           // If this is actually a cancellation, this exception will be changed
   452           // in the catch block below.
   453           throw new DownloadError({ becauseBlockedByReputationCheck: true });
   454         }
   456         // Update the status properties for a successful download.
   457         this.progress = 100;
   458         this.succeeded = true;
   459         this.hasPartialData = false;
   460       } catch (ex) {
   461         // Fail with a generic status code on cancellation, so that the caller
   462         // is forced to actually check the status properties to see if the
   463         // download was canceled or failed because of other reasons.
   464         if (this._promiseCanceled) {
   465           throw new DownloadError({ message: "Download canceled." });
   466         }
   468         // An HTTP 450 error code is used by Windows to indicate that a uri is
   469         // blocked by parental controls. This will prevent the download from
   470         // occuring, so an error needs to be raised. This is not performed
   471         // during the parental controls check above as it requires the request
   472         // to start.
   473         if (this._blockedByParentalControls) {
   474           ex = new DownloadError({ becauseBlockedByParentalControls: true });
   475         }
   477         // Update the download error, unless a new attempt already started. The
   478         // change in the status property is notified in the finally block.
   479         if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
   480           this.error = ex;
   481         }
   482         throw ex;
   483       } finally {
   484         // Any cancellation request has now been processed.
   485         this._saverExecuting = false;
   486         this._promiseCanceled = null;
   488         // Update the status properties, unless a new attempt already started.
   489         if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
   490           this._currentAttempt = null;
   491           this.stopped = true;
   492           this.speed = 0;
   493           this._notifyChange();
   494           if (this.succeeded) {
   495             yield DownloadIntegration.downloadDone(this);
   497             this._deferSucceeded.resolve();
   499             if (this.launchWhenSucceeded) {
   500               this.launch().then(null, Cu.reportError);
   502               // Always schedule files to be deleted at the end of the private browsing
   503               // mode, regardless of the value of the pref.
   504               if (this.source.isPrivate) {
   505                 gExternalAppLauncher.deleteTemporaryPrivateFileWhenPossible(
   506                                      new FileUtils.File(this.target.path));
   507               } else if (Services.prefs.getBoolPref(
   508                           "browser.helperApps.deleteTempFileOnExit")) {
   509                 gExternalAppLauncher.deleteTemporaryFileOnExit(
   510                                      new FileUtils.File(this.target.path));
   511               }
   512             }
   513           }
   514         }
   515       }
   516     }.bind(this)));
   518     // Notify the new download state before returning.
   519     this._notifyChange();
   520     return currentAttempt;
   521   },
   523   /*
   524    * Launches the file after download has completed. This can open
   525    * the file with the default application for the target MIME type
   526    * or file extension, or with a custom application if launcherPath
   527    * is set.
   528    *
   529    * @return {Promise}
   530    * @resolves When the instruction to launch the file has been
   531    *           successfully given to the operating system. Note that
   532    *           the OS might still take a while until the file is actually
   533    *           launched.
   534    * @rejects  JavaScript exception if there was an error trying to launch
   535    *           the file.
   536    */
   537   launch: function() {
   538     if (!this.succeeded) {
   539       return Promise.reject(
   540         new Error("launch can only be called if the download succeeded")
   541       );
   542     }
   544     return DownloadIntegration.launchDownload(this);
   545   },
   547   /*
   548    * Shows the folder containing the target file, or where the target file
   549    * will be saved. This may be called at any time, even if the download
   550    * failed or is currently in progress.
   551    *
   552    * @return {Promise}
   553    * @resolves When the instruction to open the containing folder has been
   554    *           successfully given to the operating system. Note that
   555    *           the OS might still take a while until the folder is actually
   556    *           opened.
   557    * @rejects  JavaScript exception if there was an error trying to open
   558    *           the containing folder.
   559    */
   560   showContainingDirectory: function D_showContainingDirectory() {
   561     return DownloadIntegration.showContainingDirectory(this.target.path);
   562   },
   564   /**
   565    * When a request to cancel the download is received, contains a promise that
   566    * will be resolved when the cancellation request is processed.  When the
   567    * request is processed, this property becomes null again.
   568    */
   569   _promiseCanceled: null,
   571   /**
   572    * True between the call to the "execute" method of the saver and the
   573    * completion of the current download attempt.
   574    */
   575   _saverExecuting: false,
   577   /**
   578    * Cancels the download.
   579    *
   580    * The cancellation request is asynchronous.  Until the cancellation process
   581    * finishes, temporary files or part files may still exist even if they are
   582    * expected to be deleted.
   583    *
   584    * In case the download completes successfully before the cancellation request
   585    * could be processed, this method has no effect, and it returns a resolved
   586    * promise.  You should check the properties of the download at the time the
   587    * returned promise is resolved to determine if the download was cancelled.
   588    *
   589    * Calling this method when the download has been completed successfully,
   590    * failed, or has been canceled has no effect, and the method returns a
   591    * resolved promise.  This behavior is designed for the case where the call
   592    * to "cancel" happens asynchronously, and is consistent with the case where
   593    * the cancellation request could not be processed in time.
   594    *
   595    * @return {Promise}
   596    * @resolves When the cancellation process has finished.
   597    * @rejects Never.
   598    */
   599   cancel: function D_cancel()
   600   {
   601     // If the download is currently stopped, we have nothing to do.
   602     if (this.stopped) {
   603       return Promise.resolve();
   604     }
   606     if (!this._promiseCanceled) {
   607       // Start a new cancellation request.
   608       let deferCanceled = Promise.defer();
   609       this._currentAttempt.then(function () deferCanceled.resolve(),
   610                                 function () deferCanceled.resolve());
   611       this._promiseCanceled = deferCanceled.promise;
   613       // The download can already be restarted.
   614       this._currentAttempt = null;
   616       // Notify that the cancellation request was received.
   617       this.canceled = true;
   618       this._notifyChange();
   620       // Execute the actual cancellation through the saver object, in case it
   621       // has already started.  Otherwise, the cancellation will be handled just
   622       // before the saver is started.
   623       if (this._saverExecuting) {
   624         this.saver.cancel();
   625       }
   626     }
   628     return this._promiseCanceled;
   629   },
   631   /**
   632    * Indicates whether any partially downloaded data should be retained, to use
   633    * when restarting a failed or canceled download.  The default is false.
   634    *
   635    * Whether partial data can actually be retained depends on the saver and the
   636    * download source, and may not be known before the download is started.
   637    *
   638    * To have any effect, this property must be set before starting the download.
   639    * Resetting this property to false after the download has already started
   640    * will not remove any partial data.
   641    *
   642    * If this property is set to true, care should be taken that partial data is
   643    * removed before the reference to the download is discarded.  This can be
   644    * done using the removePartialData or the "finalize" methods.
   645    */
   646   tryToKeepPartialData: false,
   648   /**
   649    * When a request to remove partially downloaded data is received, contains a
   650    * promise that will be resolved when the removal request is processed.  When
   651    * the request is processed, this property becomes null again.
   652    */
   653   _promiseRemovePartialData: null,
   655   /**
   656    * Removes any partial data kept as part of a canceled or failed download.
   657    *
   658    * If the download is not canceled or failed, this method has no effect, and
   659    * it returns a resolved promise.  If the "cancel" method was called but the
   660    * cancellation process has not finished yet, this method waits for the
   661    * cancellation to finish, then removes the partial data.
   662    *
   663    * After this method has been called, if the tryToKeepPartialData property is
   664    * still true when the download is restarted, partial data will be retained
   665    * during the new download attempt.
   666    *
   667    * @return {Promise}
   668    * @resolves When the partial data has been successfully removed.
   669    * @rejects JavaScript exception if the operation could not be completed.
   670    */
   671   removePartialData: function ()
   672   {
   673     if (!this.canceled && !this.error) {
   674       return Promise.resolve();
   675     }
   677     let promiseRemovePartialData = this._promiseRemovePartialData;
   679     if (!promiseRemovePartialData) {
   680       let deferRemovePartialData = Promise.defer();
   681       promiseRemovePartialData = deferRemovePartialData.promise;
   682       this._promiseRemovePartialData = promiseRemovePartialData;
   684       deferRemovePartialData.resolve(
   685         Task.spawn(function task_D_removePartialData() {
   686           try {
   687             // Wait upon any pending cancellation request.
   688             if (this._promiseCanceled) {
   689               yield this._promiseCanceled;
   690             }
   691             // Ask the saver object to remove any partial data.
   692             yield this.saver.removePartialData();
   693             // For completeness, clear the number of bytes transferred.
   694             if (this.currentBytes != 0 || this.hasPartialData) {
   695               this.currentBytes = 0;
   696               this.hasPartialData = false;
   697               this._notifyChange();
   698             }
   699           } finally {
   700             this._promiseRemovePartialData = null;
   701           }
   702         }.bind(this)));
   703     }
   705     return promiseRemovePartialData;
   706   },
   708   /**
   709    * This deferred object contains a promise that is resolved as soon as this
   710    * download finishes successfully, and is never rejected.  This property is
   711    * initialized when the download is created, and never changes.
   712    */
   713   _deferSucceeded: null,
   715   /**
   716    * Returns a promise that is resolved as soon as this download finishes
   717    * successfully, even if the download was stopped and restarted meanwhile.
   718    *
   719    * You can use this property for scheduling download completion actions in the
   720    * current session, for downloads that are controlled interactively.  If the
   721    * download is not controlled interactively, you should use the promise
   722    * returned by the "start" method instead, to check for success or failure.
   723    *
   724    * @return {Promise}
   725    * @resolves When the download has finished successfully.
   726    * @rejects Never.
   727    */
   728   whenSucceeded: function D_whenSucceeded()
   729   {
   730     return this._deferSucceeded.promise;
   731   },
   733   /**
   734    * Updates the state of a finished, failed, or canceled download based on the
   735    * current state in the file system.  If the download is in progress or it has
   736    * been finalized, this method has no effect, and it returns a resolved
   737    * promise.
   738    *
   739    * This allows the properties of the download to be updated in case the user
   740    * moved or deleted the target file or its associated ".part" file.
   741    *
   742    * @return {Promise}
   743    * @resolves When the operation has completed.
   744    * @rejects Never.
   745    */
   746   refresh: function ()
   747   {
   748     return Task.spawn(function () {
   749       if (!this.stopped || this._finalized) {
   750         return;
   751       }
   753       // Update the current progress from disk if we retained partial data.
   754       if (this.hasPartialData && this.target.partFilePath) {
   755         let stat = yield OS.File.stat(this.target.partFilePath);
   757         // Ignore the result if the state has changed meanwhile.
   758         if (!this.stopped || this._finalized) {
   759           return;
   760         }
   762         // Update the bytes transferred and the related progress properties.
   763         this.currentBytes = stat.size;
   764         if (this.totalBytes > 0) {
   765           this.hasProgress = true;
   766           this.progress = Math.floor(this.currentBytes /
   767                                          this.totalBytes * 100);
   768         }
   769         this._notifyChange();
   770       }
   771     }.bind(this)).then(null, Cu.reportError);
   772   },
   774   /**
   775    * True if the "finalize" method has been called.  This prevents the download
   776    * from starting again after having been stopped.
   777    */
   778   _finalized: false,
   780   /**
   781    * Ensures that the download is stopped, and optionally removes any partial
   782    * data kept as part of a canceled or failed download.  After this method has
   783    * been called, the download cannot be started again.
   784    *
   785    * This method should be used in place of "cancel" and removePartialData while
   786    * shutting down or disposing of the download object, to prevent other callers
   787    * from interfering with the operation.  This is required because cancellation
   788    * and other operations are asynchronous.
   789    *
   790    * @param aRemovePartialData
   791    *        Whether any partially downloaded data should be removed after the
   792    *        download has been stopped.
   793    *
   794    * @return {Promise}
   795    * @resolves When the operation has finished successfully.
   796    * @rejects JavaScript exception if an error occurred while removing the
   797    *          partially downloaded data.
   798    */
   799   finalize: function (aRemovePartialData)
   800   {
   801     // Prevents the download from starting again after having been stopped.
   802     this._finalized = true;
   804     if (aRemovePartialData) {
   805       // Cancel the download, in case it is currently in progress, then remove
   806       // any partially downloaded data.  The removal operation waits for
   807       // cancellation to be completed before resolving the promise it returns.
   808       this.cancel();
   809       return this.removePartialData();
   810     } else {
   811       // Just cancel the download, in case it is currently in progress.
   812       return this.cancel();
   813     }
   814   },
   816   /**
   817    * Indicates the time of the last progress notification, expressed as the
   818    * number of milliseconds since January 1, 1970, 00:00:00 UTC.  This is zero
   819    * until some bytes have actually been transferred.
   820    */
   821   _lastProgressTimeMs: 0,
   823   /**
   824    * Updates progress notifications based on the number of bytes transferred.
   825    *
   826    * The number of bytes transferred is not updated unless enough time passed
   827    * since this function was last called.  This limits the computation load, in
   828    * particular when the listeners update the user interface in response.
   829    *
   830    * @param aCurrentBytes
   831    *        Number of bytes transferred until now.
   832    * @param aTotalBytes
   833    *        Total number of bytes to be transferred, or -1 if unknown.
   834    * @param aHasPartialData
   835    *        Indicates whether the partially downloaded data can be used when
   836    *        restarting the download if it fails or is canceled.
   837    */
   838   _setBytes: function D_setBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
   839     let changeMade = (this.hasPartialData != aHasPartialData);
   840     this.hasPartialData = aHasPartialData;
   842     // Unless aTotalBytes is -1, we can report partial download progress.  In
   843     // this case, notify when the related properties changed since last time.
   844     if (aTotalBytes != -1 && (!this.hasProgress ||
   845                               this.totalBytes != aTotalBytes)) {
   846       this.hasProgress = true;
   847       this.totalBytes = aTotalBytes;
   848       changeMade = true;
   849     }
   851     // Updating the progress and computing the speed require that enough time
   852     // passed since the last update, or that we haven't started throttling yet.
   853     let currentTimeMs = Date.now();
   854     let intervalMs = currentTimeMs - this._lastProgressTimeMs;
   855     if (intervalMs >= kProgressUpdateIntervalMs) {
   856       // Don't compute the speed unless we started throttling notifications.
   857       if (this._lastProgressTimeMs != 0) {
   858         // Calculate the speed in bytes per second.
   859         let rawSpeed = (aCurrentBytes - this.currentBytes) / intervalMs * 1000;
   860         if (this.speed == 0) {
   861           // When the previous speed is exactly zero instead of a fractional
   862           // number, this can be considered the first element of the series.
   863           this.speed = rawSpeed;
   864         } else {
   865           // Apply exponential smoothing, with a smoothing factor of 0.1.
   866           this.speed = rawSpeed * 0.1 + this.speed * 0.9;
   867         }
   868       }
   870       // Start throttling notifications only when we have actually received some
   871       // bytes for the first time.  The timing of the first part of the download
   872       // is not reliable, due to possible latency in the initial notifications.
   873       // This also allows automated tests to receive and verify the number of
   874       // bytes initially transferred.
   875       if (aCurrentBytes > 0) {
   876         this._lastProgressTimeMs = currentTimeMs;
   878         // Update the progress now that we don't need its previous value.
   879         this.currentBytes = aCurrentBytes;
   880         if (this.totalBytes > 0) {
   881           this.progress = Math.floor(this.currentBytes / this.totalBytes * 100);
   882         }
   883         changeMade = true;
   884       }
   885     }
   887     if (changeMade) {
   888       this._notifyChange();
   889     }
   890   },
   892   /**
   893    * Returns a static representation of the current object state.
   894    *
   895    * @return A JavaScript object that can be serialized to JSON.
   896    */
   897   toSerializable: function ()
   898   {
   899     let serializable = {
   900       source: this.source.toSerializable(),
   901       target: this.target.toSerializable(),
   902     };
   904     // Simplify the representation for the most common saver type.  If the saver
   905     // is an object instead of a simple string, we can't simplify it because we
   906     // need to persist all its properties, not only "type".  This may happen for
   907     // savers of type "copy" as well as other types.
   908     let saver = this.saver.toSerializable();
   909     if (saver !== "copy") {
   910       serializable.saver = saver;
   911     }
   913     if (this.error && ("message" in this.error)) {
   914       serializable.error = { message: this.error.message };
   915     }
   917     if (this.startTime) {
   918       serializable.startTime = this.startTime.toJSON();
   919     }
   921     // These are serialized unless they are false, null, or empty strings.
   922     for (let property of kSerializableDownloadProperties) {
   923       if (property != "error" && property != "startTime" && this[property]) {
   924         serializable[property] = this[property];
   925       }
   926     }
   928     serializeUnknownProperties(this, serializable);
   930     return serializable;
   931   },
   933   /**
   934    * Returns a value that changes only when one of the properties of a Download
   935    * object that should be saved into a file also change.  This excludes
   936    * properties whose value doesn't usually change during the download lifetime.
   937    *
   938    * This function is used to determine whether the download should be
   939    * serialized after a property change notification has been received.
   940    *
   941    * @return String representing the relevant download state.
   942    */
   943   getSerializationHash: function ()
   944   {
   945     // The "succeeded", "canceled", "error", and startTime properties are not
   946     // taken into account because they all change before the "stopped" property
   947     // changes, and are not altered in other cases.
   948     return this.stopped + "," + this.totalBytes + "," + this.hasPartialData +
   949            "," + this.contentType;
   950   },
   951 };
   953 /**
   954  * Defines which properties of the Download object are serializable.
   955  */
   956 const kSerializableDownloadProperties = [
   957   "succeeded",
   958   "canceled",
   959   "error",
   960   "totalBytes",
   961   "hasPartialData",
   962   "tryToKeepPartialData",
   963   "launcherPath",
   964   "launchWhenSucceeded",
   965   "contentType",
   966 ];
   968 /**
   969  * Creates a new Download object from a serializable representation.  This
   970  * function is used by the createDownload method of Downloads.jsm when a new
   971  * Download object is requested, thus some properties may refer to live objects
   972  * in place of their serializable representations.
   973  *
   974  * @param aSerializable
   975  *        An object with the following fields:
   976  *        {
   977  *          source: DownloadSource object, or its serializable representation.
   978  *                  See DownloadSource.fromSerializable for details.
   979  *          target: DownloadTarget object, or its serializable representation.
   980  *                  See DownloadTarget.fromSerializable for details.
   981  *          saver: Serializable representation of a DownloadSaver object.  See
   982  *                 DownloadSaver.fromSerializable for details.  If omitted,
   983  *                 defaults to "copy".
   984  *        }
   985  *
   986  * @return The newly created Download object.
   987  */
   988 Download.fromSerializable = function (aSerializable) {
   989   let download = new Download();
   990   if (aSerializable.source instanceof DownloadSource) {
   991     download.source = aSerializable.source;
   992   } else {
   993     download.source = DownloadSource.fromSerializable(aSerializable.source);
   994   }
   995   if (aSerializable.target instanceof DownloadTarget) {
   996     download.target = aSerializable.target;
   997   } else {
   998     download.target = DownloadTarget.fromSerializable(aSerializable.target);
   999   }
  1000   if ("saver" in aSerializable) {
  1001     download.saver = DownloadSaver.fromSerializable(aSerializable.saver);
  1002   } else {
  1003     download.saver = DownloadSaver.fromSerializable("copy");
  1005   download.saver.download = download;
  1007   if ("startTime" in aSerializable) {
  1008     let time = aSerializable.startTime.getTime
  1009              ? aSerializable.startTime.getTime()
  1010              : aSerializable.startTime;
  1011     download.startTime = new Date(time);
  1014   for (let property of kSerializableDownloadProperties) {
  1015     if (property in aSerializable) {
  1016       download[property] = aSerializable[property];
  1020   deserializeUnknownProperties(download, aSerializable, property =>
  1021     kSerializableDownloadProperties.indexOf(property) == -1 &&
  1022     property != "startTime" &&
  1023     property != "source" &&
  1024     property != "target" &&
  1025     property != "saver");
  1027   return download;
  1028 };
  1030 ////////////////////////////////////////////////////////////////////////////////
  1031 //// DownloadSource
  1033 /**
  1034  * Represents the source of a download, for example a document or an URI.
  1035  */
  1036 this.DownloadSource = function () {}
  1038 this.DownloadSource.prototype = {
  1039   /**
  1040    * String containing the URI for the download source.
  1041    */
  1042   url: null,
  1044   /**
  1045    * Indicates whether the download originated from a private window.  This
  1046    * determines the context of the network request that is made to retrieve the
  1047    * resource.
  1048    */
  1049   isPrivate: false,
  1051   /**
  1052    * String containing the referrer URI of the download source, or null if no
  1053    * referrer should be sent or the download source is not HTTP.
  1054    */
  1055   referrer: null,
  1057   /**
  1058    * Returns a static representation of the current object state.
  1060    * @return A JavaScript object that can be serialized to JSON.
  1061    */
  1062   toSerializable: function ()
  1064     // Simplify the representation if we don't have other details.
  1065     if (!this.isPrivate && !this.referrer && !this._unknownProperties) {
  1066       return this.url;
  1069     let serializable = { url: this.url };
  1070     if (this.isPrivate) {
  1071       serializable.isPrivate = true;
  1073     if (this.referrer) {
  1074       serializable.referrer = this.referrer;
  1077     serializeUnknownProperties(this, serializable);
  1078     return serializable;
  1079   },
  1080 };
  1082 /**
  1083  * Creates a new DownloadSource object from its serializable representation.
  1085  * @param aSerializable
  1086  *        Serializable representation of a DownloadSource object.  This may be a
  1087  *        string containing the URI for the download source, an nsIURI, or an
  1088  *        object with the following properties:
  1089  *        {
  1090  *          url: String containing the URI for the download source.
  1091  *          isPrivate: Indicates whether the download originated from a private
  1092  *                     window.  If omitted, the download is public.
  1093  *          referrer: String containing the referrer URI of the download source.
  1094  *                    Can be omitted or null if no referrer should be sent or
  1095  *                    the download source is not HTTP.
  1096  *        }
  1098  * @return The newly created DownloadSource object.
  1099  */
  1100 this.DownloadSource.fromSerializable = function (aSerializable) {
  1101   let source = new DownloadSource();
  1102   if (isString(aSerializable)) {
  1103     // Convert String objects to primitive strings at this point.
  1104     source.url = aSerializable.toString();
  1105   } else if (aSerializable instanceof Ci.nsIURI) {
  1106     source.url = aSerializable.spec;
  1107   } else {
  1108     // Convert String objects to primitive strings at this point.
  1109     source.url = aSerializable.url.toString();
  1110     if ("isPrivate" in aSerializable) {
  1111       source.isPrivate = aSerializable.isPrivate;
  1113     if ("referrer" in aSerializable) {
  1114       source.referrer = aSerializable.referrer;
  1117     deserializeUnknownProperties(source, aSerializable, property =>
  1118       property != "url" && property != "isPrivate" && property != "referrer");
  1121   return source;
  1122 };
  1124 ////////////////////////////////////////////////////////////////////////////////
  1125 //// DownloadTarget
  1127 /**
  1128  * Represents the target of a download, for example a file in the global
  1129  * downloads directory, or a file in the system temporary directory.
  1130  */
  1131 this.DownloadTarget = function () {}
  1133 this.DownloadTarget.prototype = {
  1134   /**
  1135    * String containing the path of the target file.
  1136    */
  1137   path: null,
  1139   /**
  1140    * String containing the path of the ".part" file containing the data
  1141    * downloaded so far, or null to disable the use of a ".part" file to keep
  1142    * partially downloaded data.
  1143    */
  1144   partFilePath: null,
  1146   /**
  1147    * Returns a static representation of the current object state.
  1149    * @return A JavaScript object that can be serialized to JSON.
  1150    */
  1151   toSerializable: function ()
  1153     // Simplify the representation if we don't have other details.
  1154     if (!this.partFilePath && !this._unknownProperties) {
  1155       return this.path;
  1158     let serializable = { path: this.path,
  1159                          partFilePath: this.partFilePath };
  1160     serializeUnknownProperties(this, serializable);
  1161     return serializable;
  1162   },
  1163 };
  1165 /**
  1166  * Creates a new DownloadTarget object from its serializable representation.
  1168  * @param aSerializable
  1169  *        Serializable representation of a DownloadTarget object.  This may be a
  1170  *        string containing the path of the target file, an nsIFile, or an
  1171  *        object with the following properties:
  1172  *        {
  1173  *          path: String containing the path of the target file.
  1174  *          partFilePath: optional string containing the part file path.
  1175  *        }
  1177  * @return The newly created DownloadTarget object.
  1178  */
  1179 this.DownloadTarget.fromSerializable = function (aSerializable) {
  1180   let target = new DownloadTarget();
  1181   if (isString(aSerializable)) {
  1182     // Convert String objects to primitive strings at this point.
  1183     target.path = aSerializable.toString();
  1184   } else if (aSerializable instanceof Ci.nsIFile) {
  1185     // Read the "path" property of nsIFile after checking the object type.
  1186     target.path = aSerializable.path;
  1187   } else {
  1188     // Read the "path" property of the serializable DownloadTarget
  1189     // representation, converting String objects to primitive strings.
  1190     target.path = aSerializable.path.toString();
  1191     if ("partFilePath" in aSerializable) {
  1192       target.partFilePath = aSerializable.partFilePath;
  1195     deserializeUnknownProperties(target, aSerializable, property =>
  1196       property != "path" && property != "partFilePath");
  1198   return target;
  1199 };
  1201 ////////////////////////////////////////////////////////////////////////////////
  1202 //// DownloadError
  1204 /**
  1205  * Provides detailed information about a download failure.
  1207  * @param aProperties
  1208  *        Object which may contain any of the following properties:
  1209  *          {
  1210  *            result: Result error code, defaulting to Cr.NS_ERROR_FAILURE
  1211  *            message: String error message to be displayed, or null to use the
  1212  *                     message associated with the result code.
  1213  *            inferCause: If true, attempts to determine if the cause of the
  1214  *                        download is a network failure or a local file failure,
  1215  *                        based on a set of known values of the result code.
  1216  *                        This is useful when the error is received by a
  1217  *                        component that handles both aspects of the download.
  1218  *          }
  1219  *        The properties object may also contain any of the DownloadError's
  1220  *        because properties, which will be set accordingly in the error object.
  1221  */
  1222 this.DownloadError = function (aProperties)
  1224   const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
  1225   const NS_ERROR_MODULE_NETWORK = 6;
  1226   const NS_ERROR_MODULE_FILES = 13;
  1228   // Set the error name used by the Error object prototype first.
  1229   this.name = "DownloadError";
  1230   this.result = aProperties.result || Cr.NS_ERROR_FAILURE;
  1231   if (aProperties.message) {
  1232     this.message = aProperties.message;
  1233   } else if (aProperties.becauseBlocked ||
  1234              aProperties.becauseBlockedByParentalControls ||
  1235              aProperties.becauseBlockedByReputationCheck) {
  1236     this.message = "Download blocked.";
  1237   } else {
  1238     let exception = new Components.Exception("", this.result);
  1239     this.message = exception.toString();
  1241   if (aProperties.inferCause) {
  1242     let module = ((this.result & 0x7FFF0000) >> 16) -
  1243                  NS_ERROR_MODULE_BASE_OFFSET;
  1244     this.becauseSourceFailed = (module == NS_ERROR_MODULE_NETWORK);
  1245     this.becauseTargetFailed = (module == NS_ERROR_MODULE_FILES);
  1247   else {
  1248     if (aProperties.becauseSourceFailed) {
  1249       this.becauseSourceFailed = true;
  1251     if (aProperties.becauseTargetFailed) {
  1252       this.becauseTargetFailed = true;
  1256   if (aProperties.becauseBlockedByParentalControls) {
  1257     this.becauseBlocked = true;
  1258     this.becauseBlockedByParentalControls = true;
  1259   } else if (aProperties.becauseBlockedByReputationCheck) {
  1260     this.becauseBlocked = true;
  1261     this.becauseBlockedByReputationCheck = true;
  1262   } else if (aProperties.becauseBlocked) {
  1263     this.becauseBlocked = true;
  1266   this.stack = new Error().stack;
  1269 this.DownloadError.prototype = {
  1270   __proto__: Error.prototype,
  1272   /**
  1273    * The result code associated with this error.
  1274    */
  1275   result: false,
  1277   /**
  1278    * Indicates an error occurred while reading from the remote location.
  1279    */
  1280   becauseSourceFailed: false,
  1282   /**
  1283    * Indicates an error occurred while writing to the local target.
  1284    */
  1285   becauseTargetFailed: false,
  1287   /**
  1288    * Indicates the download failed because it was blocked.  If the reason for
  1289    * blocking is known, the corresponding property will be also set.
  1290    */
  1291   becauseBlocked: false,
  1293   /**
  1294    * Indicates the download was blocked because downloads are globally
  1295    * disallowed by the Parental Controls or Family Safety features on Windows.
  1296    */
  1297   becauseBlockedByParentalControls: false,
  1299   /**
  1300    * Indicates the download was blocked because it failed the reputation check
  1301    * and may be malware.
  1302    */
  1303   becauseBlockedByReputationCheck: false,
  1304 };
  1306 ////////////////////////////////////////////////////////////////////////////////
  1307 //// DownloadSaver
  1309 /**
  1310  * Template for an object that actually transfers the data for the download.
  1311  */
  1312 this.DownloadSaver = function () {}
  1314 this.DownloadSaver.prototype = {
  1315   /**
  1316    * Download object for raising notifications and reading properties.
  1318    * If the tryToKeepPartialData property of the download object is false, the
  1319    * saver should never try to keep partially downloaded data if the download
  1320    * fails.
  1321    */
  1322   download: null,
  1324   /**
  1325    * Executes the download.
  1327    * @param aSetProgressBytesFn
  1328    *        This function may be called by the saver to report progress. It
  1329    *        takes three arguments: the first is the number of bytes transferred
  1330    *        until now, the second is the total number of bytes to be
  1331    *        transferred (or -1 if unknown), the third indicates whether the
  1332    *        partially downloaded data can be used when restarting the download
  1333    *        if it fails or is canceled.
  1334    * @param aSetPropertiesFn
  1335    *        This function may be called by the saver to report information
  1336    *        about new download properties discovered by the saver during the
  1337    *        download process. It takes an object where the keys represents
  1338    *        the names of the properties to set, and the value represents the
  1339    *        value to set.
  1341    * @return {Promise}
  1342    * @resolves When the download has finished successfully.
  1343    * @rejects JavaScript exception if the download failed.
  1344    */
  1345   execute: function DS_execute(aSetProgressBytesFn, aSetPropertiesFn)
  1347     throw new Error("Not implemented.");
  1348   },
  1350   /**
  1351    * Cancels the download.
  1352    */
  1353   cancel: function DS_cancel()
  1355     throw new Error("Not implemented.");
  1356   },
  1358   /**
  1359    * Removes any partial data kept as part of a canceled or failed download.
  1361    * This method is never called until the promise returned by "execute" is
  1362    * either resolved or rejected, and the "execute" method is not called again
  1363    * until the promise returned by this method is resolved or rejected.
  1365    * @return {Promise}
  1366    * @resolves When the operation has finished successfully.
  1367    * @rejects JavaScript exception.
  1368    */
  1369   removePartialData: function DS_removePartialData()
  1371     return Promise.resolve();
  1372   },
  1374   /**
  1375    * This can be called by the saver implementation when the download is already
  1376    * started, to add it to the browsing history.  This method has no effect if
  1377    * the download is private.
  1378    */
  1379   addToHistory: function ()
  1381     if (this.download.source.isPrivate) {
  1382       return;
  1385     let sourceUri = NetUtil.newURI(this.download.source.url);
  1386     let referrer = this.download.source.referrer;
  1387     let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
  1388     let targetUri = NetUtil.newURI(new FileUtils.File(
  1389                                        this.download.target.path));
  1391     // The start time is always available when we reach this point.
  1392     let startPRTime = this.download.startTime.getTime() * 1000;
  1394     gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
  1395                                  targetUri);
  1396   },
  1398   /**
  1399    * Returns a static representation of the current object state.
  1401    * @return A JavaScript object that can be serialized to JSON.
  1402    */
  1403   toSerializable: function ()
  1405     throw new Error("Not implemented.");
  1406   },
  1408   /**
  1409    * Returns the SHA-256 hash of the downloaded file, if it exists.
  1410    */
  1411   getSha256Hash: function ()
  1413     throw new Error("Not implemented.");
  1414   },
  1416   getSignatureInfo: function ()
  1418     throw new Error("Not implemented.");
  1419   },
  1420 }; // DownloadSaver
  1422 /**
  1423  * Creates a new DownloadSaver object from its serializable representation.
  1425  * @param aSerializable
  1426  *        Serializable representation of a DownloadSaver object.  If no initial
  1427  *        state information for the saver object is needed, can be a string
  1428  *        representing the class of the download operation, for example "copy".
  1430  * @return The newly created DownloadSaver object.
  1431  */
  1432 this.DownloadSaver.fromSerializable = function (aSerializable) {
  1433   let serializable = isString(aSerializable) ? { type: aSerializable }
  1434                                              : aSerializable;
  1435   let saver;
  1436   switch (serializable.type) {
  1437     case "copy":
  1438       saver = DownloadCopySaver.fromSerializable(serializable);
  1439       break;
  1440     case "legacy":
  1441       saver = DownloadLegacySaver.fromSerializable(serializable);
  1442       break;
  1443     default:
  1444       throw new Error("Unrecoginzed download saver type.");
  1446   return saver;
  1447 };
  1449 ////////////////////////////////////////////////////////////////////////////////
  1450 //// DownloadCopySaver
  1452 /**
  1453  * Saver object that simply copies the entire source file to the target.
  1454  */
  1455 this.DownloadCopySaver = function () {}
  1457 this.DownloadCopySaver.prototype = {
  1458   __proto__: DownloadSaver.prototype,
  1460   /**
  1461    * BackgroundFileSaver object currently handling the download.
  1462    */
  1463   _backgroundFileSaver: null,
  1465   /**
  1466    * Indicates whether the "cancel" method has been called.  This is used to
  1467    * prevent the request from starting in case the operation is canceled before
  1468    * the BackgroundFileSaver instance has been created.
  1469    */
  1470   _canceled: false,
  1472   /**
  1473    * Save the SHA-256 hash in raw bytes of the downloaded file. This is null
  1474    * unless BackgroundFileSaver has successfully completed saving the file.
  1475    */
  1476   _sha256Hash: null,
  1478   /**
  1479    * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
  1480    * if the file is signed. This is empty if the file is unsigned, and null
  1481    * unless BackgroundFileSaver has successfully completed saving the file.
  1482    */
  1483   _signatureInfo: null,
  1485   /**
  1486    * True if the associated download has already been added to browsing history.
  1487    */
  1488   alreadyAddedToHistory: false,
  1490   /**
  1491    * String corresponding to the entityID property of the nsIResumableChannel
  1492    * used to execute the download, or null if the channel was not resumable or
  1493    * the saver was instructed not to keep partially downloaded data.
  1494    */
  1495   entityID: null,
  1497   /**
  1498    * Implements "DownloadSaver.execute".
  1499    */
  1500   execute: function DCS_execute(aSetProgressBytesFn, aSetPropertiesFn)
  1502     let copySaver = this;
  1504     this._canceled = false;
  1506     let download = this.download;
  1507     let targetPath = download.target.path;
  1508     let partFilePath = download.target.partFilePath;
  1509     let keepPartialData = download.tryToKeepPartialData;
  1511     return Task.spawn(function task_DCS_execute() {
  1512       // Add the download to history the first time it is started in this
  1513       // session.  If the download is restarted in a different session, a new
  1514       // history visit will be added.  We do this just to avoid the complexity
  1515       // of serializing this state between sessions, since adding a new visit
  1516       // does not have any noticeable side effect.
  1517       if (!this.alreadyAddedToHistory) {
  1518         this.addToHistory();
  1519         this.alreadyAddedToHistory = true;
  1522       // To reduce the chance that other downloads reuse the same final target
  1523       // file name, we should create a placeholder as soon as possible, before
  1524       // starting the network request.  The placeholder is also required in case
  1525       // we are using a ".part" file instead of the final target while the
  1526       // download is in progress.
  1527       try {
  1528         // If the file already exists, don't delete its contents yet.
  1529         let file = yield OS.File.open(targetPath, { write: true });
  1530         yield file.close();
  1531       } catch (ex if ex instanceof OS.File.Error) {
  1532         // Throw a DownloadError indicating that the operation failed because of
  1533         // the target file.  We cannot translate this into a specific result
  1534         // code, but we preserve the original message using the toString method.
  1535         let error = new DownloadError({ message: ex.toString() });
  1536         error.becauseTargetFailed = true;
  1537         throw error;
  1540       try {
  1541         let deferSaveComplete = Promise.defer();
  1543         if (this._canceled) {
  1544           // Don't create the BackgroundFileSaver object if we have been
  1545           // canceled meanwhile.
  1546           throw new DownloadError({ message: "Saver canceled." });
  1549         // Create the object that will save the file in a background thread.
  1550         let backgroundFileSaver = new BackgroundFileSaverStreamListener();
  1551         try {
  1552           // When the operation completes, reflect the status in the promise
  1553           // returned by this download execution function.
  1554           backgroundFileSaver.observer = {
  1555             onTargetChange: function () { },
  1556             onSaveComplete: (aSaver, aStatus) => {
  1557               // Send notifications now that we can restart if needed.
  1558               if (Components.isSuccessCode(aStatus)) {
  1559                 // Save the hash before freeing backgroundFileSaver.
  1560                 this._sha256Hash = aSaver.sha256Hash;
  1561                 this._signatureInfo = aSaver.signatureInfo;
  1562                 deferSaveComplete.resolve();
  1563               } else {
  1564                 // Infer the origin of the error from the failure code, because
  1565                 // BackgroundFileSaver does not provide more specific data.
  1566                 let properties = { result: aStatus, inferCause: true };
  1567                 deferSaveComplete.reject(new DownloadError(properties));
  1569               // Free the reference cycle, to release resources earlier.
  1570               backgroundFileSaver.observer = null;
  1571               this._backgroundFileSaver = null;
  1572             },
  1573           };
  1575           // Create a channel from the source, and listen to progress
  1576           // notifications.
  1577           let channel = NetUtil.newChannel(NetUtil.newURI(download.source.url));
  1578           if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
  1579             channel.setPrivate(download.source.isPrivate);
  1581           if (channel instanceof Ci.nsIHttpChannel &&
  1582               download.source.referrer) {
  1583             channel.referrer = NetUtil.newURI(download.source.referrer);
  1586           // If we have data that we can use to resume the download from where
  1587           // it stopped, try to use it.
  1588           let resumeAttempted = false;
  1589           let resumeFromBytes = 0;
  1590           if (channel instanceof Ci.nsIResumableChannel && this.entityID &&
  1591               partFilePath && keepPartialData) {
  1592             try {
  1593               let stat = yield OS.File.stat(partFilePath);
  1594               channel.resumeAt(stat.size, this.entityID);
  1595               resumeAttempted = true;
  1596               resumeFromBytes = stat.size;
  1597             } catch (ex if ex instanceof OS.File.Error &&
  1598                            ex.becauseNoSuchFile) { }
  1601           channel.notificationCallbacks = {
  1602             QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor]),
  1603             getInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink]),
  1604             onProgress: function DCSE_onProgress(aRequest, aContext, aProgress,
  1605                                                  aProgressMax)
  1607               let currentBytes = resumeFromBytes + aProgress;
  1608               let totalBytes = aProgressMax == -1 ? -1 : (resumeFromBytes +
  1609                                                           aProgressMax);
  1610               aSetProgressBytesFn(currentBytes, totalBytes, aProgress > 0 &&
  1611                                   partFilePath && keepPartialData);
  1612             },
  1613             onStatus: function () { },
  1614           };
  1616           // Open the channel, directing output to the background file saver.
  1617           backgroundFileSaver.QueryInterface(Ci.nsIStreamListener);
  1618           channel.asyncOpen({
  1619             onStartRequest: function (aRequest, aContext) {
  1620               backgroundFileSaver.onStartRequest(aRequest, aContext);
  1622               // Check if the request's response has been blocked by Windows
  1623               // Parental Controls with an HTTP 450 error code.
  1624               if (aRequest instanceof Ci.nsIHttpChannel &&
  1625                   aRequest.responseStatus == 450) {
  1626                 // Set a flag that can be retrieved later when handling the
  1627                 // cancellation so that the proper error can be thrown.
  1628                 this.download._blockedByParentalControls = true;
  1629                 aRequest.cancel(Cr.NS_BINDING_ABORTED);
  1630                 return;
  1633               aSetPropertiesFn({ contentType: channel.contentType });
  1635               // Ensure we report the value of "Content-Length", if available,
  1636               // even if the download doesn't generate any progress events
  1637               // later.
  1638               if (channel.contentLength >= 0) {
  1639                 aSetProgressBytesFn(0, channel.contentLength);
  1642               // If the URL we are downloading from includes a file extension
  1643               // that matches the "Content-Encoding" header, for example ".gz"
  1644               // with a "gzip" encoding, we should save the file in its encoded
  1645               // form.  In all other cases, we decode the body while saving.
  1646               if (channel instanceof Ci.nsIEncodedChannel &&
  1647                   channel.contentEncodings) {
  1648                 let uri = channel.URI;
  1649                 if (uri instanceof Ci.nsIURL && uri.fileExtension) {
  1650                   // Only the first, outermost encoding is considered.
  1651                   let encoding = channel.contentEncodings.getNext();
  1652                   if (encoding) {
  1653                     channel.applyConversion =
  1654                       gExternalHelperAppService.applyDecodingForExtension(
  1655                                                 uri.fileExtension, encoding);
  1660               if (keepPartialData) {
  1661                 // If the source is not resumable, don't keep partial data even
  1662                 // if we were asked to try and do it.
  1663                 if (aRequest instanceof Ci.nsIResumableChannel) {
  1664                   try {
  1665                     // If reading the ID succeeds, the source is resumable.
  1666                     this.entityID = aRequest.entityID;
  1667                   } catch (ex if ex instanceof Components.Exception &&
  1668                                  ex.result == Cr.NS_ERROR_NOT_RESUMABLE) {
  1669                     keepPartialData = false;
  1671                 } else {
  1672                   keepPartialData = false;
  1676               // Enable hashing and signature verification before setting the
  1677               // target.
  1678               backgroundFileSaver.enableSha256();
  1679               backgroundFileSaver.enableSignatureInfo();
  1680               if (partFilePath) {
  1681                 // If we actually resumed a request, append to the partial data.
  1682                 if (resumeAttempted) {
  1683                   // TODO: Handle Cr.NS_ERROR_ENTITY_CHANGED
  1684                   backgroundFileSaver.enableAppend();
  1687                 // Use a part file, determining if we should keep it on failure.
  1688                 backgroundFileSaver.setTarget(new FileUtils.File(partFilePath),
  1689                                               keepPartialData);
  1690               } else {
  1691                 // Set the final target file, and delete it on failure.
  1692                 backgroundFileSaver.setTarget(new FileUtils.File(targetPath),
  1693                                               false);
  1695             }.bind(copySaver),
  1697             onStopRequest: function (aRequest, aContext, aStatusCode) {
  1698               try {
  1699                 backgroundFileSaver.onStopRequest(aRequest, aContext,
  1700                                                   aStatusCode);
  1701               } finally {
  1702                 // If the data transfer completed successfully, indicate to the
  1703                 // background file saver that the operation can finish.  If the
  1704                 // data transfer failed, the saver has been already stopped.
  1705                 if (Components.isSuccessCode(aStatusCode)) {
  1706                   if (partFilePath) {
  1707                     // Move to the final target if we were using a part file.
  1708                     backgroundFileSaver.setTarget(
  1709                                         new FileUtils.File(targetPath), false);
  1711                   backgroundFileSaver.finish(Cr.NS_OK);
  1714             }.bind(copySaver),
  1716             onDataAvailable: function (aRequest, aContext, aInputStream,
  1717                                        aOffset, aCount) {
  1718               backgroundFileSaver.onDataAvailable(aRequest, aContext,
  1719                                                   aInputStream, aOffset,
  1720                                                   aCount);
  1721             }.bind(copySaver),
  1722           }, null);
  1724           // We should check if we have been canceled in the meantime, after
  1725           // all the previous asynchronous operations have been executed and
  1726           // just before we set the _backgroundFileSaver property.
  1727           if (this._canceled) {
  1728             throw new DownloadError({ message: "Saver canceled." });
  1731           // If the operation succeeded, store the object to allow cancellation.
  1732           this._backgroundFileSaver = backgroundFileSaver;
  1733         } catch (ex) {
  1734           // In case an error occurs while setting up the chain of objects for
  1735           // the download, ensure that we release the resources of the saver.
  1736           backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
  1737           throw ex;
  1740         // We will wait on this promise in case no error occurred while setting
  1741         // up the chain of objects for the download.
  1742         yield deferSaveComplete.promise;
  1743       } catch (ex) {
  1744         // Ensure we always remove the placeholder for the final target file on
  1745         // failure, independently of which code path failed.  In some cases, the
  1746         // background file saver may have already removed the file.
  1747         try {
  1748           yield OS.File.remove(targetPath);
  1749         } catch (e2) {
  1750           // If we failed during the operation, we report the error but use the
  1751           // original one as the failure reason of the download.  Note that on
  1752           // Windows we may get an access denied error instead of a no such file
  1753           // error if the file existed before, and was recently deleted.
  1754           if (!(e2 instanceof OS.File.Error &&
  1755                 (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
  1756             Cu.reportError(e2);
  1759         throw ex;
  1761     }.bind(this));
  1762   },
  1764   /**
  1765    * Implements "DownloadSaver.cancel".
  1766    */
  1767   cancel: function DCS_cancel()
  1769     this._canceled = true;
  1770     if (this._backgroundFileSaver) {
  1771       this._backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
  1772       this._backgroundFileSaver = null;
  1774   },
  1776   /**
  1777    * Implements "DownloadSaver.removePartialData".
  1778    */
  1779   removePartialData: function ()
  1781     return Task.spawn(function task_DCS_removePartialData() {
  1782       if (this.download.target.partFilePath) {
  1783         try {
  1784           yield OS.File.remove(this.download.target.partFilePath);
  1785         } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { }
  1787     }.bind(this));
  1788   },
  1790   /**
  1791    * Implements "DownloadSaver.toSerializable".
  1792    */
  1793   toSerializable: function ()
  1795     // Simplify the representation if we don't have other details.
  1796     if (!this.entityID && !this._unknownProperties) {
  1797       return "copy";
  1800     let serializable = { type: "copy",
  1801                          entityID: this.entityID };
  1802     serializeUnknownProperties(this, serializable);
  1803     return serializable;
  1804   },
  1806   /**
  1807    * Implements "DownloadSaver.getSha256Hash"
  1808    */
  1809   getSha256Hash: function ()
  1811     return this._sha256Hash;
  1812   },
  1814   /*
  1815    * Implements DownloadSaver.getSignatureInfo.
  1816    */
  1817   getSignatureInfo: function ()
  1819     return this._signatureInfo;
  1821 };
  1823 /**
  1824  * Creates a new DownloadCopySaver object, with its initial state derived from
  1825  * its serializable representation.
  1827  * @param aSerializable
  1828  *        Serializable representation of a DownloadCopySaver object.
  1830  * @return The newly created DownloadCopySaver object.
  1831  */
  1832 this.DownloadCopySaver.fromSerializable = function (aSerializable) {
  1833   let saver = new DownloadCopySaver();
  1834   if ("entityID" in aSerializable) {
  1835     saver.entityID = aSerializable.entityID;
  1838   deserializeUnknownProperties(saver, aSerializable, property =>
  1839     property != "entityID" && property != "type");
  1841   return saver;
  1842 };
  1844 ////////////////////////////////////////////////////////////////////////////////
  1845 //// DownloadLegacySaver
  1847 /**
  1848  * Saver object that integrates with the legacy nsITransfer interface.
  1850  * For more background on the process, see the DownloadLegacyTransfer object.
  1851  */
  1852 this.DownloadLegacySaver = function()
  1854   this.deferExecuted = Promise.defer();
  1855   this.deferCanceled = Promise.defer();
  1858 this.DownloadLegacySaver.prototype = {
  1859   __proto__: DownloadSaver.prototype,
  1861   /**
  1862    * Save the SHA-256 hash in raw bytes of the downloaded file. This may be
  1863    * null when nsExternalHelperAppService (and thus BackgroundFileSaver) is not
  1864    * invoked.
  1865    */
  1866   _sha256Hash: null,
  1868   /**
  1869    * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
  1870    * if the file is signed. This is empty if the file is unsigned, and null
  1871    * unless BackgroundFileSaver has successfully completed saving the file.
  1872    */
  1873   _signatureInfo: null,
  1875   /**
  1876    * nsIRequest object associated to the status and progress updates we
  1877    * received.  This object is null before we receive the first status and
  1878    * progress update, and is also reset to null when the download is stopped.
  1879    */
  1880   request: null,
  1882   /**
  1883    * This deferred object contains a promise that is resolved as soon as this
  1884    * download finishes successfully, and is rejected in case the download is
  1885    * canceled or receives a failure notification through nsITransfer.
  1886    */
  1887   deferExecuted: null,
  1889   /**
  1890    * This deferred object contains a promise that is resolved if the download
  1891    * receives a cancellation request through the "cancel" method, and is never
  1892    * rejected.  The nsITransfer implementation will register a handler that
  1893    * actually causes the download cancellation.
  1894    */
  1895   deferCanceled: null,
  1897   /**
  1898    * This is populated with the value of the aSetProgressBytesFn argument of the
  1899    * "execute" method, and is null before the method is called.
  1900    */
  1901   setProgressBytesFn: null,
  1903   /**
  1904    * Called by the nsITransfer implementation while the download progresses.
  1906    * @param aCurrentBytes
  1907    *        Number of bytes transferred until now.
  1908    * @param aTotalBytes
  1909    *        Total number of bytes to be transferred, or -1 if unknown.
  1910    */
  1911   onProgressBytes: function DLS_onProgressBytes(aCurrentBytes, aTotalBytes)
  1913     // Ignore progress notifications until we are ready to process them.
  1914     if (!this.setProgressBytesFn) {
  1915       return;
  1918     let hasPartFile = !!this.download.target.partFilePath;
  1920     this.progressWasNotified = true;
  1921     this.setProgressBytesFn(aCurrentBytes, aTotalBytes,
  1922                             aCurrentBytes > 0 && hasPartFile);
  1923   },
  1925   /**
  1926    * Whether the onProgressBytes function has been called at least once.
  1927    */
  1928   progressWasNotified: false,
  1930   /**
  1931    * Called by the nsITransfer implementation when the request has started.
  1933    * @param aRequest
  1934    *        nsIRequest associated to the status update.
  1935    * @param aAlreadyAddedToHistory
  1936    *        Indicates that the nsIExternalHelperAppService component already
  1937    *        added the download to the browsing history, unless it was started
  1938    *        from a private browsing window.  When this parameter is false, the
  1939    *        download is added to the browsing history here.  Private downloads
  1940    *        are never added to history even if this parameter is false.
  1941    */
  1942   onTransferStarted: function (aRequest, aAlreadyAddedToHistory)
  1944     // Store the entity ID to use for resuming if required.
  1945     if (this.download.tryToKeepPartialData &&
  1946         aRequest instanceof Ci.nsIResumableChannel) {
  1947       try {
  1948         // If reading the ID succeeds, the source is resumable.
  1949         this.entityID = aRequest.entityID;
  1950       } catch (ex if ex instanceof Components.Exception &&
  1951                      ex.result == Cr.NS_ERROR_NOT_RESUMABLE) { }
  1954     // For legacy downloads, we must update the referrer at this time.
  1955     if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
  1956       this.download.source.referrer = aRequest.referrer.spec;
  1959     if (!aAlreadyAddedToHistory) {
  1960       this.addToHistory();
  1962   },
  1964   /**
  1965    * Called by the nsITransfer implementation when the request has finished.
  1967    * @param aRequest
  1968    *        nsIRequest associated to the status update.
  1969    * @param aStatus
  1970    *        Status code received by the nsITransfer implementation.
  1971    */
  1972   onTransferFinished: function DLS_onTransferFinished(aRequest, aStatus)
  1974     // Store a reference to the request, used when handling completion.
  1975     this.request = aRequest;
  1977     if (Components.isSuccessCode(aStatus)) {
  1978       this.deferExecuted.resolve();
  1979     } else {
  1980       // Infer the origin of the error from the failure code, because more
  1981       // specific data is not available through the nsITransfer implementation.
  1982       let properties = { result: aStatus, inferCause: true };
  1983       this.deferExecuted.reject(new DownloadError(properties));
  1985   },
  1987   /**
  1988    * When the first execution of the download finished, it can be restarted by
  1989    * using a DownloadCopySaver object instead of the original legacy component
  1990    * that executed the download.
  1991    */
  1992   firstExecutionFinished: false,
  1994   /**
  1995    * In case the download is restarted after the first execution finished, this
  1996    * property contains a reference to the DownloadCopySaver that is executing
  1997    * the new download attempt.
  1998    */
  1999   copySaver: null,
  2001   /**
  2002    * String corresponding to the entityID property of the nsIResumableChannel
  2003    * used to execute the download, or null if the channel was not resumable or
  2004    * the saver was instructed not to keep partially downloaded data.
  2005    */
  2006   entityID: null,
  2008   /**
  2009    * Implements "DownloadSaver.execute".
  2010    */
  2011   execute: function DLS_execute(aSetProgressBytesFn)
  2013     // Check if this is not the first execution of the download.  The Download
  2014     // object guarantees that this function is not re-entered during execution.
  2015     if (this.firstExecutionFinished) {
  2016       if (!this.copySaver) {
  2017         this.copySaver = new DownloadCopySaver();
  2018         this.copySaver.download = this.download;
  2019         this.copySaver.entityID = this.entityID;
  2020         this.copySaver.alreadyAddedToHistory = true;
  2022       return this.copySaver.execute.apply(this.copySaver, arguments);
  2025     this.setProgressBytesFn = aSetProgressBytesFn;
  2027     return Task.spawn(function task_DLS_execute() {
  2028       try {
  2029         // Wait for the component that executes the download to finish.
  2030         yield this.deferExecuted.promise;
  2032         // At this point, the "request" property has been populated.  Ensure we
  2033         // report the value of "Content-Length", if available, even if the
  2034         // download didn't generate any progress events.
  2035         if (!this.progressWasNotified &&
  2036             this.request instanceof Ci.nsIChannel &&
  2037             this.request.contentLength >= 0) {
  2038           aSetProgressBytesFn(0, this.request.contentLength);
  2041         // If the component executing the download provides the path of a
  2042         // ".part" file, it means that it expects the listener to move the file
  2043         // to its final target path when the download succeeds.  In this case,
  2044         // an empty ".part" file is created even if no data was received from
  2045         // the source.
  2046         if (this.download.target.partFilePath) {
  2047           yield OS.File.move(this.download.target.partFilePath,
  2048                              this.download.target.path);
  2049         } else {
  2050           // The download implementation may not have created the target file if
  2051           // no data was received from the source.  In this case, ensure that an
  2052           // empty file is created as expected.
  2053           try {
  2054             // This atomic operation is more efficient than an existence check.
  2055             let file = yield OS.File.open(this.download.target.path,
  2056                                           { create: true });
  2057             yield file.close();
  2058           } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { }
  2060       } catch (ex) {
  2061         // Ensure we always remove the final target file on failure,
  2062         // independently of which code path failed.  In some cases, the
  2063         // component executing the download may have already removed the file.
  2064         try {
  2065           yield OS.File.remove(this.download.target.path);
  2066         } catch (e2) {
  2067           // If we failed during the operation, we report the error but use the
  2068           // original one as the failure reason of the download.  Note that on
  2069           // Windows we may get an access denied error instead of a no such file
  2070           // error if the file existed before, and was recently deleted.
  2071           if (!(e2 instanceof OS.File.Error &&
  2072                 (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
  2073             Cu.reportError(e2);
  2076         // In case the operation failed, ensure we stop downloading data.  Since
  2077         // we never re-enter this function, deferCanceled is always available.
  2078         this.deferCanceled.resolve();
  2079         throw ex;
  2080       } finally {
  2081         // We don't need the reference to the request anymore.  We must also set
  2082         // deferCanceled to null in order to free any indirect references it
  2083         // may hold to the request.
  2084         this.request = null;
  2085         this.deferCanceled = null;
  2086         // Allow the download to restart through a DownloadCopySaver.
  2087         this.firstExecutionFinished = true;
  2089     }.bind(this));
  2090   },
  2092   /**
  2093    * Implements "DownloadSaver.cancel".
  2094    */
  2095   cancel: function DLS_cancel()
  2097     // We may be using a DownloadCopySaver to handle resuming.
  2098     if (this.copySaver) {
  2099       return this.copySaver.cancel.apply(this.copySaver, arguments);
  2102     // If the download hasn't stopped already, resolve deferCanceled so that the
  2103     // operation is canceled as soon as a cancellation handler is registered.
  2104     // Note that the handler might not have been registered yet.
  2105     if (this.deferCanceled) {
  2106       this.deferCanceled.resolve();
  2108   },
  2110   /**
  2111    * Implements "DownloadSaver.removePartialData".
  2112    */
  2113   removePartialData: function ()
  2115     // DownloadCopySaver and DownloadLeagcySaver use the same logic for removing
  2116     // partially downloaded data, though this implementation isn't shared by
  2117     // other saver types, thus it isn't found on their shared prototype.
  2118     return DownloadCopySaver.prototype.removePartialData.call(this);
  2119   },
  2121   /**
  2122    * Implements "DownloadSaver.toSerializable".
  2123    */
  2124   toSerializable: function ()
  2126     // This object depends on legacy components that are created externally,
  2127     // thus it cannot be rebuilt during deserialization.  To support resuming
  2128     // across different browser sessions, this object is transformed into a
  2129     // DownloadCopySaver for the purpose of serialization.
  2130     return DownloadCopySaver.prototype.toSerializable.call(this);
  2131   },
  2133   /**
  2134    * Implements "DownloadSaver.getSha256Hash".
  2135    */
  2136   getSha256Hash: function ()
  2138     if (this.copySaver) {
  2139       return this.copySaver.getSha256Hash();
  2141     return this._sha256Hash;
  2142   },
  2144   /**
  2145    * Called by the nsITransfer implementation when the hash is available.
  2146    */
  2147   setSha256Hash: function (hash)
  2149     this._sha256Hash = hash;
  2150   },
  2152   /**
  2153    * Implements "DownloadSaver.getSignatureInfo".
  2154    */
  2155   getSignatureInfo: function ()
  2157     if (this.copySaver) {
  2158       return this.copySaver.getSignatureInfo();
  2160     return this._signatureInfo;
  2161   },
  2163   /**
  2164    * Called by the nsITransfer implementation when the hash is available.
  2165    */
  2166   setSignatureInfo: function (signatureInfo)
  2168     this._signatureInfo = signatureInfo;
  2169   },
  2170 };
  2172 /**
  2173  * Returns a new DownloadLegacySaver object.  This saver type has a
  2174  * deserializable form only when creating a new object in memory, because it
  2175  * cannot be serialized to disk.
  2176  */
  2177 this.DownloadLegacySaver.fromSerializable = function () {
  2178   return new DownloadLegacySaver();
  2179 };

mercurial