toolkit/components/jsdownloads/src/DownloadCore.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2179 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +/**
    1.11 + * This file includes the following constructors and global objects:
    1.12 + *
    1.13 + * Download
    1.14 + * Represents a single download, with associated state and actions.  This object
    1.15 + * is transient, though it can be included in a DownloadList so that it can be
    1.16 + * managed by the user interface and persisted across sessions.
    1.17 + *
    1.18 + * DownloadSource
    1.19 + * Represents the source of a download, for example a document or an URI.
    1.20 + *
    1.21 + * DownloadTarget
    1.22 + * Represents the target of a download, for example a file in the global
    1.23 + * downloads directory, or a file in the system temporary directory.
    1.24 + *
    1.25 + * DownloadError
    1.26 + * Provides detailed information about a download failure.
    1.27 + *
    1.28 + * DownloadSaver
    1.29 + * Template for an object that actually transfers the data for the download.
    1.30 + *
    1.31 + * DownloadCopySaver
    1.32 + * Saver object that simply copies the entire source file to the target.
    1.33 + *
    1.34 + * DownloadLegacySaver
    1.35 + * Saver object that integrates with the legacy nsITransfer interface.
    1.36 + */
    1.37 +
    1.38 +"use strict";
    1.39 +
    1.40 +this.EXPORTED_SYMBOLS = [
    1.41 +  "Download",
    1.42 +  "DownloadSource",
    1.43 +  "DownloadTarget",
    1.44 +  "DownloadError",
    1.45 +  "DownloadSaver",
    1.46 +  "DownloadCopySaver",
    1.47 +  "DownloadLegacySaver",
    1.48 +];
    1.49 +
    1.50 +////////////////////////////////////////////////////////////////////////////////
    1.51 +//// Globals
    1.52 +
    1.53 +const Cc = Components.classes;
    1.54 +const Ci = Components.interfaces;
    1.55 +const Cu = Components.utils;
    1.56 +const Cr = Components.results;
    1.57 +
    1.58 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.59 +
    1.60 +XPCOMUtils.defineLazyModuleGetter(this, "DownloadIntegration",
    1.61 +                                  "resource://gre/modules/DownloadIntegration.jsm");
    1.62 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    1.63 +                                  "resource://gre/modules/FileUtils.jsm");
    1.64 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    1.65 +                                  "resource://gre/modules/NetUtil.jsm");
    1.66 +XPCOMUtils.defineLazyModuleGetter(this, "OS",
    1.67 +                                  "resource://gre/modules/osfile.jsm")
    1.68 +XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    1.69 +                                  "resource://gre/modules/Promise.jsm");
    1.70 +XPCOMUtils.defineLazyModuleGetter(this, "Services",
    1.71 +                                  "resource://gre/modules/Services.jsm");
    1.72 +XPCOMUtils.defineLazyModuleGetter(this, "Task",
    1.73 +                                  "resource://gre/modules/Task.jsm");
    1.74 +
    1.75 +XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
    1.76 +           "@mozilla.org/browser/download-history;1",
    1.77 +           Ci.nsIDownloadHistory);
    1.78 +XPCOMUtils.defineLazyServiceGetter(this, "gExternalAppLauncher",
    1.79 +           "@mozilla.org/uriloader/external-helper-app-service;1",
    1.80 +           Ci.nsPIExternalAppLauncher);
    1.81 +XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
    1.82 +           "@mozilla.org/uriloader/external-helper-app-service;1",
    1.83 +           Ci.nsIExternalHelperAppService);
    1.84 +
    1.85 +const BackgroundFileSaverStreamListener = Components.Constructor(
    1.86 +      "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
    1.87 +      "nsIBackgroundFileSaver");
    1.88 +
    1.89 +/**
    1.90 + * Returns true if the given value is a primitive string or a String object.
    1.91 + */
    1.92 +function isString(aValue) {
    1.93 +  // We cannot use the "instanceof" operator reliably across module boundaries.
    1.94 +  return (typeof aValue == "string") ||
    1.95 +         (typeof aValue == "object" && "charAt" in aValue);
    1.96 +}
    1.97 +
    1.98 +/**
    1.99 + * Serialize the unknown properties of aObject into aSerializable.
   1.100 + */
   1.101 +function serializeUnknownProperties(aObject, aSerializable)
   1.102 +{
   1.103 +  if (aObject._unknownProperties) {
   1.104 +    for (let property in aObject._unknownProperties) {
   1.105 +      aSerializable[property] = aObject._unknownProperties[property];
   1.106 +    }
   1.107 +  }
   1.108 +}
   1.109 +
   1.110 +/**
   1.111 + * Check for any unknown properties in aSerializable and preserve those in the
   1.112 + * _unknownProperties field of aObject. aFilterFn is called for each property
   1.113 + * name of aObject and should return true only for unknown properties.
   1.114 + */
   1.115 +function deserializeUnknownProperties(aObject, aSerializable, aFilterFn)
   1.116 +{
   1.117 +  for (let property in aSerializable) {
   1.118 +    if (aFilterFn(property)) {
   1.119 +      if (!aObject._unknownProperties) {
   1.120 +        aObject._unknownProperties = { };
   1.121 +      }
   1.122 +
   1.123 +      aObject._unknownProperties[property] = aSerializable[property];
   1.124 +    }
   1.125 +  }
   1.126 +}
   1.127 +
   1.128 +/**
   1.129 + * This determines the minimum time interval between updates to the number of
   1.130 + * bytes transferred, and is a limiting factor to the sequence of readings used
   1.131 + * in calculating the speed of the download.
   1.132 + */
   1.133 +const kProgressUpdateIntervalMs = 400;
   1.134 +
   1.135 +////////////////////////////////////////////////////////////////////////////////
   1.136 +//// Download
   1.137 +
   1.138 +/**
   1.139 + * Represents a single download, with associated state and actions.  This object
   1.140 + * is transient, though it can be included in a DownloadList so that it can be
   1.141 + * managed by the user interface and persisted across sessions.
   1.142 + */
   1.143 +this.Download = function ()
   1.144 +{
   1.145 +  this._deferSucceeded = Promise.defer();
   1.146 +}
   1.147 +
   1.148 +this.Download.prototype = {
   1.149 +  /**
   1.150 +   * DownloadSource object associated with this download.
   1.151 +   */
   1.152 +  source: null,
   1.153 +
   1.154 +  /**
   1.155 +   * DownloadTarget object associated with this download.
   1.156 +   */
   1.157 +  target: null,
   1.158 +
   1.159 +  /**
   1.160 +   * DownloadSaver object associated with this download.
   1.161 +   */
   1.162 +  saver: null,
   1.163 +
   1.164 +  /**
   1.165 +   * Indicates that the download never started, has been completed successfully,
   1.166 +   * failed, or has been canceled.  This property becomes false when a download
   1.167 +   * is started for the first time, or when a failed or canceled download is
   1.168 +   * restarted.
   1.169 +   */
   1.170 +  stopped: true,
   1.171 +
   1.172 +  /**
   1.173 +   * Indicates that the download has been completed successfully.
   1.174 +   */
   1.175 +  succeeded: false,
   1.176 +
   1.177 +  /**
   1.178 +   * Indicates that the download has been canceled.  This property can become
   1.179 +   * true, then it can be reset to false when a canceled download is restarted.
   1.180 +   *
   1.181 +   * This property becomes true as soon as the "cancel" method is called, though
   1.182 +   * the "stopped" property might remain false until the cancellation request
   1.183 +   * has been processed.  Temporary files or part files may still exist even if
   1.184 +   * they are expected to be deleted, until the "stopped" property becomes true.
   1.185 +   */
   1.186 +  canceled: false,
   1.187 +
   1.188 +  /**
   1.189 +   * When the download fails, this is set to a DownloadError instance indicating
   1.190 +   * the cause of the failure.  If the download has been completed successfully
   1.191 +   * or has been canceled, this property is null.  This property is reset to
   1.192 +   * null when a failed download is restarted.
   1.193 +   */
   1.194 +  error: null,
   1.195 +
   1.196 +  /**
   1.197 +   * Indicates the start time of the download.  When the download starts,
   1.198 +   * this property is set to a valid Date object.  The default value is null
   1.199 +   * before the download starts.
   1.200 +   */
   1.201 +  startTime: null,
   1.202 +
   1.203 +  /**
   1.204 +   * Indicates whether this download's "progress" property is able to report
   1.205 +   * partial progress while the download proceeds, and whether the value in
   1.206 +   * totalBytes is relevant.  This depends on the saver and the download source.
   1.207 +   */
   1.208 +  hasProgress: false,
   1.209 +
   1.210 +  /**
   1.211 +   * Progress percent, from 0 to 100.  Intermediate values are reported only if
   1.212 +   * hasProgress is true.
   1.213 +   *
   1.214 +   * @note You shouldn't rely on this property being equal to 100 to determine
   1.215 +   *       whether the download is completed.  You should use the individual
   1.216 +   *       state properties instead.
   1.217 +   */
   1.218 +  progress: 0,
   1.219 +
   1.220 +  /**
   1.221 +   * When hasProgress is true, indicates the total number of bytes to be
   1.222 +   * transferred before the download finishes, that can be zero for empty files.
   1.223 +   *
   1.224 +   * When hasProgress is false, this property is always zero.
   1.225 +   */
   1.226 +  totalBytes: 0,
   1.227 +
   1.228 +  /**
   1.229 +   * Number of bytes currently transferred.  This value starts at zero, and may
   1.230 +   * be updated regardless of the value of hasProgress.
   1.231 +   *
   1.232 +   * @note You shouldn't rely on this property being equal to totalBytes to
   1.233 +   *       determine whether the download is completed.  You should use the
   1.234 +   *       individual state properties instead.
   1.235 +   */
   1.236 +  currentBytes: 0,
   1.237 +
   1.238 +  /**
   1.239 +   * Fractional number representing the speed of the download, in bytes per
   1.240 +   * second.  This value is zero when the download is stopped, and may be
   1.241 +   * updated regardless of the value of hasProgress.
   1.242 +   */
   1.243 +  speed: 0,
   1.244 +
   1.245 +  /**
   1.246 +   * Indicates whether, at this time, there is any partially downloaded data
   1.247 +   * that can be used when restarting a failed or canceled download.
   1.248 +   *
   1.249 +   * This property is relevant while the download is in progress, and also if it
   1.250 +   * failed or has been canceled.  If the download has been completed
   1.251 +   * successfully, this property is always false.
   1.252 +   *
   1.253 +   * Whether partial data can actually be retained depends on the saver and the
   1.254 +   * download source, and may not be known before the download is started.
   1.255 +   */
   1.256 +  hasPartialData: false,
   1.257 +
   1.258 +  /**
   1.259 +   * This can be set to a function that is called after other properties change.
   1.260 +   */
   1.261 +  onchange: null,
   1.262 +
   1.263 +  /**
   1.264 +   * This tells if the user has chosen to open/run the downloaded file after
   1.265 +   * download has completed.
   1.266 +   */
   1.267 +  launchWhenSucceeded: false,
   1.268 +
   1.269 +  /**
   1.270 +   * This represents the MIME type of the download.
   1.271 +   */
   1.272 +  contentType: null,
   1.273 +
   1.274 +  /**
   1.275 +   * This indicates the path of the application to be used to launch the file,
   1.276 +   * or null if the file should be launched with the default application.
   1.277 +   */
   1.278 +  launcherPath: null,
   1.279 +
   1.280 +  /**
   1.281 +   * Raises the onchange notification.
   1.282 +   */
   1.283 +  _notifyChange: function D_notifyChange() {
   1.284 +    try {
   1.285 +      if (this.onchange) {
   1.286 +        this.onchange();
   1.287 +      }
   1.288 +    } catch (ex) {
   1.289 +      Cu.reportError(ex);
   1.290 +    }
   1.291 +  },
   1.292 +
   1.293 +  /**
   1.294 +   * The download may be stopped and restarted multiple times before it
   1.295 +   * completes successfully. This may happen if any of the download attempts is
   1.296 +   * canceled or fails.
   1.297 +   *
   1.298 +   * This property contains a promise that is linked to the current attempt, or
   1.299 +   * null if the download is either stopped or in the process of being canceled.
   1.300 +   * If the download restarts, this property is replaced with a new promise.
   1.301 +   *
   1.302 +   * The promise is resolved if the attempt it represents finishes successfully,
   1.303 +   * and rejected if the attempt fails.
   1.304 +   */
   1.305 +  _currentAttempt: null,
   1.306 +
   1.307 +  /**
   1.308 +   * Starts the download for the first time, or restarts a download that failed
   1.309 +   * or has been canceled.
   1.310 +   *
   1.311 +   * Calling this method when the download has been completed successfully has
   1.312 +   * no effect, and the method returns a resolved promise.  If the download is
   1.313 +   * in progress, the method returns the same promise as the previous call.
   1.314 +   *
   1.315 +   * If the "cancel" method was called but the cancellation process has not
   1.316 +   * finished yet, this method waits for the cancellation to finish, then
   1.317 +   * restarts the download immediately.
   1.318 +   *
   1.319 +   * @note If you need to start a new download from the same source, rather than
   1.320 +   *       restarting a failed or canceled one, you should create a separate
   1.321 +   *       Download object with the same source as the current one.
   1.322 +   *
   1.323 +   * @return {Promise}
   1.324 +   * @resolves When the download has finished successfully.
   1.325 +   * @rejects JavaScript exception if the download failed.
   1.326 +   */
   1.327 +  start: function D_start()
   1.328 +  {
   1.329 +    // If the download succeeded, it's the final state, we have nothing to do.
   1.330 +    if (this.succeeded) {
   1.331 +      return Promise.resolve();
   1.332 +    }
   1.333 +
   1.334 +    // If the download already started and hasn't failed or hasn't been
   1.335 +    // canceled, return the same promise as the previous call, allowing the
   1.336 +    // caller to wait for the current attempt to finish.
   1.337 +    if (this._currentAttempt) {
   1.338 +      return this._currentAttempt;
   1.339 +    }
   1.340 +
   1.341 +    // While shutting down or disposing of this object, we prevent the download
   1.342 +    // from returning to be in progress.
   1.343 +    if (this._finalized) {
   1.344 +      return Promise.reject(new DownloadError({
   1.345 +                                message: "Cannot start after finalization."}));
   1.346 +    }
   1.347 +
   1.348 +    // Initialize all the status properties for a new or restarted download.
   1.349 +    this.stopped = false;
   1.350 +    this.canceled = false;
   1.351 +    this.error = null;
   1.352 +    this.hasProgress = false;
   1.353 +    this.progress = 0;
   1.354 +    this.totalBytes = 0;
   1.355 +    this.currentBytes = 0;
   1.356 +    this.startTime = new Date();
   1.357 +
   1.358 +    // Create a new deferred object and an associated promise before starting
   1.359 +    // the actual download.  We store it on the download as the current attempt.
   1.360 +    let deferAttempt = Promise.defer();
   1.361 +    let currentAttempt = deferAttempt.promise;
   1.362 +    this._currentAttempt = currentAttempt;
   1.363 +
   1.364 +    // Restart the progress and speed calculations from scratch.
   1.365 +    this._lastProgressTimeMs = 0;
   1.366 +
   1.367 +    // This function propagates progress from the DownloadSaver object, unless
   1.368 +    // it comes in late from a download attempt that was replaced by a new one.
   1.369 +    // If the cancellation process for the download has started, then the update
   1.370 +    // is ignored.
   1.371 +    function DS_setProgressBytes(aCurrentBytes, aTotalBytes, aHasPartialData)
   1.372 +    {
   1.373 +      if (this._currentAttempt == currentAttempt) {
   1.374 +        this._setBytes(aCurrentBytes, aTotalBytes, aHasPartialData);
   1.375 +      }
   1.376 +    }
   1.377 +
   1.378 +    // This function propagates download properties from the DownloadSaver
   1.379 +    // object, unless it comes in late from a download attempt that was
   1.380 +    // replaced by a new one.  If the cancellation process for the download has
   1.381 +    // started, then the update is ignored.
   1.382 +    function DS_setProperties(aOptions)
   1.383 +    {
   1.384 +      if (this._currentAttempt != currentAttempt) {
   1.385 +        return;
   1.386 +      }
   1.387 +
   1.388 +      let changeMade = false;
   1.389 +
   1.390 +      if ("contentType" in aOptions &&
   1.391 +          this.contentType != aOptions.contentType) {
   1.392 +        this.contentType = aOptions.contentType;
   1.393 +        changeMade = true;
   1.394 +      }
   1.395 +
   1.396 +      if (changeMade) {
   1.397 +        this._notifyChange();
   1.398 +      }
   1.399 +    }
   1.400 +
   1.401 +    // Now that we stored the promise in the download object, we can start the
   1.402 +    // task that will actually execute the download.
   1.403 +    deferAttempt.resolve(Task.spawn(function task_D_start() {
   1.404 +      // Wait upon any pending operation before restarting.
   1.405 +      if (this._promiseCanceled) {
   1.406 +        yield this._promiseCanceled;
   1.407 +      }
   1.408 +      if (this._promiseRemovePartialData) {
   1.409 +        try {
   1.410 +          yield this._promiseRemovePartialData;
   1.411 +        } catch (ex) {
   1.412 +          // Ignore any errors, which are already reported by the original
   1.413 +          // caller of the removePartialData method.
   1.414 +        }
   1.415 +      }
   1.416 +
   1.417 +      // In case the download was restarted while cancellation was in progress,
   1.418 +      // but the previous attempt actually succeeded before cancellation could
   1.419 +      // be processed, it is possible that the download has already finished.
   1.420 +      if (this.succeeded) {
   1.421 +        return;
   1.422 +      }
   1.423 +
   1.424 +      try {
   1.425 +        // Disallow download if parental controls service restricts it.
   1.426 +        if (yield DownloadIntegration.shouldBlockForParentalControls(this)) {
   1.427 +          throw new DownloadError({ becauseBlockedByParentalControls: true });
   1.428 +        }
   1.429 +
   1.430 +        // We should check if we have been canceled in the meantime, after all
   1.431 +        // the previous asynchronous operations have been executed and just
   1.432 +        // before we call the "execute" method of the saver.
   1.433 +        if (this._promiseCanceled) {
   1.434 +          // The exception will become a cancellation in the "catch" block.
   1.435 +          throw undefined;
   1.436 +        }
   1.437 +
   1.438 +        // Execute the actual download through the saver object.
   1.439 +        this._saverExecuting = true;
   1.440 +        yield this.saver.execute(DS_setProgressBytes.bind(this),
   1.441 +                                 DS_setProperties.bind(this));
   1.442 +
   1.443 +        // Check for application reputation, which requires the entire file to
   1.444 +        // be downloaded.  After that, check for the last time if the download
   1.445 +        // has been canceled.  Both cases require the target file to be deleted,
   1.446 +        // thus we process both in the same block of code.
   1.447 +        if ((yield DownloadIntegration.shouldBlockForReputationCheck(this)) ||
   1.448 +            this._promiseCanceled) {
   1.449 +          try {
   1.450 +            yield OS.File.remove(this.target.path);
   1.451 +          } catch (ex) {
   1.452 +            Cu.reportError(ex);
   1.453 +          }
   1.454 +          // If this is actually a cancellation, this exception will be changed
   1.455 +          // in the catch block below.
   1.456 +          throw new DownloadError({ becauseBlockedByReputationCheck: true });
   1.457 +        }
   1.458 +
   1.459 +        // Update the status properties for a successful download.
   1.460 +        this.progress = 100;
   1.461 +        this.succeeded = true;
   1.462 +        this.hasPartialData = false;
   1.463 +      } catch (ex) {
   1.464 +        // Fail with a generic status code on cancellation, so that the caller
   1.465 +        // is forced to actually check the status properties to see if the
   1.466 +        // download was canceled or failed because of other reasons.
   1.467 +        if (this._promiseCanceled) {
   1.468 +          throw new DownloadError({ message: "Download canceled." });
   1.469 +        }
   1.470 +
   1.471 +        // An HTTP 450 error code is used by Windows to indicate that a uri is
   1.472 +        // blocked by parental controls. This will prevent the download from
   1.473 +        // occuring, so an error needs to be raised. This is not performed
   1.474 +        // during the parental controls check above as it requires the request
   1.475 +        // to start.
   1.476 +        if (this._blockedByParentalControls) {
   1.477 +          ex = new DownloadError({ becauseBlockedByParentalControls: true });
   1.478 +        }
   1.479 +
   1.480 +        // Update the download error, unless a new attempt already started. The
   1.481 +        // change in the status property is notified in the finally block.
   1.482 +        if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
   1.483 +          this.error = ex;
   1.484 +        }
   1.485 +        throw ex;
   1.486 +      } finally {
   1.487 +        // Any cancellation request has now been processed.
   1.488 +        this._saverExecuting = false;
   1.489 +        this._promiseCanceled = null;
   1.490 +
   1.491 +        // Update the status properties, unless a new attempt already started.
   1.492 +        if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
   1.493 +          this._currentAttempt = null;
   1.494 +          this.stopped = true;
   1.495 +          this.speed = 0;
   1.496 +          this._notifyChange();
   1.497 +          if (this.succeeded) {
   1.498 +            yield DownloadIntegration.downloadDone(this);
   1.499 +
   1.500 +            this._deferSucceeded.resolve();
   1.501 +
   1.502 +            if (this.launchWhenSucceeded) {
   1.503 +              this.launch().then(null, Cu.reportError);
   1.504 +
   1.505 +              // Always schedule files to be deleted at the end of the private browsing
   1.506 +              // mode, regardless of the value of the pref.
   1.507 +              if (this.source.isPrivate) {
   1.508 +                gExternalAppLauncher.deleteTemporaryPrivateFileWhenPossible(
   1.509 +                                     new FileUtils.File(this.target.path));
   1.510 +              } else if (Services.prefs.getBoolPref(
   1.511 +                          "browser.helperApps.deleteTempFileOnExit")) {
   1.512 +                gExternalAppLauncher.deleteTemporaryFileOnExit(
   1.513 +                                     new FileUtils.File(this.target.path));
   1.514 +              }
   1.515 +            }
   1.516 +          }
   1.517 +        }
   1.518 +      }
   1.519 +    }.bind(this)));
   1.520 +
   1.521 +    // Notify the new download state before returning.
   1.522 +    this._notifyChange();
   1.523 +    return currentAttempt;
   1.524 +  },
   1.525 +
   1.526 +  /*
   1.527 +   * Launches the file after download has completed. This can open
   1.528 +   * the file with the default application for the target MIME type
   1.529 +   * or file extension, or with a custom application if launcherPath
   1.530 +   * is set.
   1.531 +   *
   1.532 +   * @return {Promise}
   1.533 +   * @resolves When the instruction to launch the file has been
   1.534 +   *           successfully given to the operating system. Note that
   1.535 +   *           the OS might still take a while until the file is actually
   1.536 +   *           launched.
   1.537 +   * @rejects  JavaScript exception if there was an error trying to launch
   1.538 +   *           the file.
   1.539 +   */
   1.540 +  launch: function() {
   1.541 +    if (!this.succeeded) {
   1.542 +      return Promise.reject(
   1.543 +        new Error("launch can only be called if the download succeeded")
   1.544 +      );
   1.545 +    }
   1.546 +
   1.547 +    return DownloadIntegration.launchDownload(this);
   1.548 +  },
   1.549 +
   1.550 +  /*
   1.551 +   * Shows the folder containing the target file, or where the target file
   1.552 +   * will be saved. This may be called at any time, even if the download
   1.553 +   * failed or is currently in progress.
   1.554 +   *
   1.555 +   * @return {Promise}
   1.556 +   * @resolves When the instruction to open the containing folder has been
   1.557 +   *           successfully given to the operating system. Note that
   1.558 +   *           the OS might still take a while until the folder is actually
   1.559 +   *           opened.
   1.560 +   * @rejects  JavaScript exception if there was an error trying to open
   1.561 +   *           the containing folder.
   1.562 +   */
   1.563 +  showContainingDirectory: function D_showContainingDirectory() {
   1.564 +    return DownloadIntegration.showContainingDirectory(this.target.path);
   1.565 +  },
   1.566 +
   1.567 +  /**
   1.568 +   * When a request to cancel the download is received, contains a promise that
   1.569 +   * will be resolved when the cancellation request is processed.  When the
   1.570 +   * request is processed, this property becomes null again.
   1.571 +   */
   1.572 +  _promiseCanceled: null,
   1.573 +
   1.574 +  /**
   1.575 +   * True between the call to the "execute" method of the saver and the
   1.576 +   * completion of the current download attempt.
   1.577 +   */
   1.578 +  _saverExecuting: false,
   1.579 +
   1.580 +  /**
   1.581 +   * Cancels the download.
   1.582 +   *
   1.583 +   * The cancellation request is asynchronous.  Until the cancellation process
   1.584 +   * finishes, temporary files or part files may still exist even if they are
   1.585 +   * expected to be deleted.
   1.586 +   *
   1.587 +   * In case the download completes successfully before the cancellation request
   1.588 +   * could be processed, this method has no effect, and it returns a resolved
   1.589 +   * promise.  You should check the properties of the download at the time the
   1.590 +   * returned promise is resolved to determine if the download was cancelled.
   1.591 +   *
   1.592 +   * Calling this method when the download has been completed successfully,
   1.593 +   * failed, or has been canceled has no effect, and the method returns a
   1.594 +   * resolved promise.  This behavior is designed for the case where the call
   1.595 +   * to "cancel" happens asynchronously, and is consistent with the case where
   1.596 +   * the cancellation request could not be processed in time.
   1.597 +   *
   1.598 +   * @return {Promise}
   1.599 +   * @resolves When the cancellation process has finished.
   1.600 +   * @rejects Never.
   1.601 +   */
   1.602 +  cancel: function D_cancel()
   1.603 +  {
   1.604 +    // If the download is currently stopped, we have nothing to do.
   1.605 +    if (this.stopped) {
   1.606 +      return Promise.resolve();
   1.607 +    }
   1.608 +
   1.609 +    if (!this._promiseCanceled) {
   1.610 +      // Start a new cancellation request.
   1.611 +      let deferCanceled = Promise.defer();
   1.612 +      this._currentAttempt.then(function () deferCanceled.resolve(),
   1.613 +                                function () deferCanceled.resolve());
   1.614 +      this._promiseCanceled = deferCanceled.promise;
   1.615 +
   1.616 +      // The download can already be restarted.
   1.617 +      this._currentAttempt = null;
   1.618 +
   1.619 +      // Notify that the cancellation request was received.
   1.620 +      this.canceled = true;
   1.621 +      this._notifyChange();
   1.622 +
   1.623 +      // Execute the actual cancellation through the saver object, in case it
   1.624 +      // has already started.  Otherwise, the cancellation will be handled just
   1.625 +      // before the saver is started.
   1.626 +      if (this._saverExecuting) {
   1.627 +        this.saver.cancel();
   1.628 +      }
   1.629 +    }
   1.630 +
   1.631 +    return this._promiseCanceled;
   1.632 +  },
   1.633 +
   1.634 +  /**
   1.635 +   * Indicates whether any partially downloaded data should be retained, to use
   1.636 +   * when restarting a failed or canceled download.  The default is false.
   1.637 +   *
   1.638 +   * Whether partial data can actually be retained depends on the saver and the
   1.639 +   * download source, and may not be known before the download is started.
   1.640 +   *
   1.641 +   * To have any effect, this property must be set before starting the download.
   1.642 +   * Resetting this property to false after the download has already started
   1.643 +   * will not remove any partial data.
   1.644 +   *
   1.645 +   * If this property is set to true, care should be taken that partial data is
   1.646 +   * removed before the reference to the download is discarded.  This can be
   1.647 +   * done using the removePartialData or the "finalize" methods.
   1.648 +   */
   1.649 +  tryToKeepPartialData: false,
   1.650 +
   1.651 +  /**
   1.652 +   * When a request to remove partially downloaded data is received, contains a
   1.653 +   * promise that will be resolved when the removal request is processed.  When
   1.654 +   * the request is processed, this property becomes null again.
   1.655 +   */
   1.656 +  _promiseRemovePartialData: null,
   1.657 +
   1.658 +  /**
   1.659 +   * Removes any partial data kept as part of a canceled or failed download.
   1.660 +   *
   1.661 +   * If the download is not canceled or failed, this method has no effect, and
   1.662 +   * it returns a resolved promise.  If the "cancel" method was called but the
   1.663 +   * cancellation process has not finished yet, this method waits for the
   1.664 +   * cancellation to finish, then removes the partial data.
   1.665 +   *
   1.666 +   * After this method has been called, if the tryToKeepPartialData property is
   1.667 +   * still true when the download is restarted, partial data will be retained
   1.668 +   * during the new download attempt.
   1.669 +   *
   1.670 +   * @return {Promise}
   1.671 +   * @resolves When the partial data has been successfully removed.
   1.672 +   * @rejects JavaScript exception if the operation could not be completed.
   1.673 +   */
   1.674 +  removePartialData: function ()
   1.675 +  {
   1.676 +    if (!this.canceled && !this.error) {
   1.677 +      return Promise.resolve();
   1.678 +    }
   1.679 +
   1.680 +    let promiseRemovePartialData = this._promiseRemovePartialData;
   1.681 +
   1.682 +    if (!promiseRemovePartialData) {
   1.683 +      let deferRemovePartialData = Promise.defer();
   1.684 +      promiseRemovePartialData = deferRemovePartialData.promise;
   1.685 +      this._promiseRemovePartialData = promiseRemovePartialData;
   1.686 +
   1.687 +      deferRemovePartialData.resolve(
   1.688 +        Task.spawn(function task_D_removePartialData() {
   1.689 +          try {
   1.690 +            // Wait upon any pending cancellation request.
   1.691 +            if (this._promiseCanceled) {
   1.692 +              yield this._promiseCanceled;
   1.693 +            }
   1.694 +            // Ask the saver object to remove any partial data.
   1.695 +            yield this.saver.removePartialData();
   1.696 +            // For completeness, clear the number of bytes transferred.
   1.697 +            if (this.currentBytes != 0 || this.hasPartialData) {
   1.698 +              this.currentBytes = 0;
   1.699 +              this.hasPartialData = false;
   1.700 +              this._notifyChange();
   1.701 +            }
   1.702 +          } finally {
   1.703 +            this._promiseRemovePartialData = null;
   1.704 +          }
   1.705 +        }.bind(this)));
   1.706 +    }
   1.707 +
   1.708 +    return promiseRemovePartialData;
   1.709 +  },
   1.710 +
   1.711 +  /**
   1.712 +   * This deferred object contains a promise that is resolved as soon as this
   1.713 +   * download finishes successfully, and is never rejected.  This property is
   1.714 +   * initialized when the download is created, and never changes.
   1.715 +   */
   1.716 +  _deferSucceeded: null,
   1.717 +
   1.718 +  /**
   1.719 +   * Returns a promise that is resolved as soon as this download finishes
   1.720 +   * successfully, even if the download was stopped and restarted meanwhile.
   1.721 +   *
   1.722 +   * You can use this property for scheduling download completion actions in the
   1.723 +   * current session, for downloads that are controlled interactively.  If the
   1.724 +   * download is not controlled interactively, you should use the promise
   1.725 +   * returned by the "start" method instead, to check for success or failure.
   1.726 +   *
   1.727 +   * @return {Promise}
   1.728 +   * @resolves When the download has finished successfully.
   1.729 +   * @rejects Never.
   1.730 +   */
   1.731 +  whenSucceeded: function D_whenSucceeded()
   1.732 +  {
   1.733 +    return this._deferSucceeded.promise;
   1.734 +  },
   1.735 +
   1.736 +  /**
   1.737 +   * Updates the state of a finished, failed, or canceled download based on the
   1.738 +   * current state in the file system.  If the download is in progress or it has
   1.739 +   * been finalized, this method has no effect, and it returns a resolved
   1.740 +   * promise.
   1.741 +   *
   1.742 +   * This allows the properties of the download to be updated in case the user
   1.743 +   * moved or deleted the target file or its associated ".part" file.
   1.744 +   *
   1.745 +   * @return {Promise}
   1.746 +   * @resolves When the operation has completed.
   1.747 +   * @rejects Never.
   1.748 +   */
   1.749 +  refresh: function ()
   1.750 +  {
   1.751 +    return Task.spawn(function () {
   1.752 +      if (!this.stopped || this._finalized) {
   1.753 +        return;
   1.754 +      }
   1.755 +
   1.756 +      // Update the current progress from disk if we retained partial data.
   1.757 +      if (this.hasPartialData && this.target.partFilePath) {
   1.758 +        let stat = yield OS.File.stat(this.target.partFilePath);
   1.759 +
   1.760 +        // Ignore the result if the state has changed meanwhile.
   1.761 +        if (!this.stopped || this._finalized) {
   1.762 +          return;
   1.763 +        }
   1.764 +
   1.765 +        // Update the bytes transferred and the related progress properties.
   1.766 +        this.currentBytes = stat.size;
   1.767 +        if (this.totalBytes > 0) {
   1.768 +          this.hasProgress = true;
   1.769 +          this.progress = Math.floor(this.currentBytes /
   1.770 +                                         this.totalBytes * 100);
   1.771 +        }
   1.772 +        this._notifyChange();
   1.773 +      }
   1.774 +    }.bind(this)).then(null, Cu.reportError);
   1.775 +  },
   1.776 +
   1.777 +  /**
   1.778 +   * True if the "finalize" method has been called.  This prevents the download
   1.779 +   * from starting again after having been stopped.
   1.780 +   */
   1.781 +  _finalized: false,
   1.782 +
   1.783 +  /**
   1.784 +   * Ensures that the download is stopped, and optionally removes any partial
   1.785 +   * data kept as part of a canceled or failed download.  After this method has
   1.786 +   * been called, the download cannot be started again.
   1.787 +   *
   1.788 +   * This method should be used in place of "cancel" and removePartialData while
   1.789 +   * shutting down or disposing of the download object, to prevent other callers
   1.790 +   * from interfering with the operation.  This is required because cancellation
   1.791 +   * and other operations are asynchronous.
   1.792 +   *
   1.793 +   * @param aRemovePartialData
   1.794 +   *        Whether any partially downloaded data should be removed after the
   1.795 +   *        download has been stopped.
   1.796 +   *
   1.797 +   * @return {Promise}
   1.798 +   * @resolves When the operation has finished successfully.
   1.799 +   * @rejects JavaScript exception if an error occurred while removing the
   1.800 +   *          partially downloaded data.
   1.801 +   */
   1.802 +  finalize: function (aRemovePartialData)
   1.803 +  {
   1.804 +    // Prevents the download from starting again after having been stopped.
   1.805 +    this._finalized = true;
   1.806 +
   1.807 +    if (aRemovePartialData) {
   1.808 +      // Cancel the download, in case it is currently in progress, then remove
   1.809 +      // any partially downloaded data.  The removal operation waits for
   1.810 +      // cancellation to be completed before resolving the promise it returns.
   1.811 +      this.cancel();
   1.812 +      return this.removePartialData();
   1.813 +    } else {
   1.814 +      // Just cancel the download, in case it is currently in progress.
   1.815 +      return this.cancel();
   1.816 +    }
   1.817 +  },
   1.818 +
   1.819 +  /**
   1.820 +   * Indicates the time of the last progress notification, expressed as the
   1.821 +   * number of milliseconds since January 1, 1970, 00:00:00 UTC.  This is zero
   1.822 +   * until some bytes have actually been transferred.
   1.823 +   */
   1.824 +  _lastProgressTimeMs: 0,
   1.825 +
   1.826 +  /**
   1.827 +   * Updates progress notifications based on the number of bytes transferred.
   1.828 +   *
   1.829 +   * The number of bytes transferred is not updated unless enough time passed
   1.830 +   * since this function was last called.  This limits the computation load, in
   1.831 +   * particular when the listeners update the user interface in response.
   1.832 +   *
   1.833 +   * @param aCurrentBytes
   1.834 +   *        Number of bytes transferred until now.
   1.835 +   * @param aTotalBytes
   1.836 +   *        Total number of bytes to be transferred, or -1 if unknown.
   1.837 +   * @param aHasPartialData
   1.838 +   *        Indicates whether the partially downloaded data can be used when
   1.839 +   *        restarting the download if it fails or is canceled.
   1.840 +   */
   1.841 +  _setBytes: function D_setBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
   1.842 +    let changeMade = (this.hasPartialData != aHasPartialData);
   1.843 +    this.hasPartialData = aHasPartialData;
   1.844 +
   1.845 +    // Unless aTotalBytes is -1, we can report partial download progress.  In
   1.846 +    // this case, notify when the related properties changed since last time.
   1.847 +    if (aTotalBytes != -1 && (!this.hasProgress ||
   1.848 +                              this.totalBytes != aTotalBytes)) {
   1.849 +      this.hasProgress = true;
   1.850 +      this.totalBytes = aTotalBytes;
   1.851 +      changeMade = true;
   1.852 +    }
   1.853 +
   1.854 +    // Updating the progress and computing the speed require that enough time
   1.855 +    // passed since the last update, or that we haven't started throttling yet.
   1.856 +    let currentTimeMs = Date.now();
   1.857 +    let intervalMs = currentTimeMs - this._lastProgressTimeMs;
   1.858 +    if (intervalMs >= kProgressUpdateIntervalMs) {
   1.859 +      // Don't compute the speed unless we started throttling notifications.
   1.860 +      if (this._lastProgressTimeMs != 0) {
   1.861 +        // Calculate the speed in bytes per second.
   1.862 +        let rawSpeed = (aCurrentBytes - this.currentBytes) / intervalMs * 1000;
   1.863 +        if (this.speed == 0) {
   1.864 +          // When the previous speed is exactly zero instead of a fractional
   1.865 +          // number, this can be considered the first element of the series.
   1.866 +          this.speed = rawSpeed;
   1.867 +        } else {
   1.868 +          // Apply exponential smoothing, with a smoothing factor of 0.1.
   1.869 +          this.speed = rawSpeed * 0.1 + this.speed * 0.9;
   1.870 +        }
   1.871 +      }
   1.872 +
   1.873 +      // Start throttling notifications only when we have actually received some
   1.874 +      // bytes for the first time.  The timing of the first part of the download
   1.875 +      // is not reliable, due to possible latency in the initial notifications.
   1.876 +      // This also allows automated tests to receive and verify the number of
   1.877 +      // bytes initially transferred.
   1.878 +      if (aCurrentBytes > 0) {
   1.879 +        this._lastProgressTimeMs = currentTimeMs;
   1.880 +
   1.881 +        // Update the progress now that we don't need its previous value.
   1.882 +        this.currentBytes = aCurrentBytes;
   1.883 +        if (this.totalBytes > 0) {
   1.884 +          this.progress = Math.floor(this.currentBytes / this.totalBytes * 100);
   1.885 +        }
   1.886 +        changeMade = true;
   1.887 +      }
   1.888 +    }
   1.889 +
   1.890 +    if (changeMade) {
   1.891 +      this._notifyChange();
   1.892 +    }
   1.893 +  },
   1.894 +
   1.895 +  /**
   1.896 +   * Returns a static representation of the current object state.
   1.897 +   *
   1.898 +   * @return A JavaScript object that can be serialized to JSON.
   1.899 +   */
   1.900 +  toSerializable: function ()
   1.901 +  {
   1.902 +    let serializable = {
   1.903 +      source: this.source.toSerializable(),
   1.904 +      target: this.target.toSerializable(),
   1.905 +    };
   1.906 +
   1.907 +    // Simplify the representation for the most common saver type.  If the saver
   1.908 +    // is an object instead of a simple string, we can't simplify it because we
   1.909 +    // need to persist all its properties, not only "type".  This may happen for
   1.910 +    // savers of type "copy" as well as other types.
   1.911 +    let saver = this.saver.toSerializable();
   1.912 +    if (saver !== "copy") {
   1.913 +      serializable.saver = saver;
   1.914 +    }
   1.915 +
   1.916 +    if (this.error && ("message" in this.error)) {
   1.917 +      serializable.error = { message: this.error.message };
   1.918 +    }
   1.919 +
   1.920 +    if (this.startTime) {
   1.921 +      serializable.startTime = this.startTime.toJSON();
   1.922 +    }
   1.923 +
   1.924 +    // These are serialized unless they are false, null, or empty strings.
   1.925 +    for (let property of kSerializableDownloadProperties) {
   1.926 +      if (property != "error" && property != "startTime" && this[property]) {
   1.927 +        serializable[property] = this[property];
   1.928 +      }
   1.929 +    }
   1.930 +
   1.931 +    serializeUnknownProperties(this, serializable);
   1.932 +
   1.933 +    return serializable;
   1.934 +  },
   1.935 +
   1.936 +  /**
   1.937 +   * Returns a value that changes only when one of the properties of a Download
   1.938 +   * object that should be saved into a file also change.  This excludes
   1.939 +   * properties whose value doesn't usually change during the download lifetime.
   1.940 +   *
   1.941 +   * This function is used to determine whether the download should be
   1.942 +   * serialized after a property change notification has been received.
   1.943 +   *
   1.944 +   * @return String representing the relevant download state.
   1.945 +   */
   1.946 +  getSerializationHash: function ()
   1.947 +  {
   1.948 +    // The "succeeded", "canceled", "error", and startTime properties are not
   1.949 +    // taken into account because they all change before the "stopped" property
   1.950 +    // changes, and are not altered in other cases.
   1.951 +    return this.stopped + "," + this.totalBytes + "," + this.hasPartialData +
   1.952 +           "," + this.contentType;
   1.953 +  },
   1.954 +};
   1.955 +
   1.956 +/**
   1.957 + * Defines which properties of the Download object are serializable.
   1.958 + */
   1.959 +const kSerializableDownloadProperties = [
   1.960 +  "succeeded",
   1.961 +  "canceled",
   1.962 +  "error",
   1.963 +  "totalBytes",
   1.964 +  "hasPartialData",
   1.965 +  "tryToKeepPartialData",
   1.966 +  "launcherPath",
   1.967 +  "launchWhenSucceeded",
   1.968 +  "contentType",
   1.969 +];
   1.970 +
   1.971 +/**
   1.972 + * Creates a new Download object from a serializable representation.  This
   1.973 + * function is used by the createDownload method of Downloads.jsm when a new
   1.974 + * Download object is requested, thus some properties may refer to live objects
   1.975 + * in place of their serializable representations.
   1.976 + *
   1.977 + * @param aSerializable
   1.978 + *        An object with the following fields:
   1.979 + *        {
   1.980 + *          source: DownloadSource object, or its serializable representation.
   1.981 + *                  See DownloadSource.fromSerializable for details.
   1.982 + *          target: DownloadTarget object, or its serializable representation.
   1.983 + *                  See DownloadTarget.fromSerializable for details.
   1.984 + *          saver: Serializable representation of a DownloadSaver object.  See
   1.985 + *                 DownloadSaver.fromSerializable for details.  If omitted,
   1.986 + *                 defaults to "copy".
   1.987 + *        }
   1.988 + *
   1.989 + * @return The newly created Download object.
   1.990 + */
   1.991 +Download.fromSerializable = function (aSerializable) {
   1.992 +  let download = new Download();
   1.993 +  if (aSerializable.source instanceof DownloadSource) {
   1.994 +    download.source = aSerializable.source;
   1.995 +  } else {
   1.996 +    download.source = DownloadSource.fromSerializable(aSerializable.source);
   1.997 +  }
   1.998 +  if (aSerializable.target instanceof DownloadTarget) {
   1.999 +    download.target = aSerializable.target;
  1.1000 +  } else {
  1.1001 +    download.target = DownloadTarget.fromSerializable(aSerializable.target);
  1.1002 +  }
  1.1003 +  if ("saver" in aSerializable) {
  1.1004 +    download.saver = DownloadSaver.fromSerializable(aSerializable.saver);
  1.1005 +  } else {
  1.1006 +    download.saver = DownloadSaver.fromSerializable("copy");
  1.1007 +  }
  1.1008 +  download.saver.download = download;
  1.1009 +
  1.1010 +  if ("startTime" in aSerializable) {
  1.1011 +    let time = aSerializable.startTime.getTime
  1.1012 +             ? aSerializable.startTime.getTime()
  1.1013 +             : aSerializable.startTime;
  1.1014 +    download.startTime = new Date(time);
  1.1015 +  }
  1.1016 +
  1.1017 +  for (let property of kSerializableDownloadProperties) {
  1.1018 +    if (property in aSerializable) {
  1.1019 +      download[property] = aSerializable[property];
  1.1020 +    }
  1.1021 +  }
  1.1022 +
  1.1023 +  deserializeUnknownProperties(download, aSerializable, property =>
  1.1024 +    kSerializableDownloadProperties.indexOf(property) == -1 &&
  1.1025 +    property != "startTime" &&
  1.1026 +    property != "source" &&
  1.1027 +    property != "target" &&
  1.1028 +    property != "saver");
  1.1029 +
  1.1030 +  return download;
  1.1031 +};
  1.1032 +
  1.1033 +////////////////////////////////////////////////////////////////////////////////
  1.1034 +//// DownloadSource
  1.1035 +
  1.1036 +/**
  1.1037 + * Represents the source of a download, for example a document or an URI.
  1.1038 + */
  1.1039 +this.DownloadSource = function () {}
  1.1040 +
  1.1041 +this.DownloadSource.prototype = {
  1.1042 +  /**
  1.1043 +   * String containing the URI for the download source.
  1.1044 +   */
  1.1045 +  url: null,
  1.1046 +
  1.1047 +  /**
  1.1048 +   * Indicates whether the download originated from a private window.  This
  1.1049 +   * determines the context of the network request that is made to retrieve the
  1.1050 +   * resource.
  1.1051 +   */
  1.1052 +  isPrivate: false,
  1.1053 +
  1.1054 +  /**
  1.1055 +   * String containing the referrer URI of the download source, or null if no
  1.1056 +   * referrer should be sent or the download source is not HTTP.
  1.1057 +   */
  1.1058 +  referrer: null,
  1.1059 +
  1.1060 +  /**
  1.1061 +   * Returns a static representation of the current object state.
  1.1062 +   *
  1.1063 +   * @return A JavaScript object that can be serialized to JSON.
  1.1064 +   */
  1.1065 +  toSerializable: function ()
  1.1066 +  {
  1.1067 +    // Simplify the representation if we don't have other details.
  1.1068 +    if (!this.isPrivate && !this.referrer && !this._unknownProperties) {
  1.1069 +      return this.url;
  1.1070 +    }
  1.1071 +
  1.1072 +    let serializable = { url: this.url };
  1.1073 +    if (this.isPrivate) {
  1.1074 +      serializable.isPrivate = true;
  1.1075 +    }
  1.1076 +    if (this.referrer) {
  1.1077 +      serializable.referrer = this.referrer;
  1.1078 +    }
  1.1079 +
  1.1080 +    serializeUnknownProperties(this, serializable);
  1.1081 +    return serializable;
  1.1082 +  },
  1.1083 +};
  1.1084 +
  1.1085 +/**
  1.1086 + * Creates a new DownloadSource object from its serializable representation.
  1.1087 + *
  1.1088 + * @param aSerializable
  1.1089 + *        Serializable representation of a DownloadSource object.  This may be a
  1.1090 + *        string containing the URI for the download source, an nsIURI, or an
  1.1091 + *        object with the following properties:
  1.1092 + *        {
  1.1093 + *          url: String containing the URI for the download source.
  1.1094 + *          isPrivate: Indicates whether the download originated from a private
  1.1095 + *                     window.  If omitted, the download is public.
  1.1096 + *          referrer: String containing the referrer URI of the download source.
  1.1097 + *                    Can be omitted or null if no referrer should be sent or
  1.1098 + *                    the download source is not HTTP.
  1.1099 + *        }
  1.1100 + *
  1.1101 + * @return The newly created DownloadSource object.
  1.1102 + */
  1.1103 +this.DownloadSource.fromSerializable = function (aSerializable) {
  1.1104 +  let source = new DownloadSource();
  1.1105 +  if (isString(aSerializable)) {
  1.1106 +    // Convert String objects to primitive strings at this point.
  1.1107 +    source.url = aSerializable.toString();
  1.1108 +  } else if (aSerializable instanceof Ci.nsIURI) {
  1.1109 +    source.url = aSerializable.spec;
  1.1110 +  } else {
  1.1111 +    // Convert String objects to primitive strings at this point.
  1.1112 +    source.url = aSerializable.url.toString();
  1.1113 +    if ("isPrivate" in aSerializable) {
  1.1114 +      source.isPrivate = aSerializable.isPrivate;
  1.1115 +    }
  1.1116 +    if ("referrer" in aSerializable) {
  1.1117 +      source.referrer = aSerializable.referrer;
  1.1118 +    }
  1.1119 +
  1.1120 +    deserializeUnknownProperties(source, aSerializable, property =>
  1.1121 +      property != "url" && property != "isPrivate" && property != "referrer");
  1.1122 +  }
  1.1123 +
  1.1124 +  return source;
  1.1125 +};
  1.1126 +
  1.1127 +////////////////////////////////////////////////////////////////////////////////
  1.1128 +//// DownloadTarget
  1.1129 +
  1.1130 +/**
  1.1131 + * Represents the target of a download, for example a file in the global
  1.1132 + * downloads directory, or a file in the system temporary directory.
  1.1133 + */
  1.1134 +this.DownloadTarget = function () {}
  1.1135 +
  1.1136 +this.DownloadTarget.prototype = {
  1.1137 +  /**
  1.1138 +   * String containing the path of the target file.
  1.1139 +   */
  1.1140 +  path: null,
  1.1141 +
  1.1142 +  /**
  1.1143 +   * String containing the path of the ".part" file containing the data
  1.1144 +   * downloaded so far, or null to disable the use of a ".part" file to keep
  1.1145 +   * partially downloaded data.
  1.1146 +   */
  1.1147 +  partFilePath: null,
  1.1148 +
  1.1149 +  /**
  1.1150 +   * Returns a static representation of the current object state.
  1.1151 +   *
  1.1152 +   * @return A JavaScript object that can be serialized to JSON.
  1.1153 +   */
  1.1154 +  toSerializable: function ()
  1.1155 +  {
  1.1156 +    // Simplify the representation if we don't have other details.
  1.1157 +    if (!this.partFilePath && !this._unknownProperties) {
  1.1158 +      return this.path;
  1.1159 +    }
  1.1160 +
  1.1161 +    let serializable = { path: this.path,
  1.1162 +                         partFilePath: this.partFilePath };
  1.1163 +    serializeUnknownProperties(this, serializable);
  1.1164 +    return serializable;
  1.1165 +  },
  1.1166 +};
  1.1167 +
  1.1168 +/**
  1.1169 + * Creates a new DownloadTarget object from its serializable representation.
  1.1170 + *
  1.1171 + * @param aSerializable
  1.1172 + *        Serializable representation of a DownloadTarget object.  This may be a
  1.1173 + *        string containing the path of the target file, an nsIFile, or an
  1.1174 + *        object with the following properties:
  1.1175 + *        {
  1.1176 + *          path: String containing the path of the target file.
  1.1177 + *          partFilePath: optional string containing the part file path.
  1.1178 + *        }
  1.1179 + *
  1.1180 + * @return The newly created DownloadTarget object.
  1.1181 + */
  1.1182 +this.DownloadTarget.fromSerializable = function (aSerializable) {
  1.1183 +  let target = new DownloadTarget();
  1.1184 +  if (isString(aSerializable)) {
  1.1185 +    // Convert String objects to primitive strings at this point.
  1.1186 +    target.path = aSerializable.toString();
  1.1187 +  } else if (aSerializable instanceof Ci.nsIFile) {
  1.1188 +    // Read the "path" property of nsIFile after checking the object type.
  1.1189 +    target.path = aSerializable.path;
  1.1190 +  } else {
  1.1191 +    // Read the "path" property of the serializable DownloadTarget
  1.1192 +    // representation, converting String objects to primitive strings.
  1.1193 +    target.path = aSerializable.path.toString();
  1.1194 +    if ("partFilePath" in aSerializable) {
  1.1195 +      target.partFilePath = aSerializable.partFilePath;
  1.1196 +    }
  1.1197 +
  1.1198 +    deserializeUnknownProperties(target, aSerializable, property =>
  1.1199 +      property != "path" && property != "partFilePath");
  1.1200 +  }
  1.1201 +  return target;
  1.1202 +};
  1.1203 +
  1.1204 +////////////////////////////////////////////////////////////////////////////////
  1.1205 +//// DownloadError
  1.1206 +
  1.1207 +/**
  1.1208 + * Provides detailed information about a download failure.
  1.1209 + *
  1.1210 + * @param aProperties
  1.1211 + *        Object which may contain any of the following properties:
  1.1212 + *          {
  1.1213 + *            result: Result error code, defaulting to Cr.NS_ERROR_FAILURE
  1.1214 + *            message: String error message to be displayed, or null to use the
  1.1215 + *                     message associated with the result code.
  1.1216 + *            inferCause: If true, attempts to determine if the cause of the
  1.1217 + *                        download is a network failure or a local file failure,
  1.1218 + *                        based on a set of known values of the result code.
  1.1219 + *                        This is useful when the error is received by a
  1.1220 + *                        component that handles both aspects of the download.
  1.1221 + *          }
  1.1222 + *        The properties object may also contain any of the DownloadError's
  1.1223 + *        because properties, which will be set accordingly in the error object.
  1.1224 + */
  1.1225 +this.DownloadError = function (aProperties)
  1.1226 +{
  1.1227 +  const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
  1.1228 +  const NS_ERROR_MODULE_NETWORK = 6;
  1.1229 +  const NS_ERROR_MODULE_FILES = 13;
  1.1230 +
  1.1231 +  // Set the error name used by the Error object prototype first.
  1.1232 +  this.name = "DownloadError";
  1.1233 +  this.result = aProperties.result || Cr.NS_ERROR_FAILURE;
  1.1234 +  if (aProperties.message) {
  1.1235 +    this.message = aProperties.message;
  1.1236 +  } else if (aProperties.becauseBlocked ||
  1.1237 +             aProperties.becauseBlockedByParentalControls ||
  1.1238 +             aProperties.becauseBlockedByReputationCheck) {
  1.1239 +    this.message = "Download blocked.";
  1.1240 +  } else {
  1.1241 +    let exception = new Components.Exception("", this.result);
  1.1242 +    this.message = exception.toString();
  1.1243 +  }
  1.1244 +  if (aProperties.inferCause) {
  1.1245 +    let module = ((this.result & 0x7FFF0000) >> 16) -
  1.1246 +                 NS_ERROR_MODULE_BASE_OFFSET;
  1.1247 +    this.becauseSourceFailed = (module == NS_ERROR_MODULE_NETWORK);
  1.1248 +    this.becauseTargetFailed = (module == NS_ERROR_MODULE_FILES);
  1.1249 +  }
  1.1250 +  else {
  1.1251 +    if (aProperties.becauseSourceFailed) {
  1.1252 +      this.becauseSourceFailed = true;
  1.1253 +    }
  1.1254 +    if (aProperties.becauseTargetFailed) {
  1.1255 +      this.becauseTargetFailed = true;
  1.1256 +    }
  1.1257 +  }
  1.1258 +
  1.1259 +  if (aProperties.becauseBlockedByParentalControls) {
  1.1260 +    this.becauseBlocked = true;
  1.1261 +    this.becauseBlockedByParentalControls = true;
  1.1262 +  } else if (aProperties.becauseBlockedByReputationCheck) {
  1.1263 +    this.becauseBlocked = true;
  1.1264 +    this.becauseBlockedByReputationCheck = true;
  1.1265 +  } else if (aProperties.becauseBlocked) {
  1.1266 +    this.becauseBlocked = true;
  1.1267 +  }
  1.1268 +
  1.1269 +  this.stack = new Error().stack;
  1.1270 +}
  1.1271 +
  1.1272 +this.DownloadError.prototype = {
  1.1273 +  __proto__: Error.prototype,
  1.1274 +
  1.1275 +  /**
  1.1276 +   * The result code associated with this error.
  1.1277 +   */
  1.1278 +  result: false,
  1.1279 +
  1.1280 +  /**
  1.1281 +   * Indicates an error occurred while reading from the remote location.
  1.1282 +   */
  1.1283 +  becauseSourceFailed: false,
  1.1284 +
  1.1285 +  /**
  1.1286 +   * Indicates an error occurred while writing to the local target.
  1.1287 +   */
  1.1288 +  becauseTargetFailed: false,
  1.1289 +
  1.1290 +  /**
  1.1291 +   * Indicates the download failed because it was blocked.  If the reason for
  1.1292 +   * blocking is known, the corresponding property will be also set.
  1.1293 +   */
  1.1294 +  becauseBlocked: false,
  1.1295 +
  1.1296 +  /**
  1.1297 +   * Indicates the download was blocked because downloads are globally
  1.1298 +   * disallowed by the Parental Controls or Family Safety features on Windows.
  1.1299 +   */
  1.1300 +  becauseBlockedByParentalControls: false,
  1.1301 +
  1.1302 +  /**
  1.1303 +   * Indicates the download was blocked because it failed the reputation check
  1.1304 +   * and may be malware.
  1.1305 +   */
  1.1306 +  becauseBlockedByReputationCheck: false,
  1.1307 +};
  1.1308 +
  1.1309 +////////////////////////////////////////////////////////////////////////////////
  1.1310 +//// DownloadSaver
  1.1311 +
  1.1312 +/**
  1.1313 + * Template for an object that actually transfers the data for the download.
  1.1314 + */
  1.1315 +this.DownloadSaver = function () {}
  1.1316 +
  1.1317 +this.DownloadSaver.prototype = {
  1.1318 +  /**
  1.1319 +   * Download object for raising notifications and reading properties.
  1.1320 +   *
  1.1321 +   * If the tryToKeepPartialData property of the download object is false, the
  1.1322 +   * saver should never try to keep partially downloaded data if the download
  1.1323 +   * fails.
  1.1324 +   */
  1.1325 +  download: null,
  1.1326 +
  1.1327 +  /**
  1.1328 +   * Executes the download.
  1.1329 +   *
  1.1330 +   * @param aSetProgressBytesFn
  1.1331 +   *        This function may be called by the saver to report progress. It
  1.1332 +   *        takes three arguments: the first is the number of bytes transferred
  1.1333 +   *        until now, the second is the total number of bytes to be
  1.1334 +   *        transferred (or -1 if unknown), the third indicates whether the
  1.1335 +   *        partially downloaded data can be used when restarting the download
  1.1336 +   *        if it fails or is canceled.
  1.1337 +   * @param aSetPropertiesFn
  1.1338 +   *        This function may be called by the saver to report information
  1.1339 +   *        about new download properties discovered by the saver during the
  1.1340 +   *        download process. It takes an object where the keys represents
  1.1341 +   *        the names of the properties to set, and the value represents the
  1.1342 +   *        value to set.
  1.1343 +   *
  1.1344 +   * @return {Promise}
  1.1345 +   * @resolves When the download has finished successfully.
  1.1346 +   * @rejects JavaScript exception if the download failed.
  1.1347 +   */
  1.1348 +  execute: function DS_execute(aSetProgressBytesFn, aSetPropertiesFn)
  1.1349 +  {
  1.1350 +    throw new Error("Not implemented.");
  1.1351 +  },
  1.1352 +
  1.1353 +  /**
  1.1354 +   * Cancels the download.
  1.1355 +   */
  1.1356 +  cancel: function DS_cancel()
  1.1357 +  {
  1.1358 +    throw new Error("Not implemented.");
  1.1359 +  },
  1.1360 +
  1.1361 +  /**
  1.1362 +   * Removes any partial data kept as part of a canceled or failed download.
  1.1363 +   *
  1.1364 +   * This method is never called until the promise returned by "execute" is
  1.1365 +   * either resolved or rejected, and the "execute" method is not called again
  1.1366 +   * until the promise returned by this method is resolved or rejected.
  1.1367 +   *
  1.1368 +   * @return {Promise}
  1.1369 +   * @resolves When the operation has finished successfully.
  1.1370 +   * @rejects JavaScript exception.
  1.1371 +   */
  1.1372 +  removePartialData: function DS_removePartialData()
  1.1373 +  {
  1.1374 +    return Promise.resolve();
  1.1375 +  },
  1.1376 +
  1.1377 +  /**
  1.1378 +   * This can be called by the saver implementation when the download is already
  1.1379 +   * started, to add it to the browsing history.  This method has no effect if
  1.1380 +   * the download is private.
  1.1381 +   */
  1.1382 +  addToHistory: function ()
  1.1383 +  {
  1.1384 +    if (this.download.source.isPrivate) {
  1.1385 +      return;
  1.1386 +    }
  1.1387 +
  1.1388 +    let sourceUri = NetUtil.newURI(this.download.source.url);
  1.1389 +    let referrer = this.download.source.referrer;
  1.1390 +    let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
  1.1391 +    let targetUri = NetUtil.newURI(new FileUtils.File(
  1.1392 +                                       this.download.target.path));
  1.1393 +
  1.1394 +    // The start time is always available when we reach this point.
  1.1395 +    let startPRTime = this.download.startTime.getTime() * 1000;
  1.1396 +
  1.1397 +    gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
  1.1398 +                                 targetUri);
  1.1399 +  },
  1.1400 +
  1.1401 +  /**
  1.1402 +   * Returns a static representation of the current object state.
  1.1403 +   *
  1.1404 +   * @return A JavaScript object that can be serialized to JSON.
  1.1405 +   */
  1.1406 +  toSerializable: function ()
  1.1407 +  {
  1.1408 +    throw new Error("Not implemented.");
  1.1409 +  },
  1.1410 +
  1.1411 +  /**
  1.1412 +   * Returns the SHA-256 hash of the downloaded file, if it exists.
  1.1413 +   */
  1.1414 +  getSha256Hash: function ()
  1.1415 +  {
  1.1416 +    throw new Error("Not implemented.");
  1.1417 +  },
  1.1418 +
  1.1419 +  getSignatureInfo: function ()
  1.1420 +  {
  1.1421 +    throw new Error("Not implemented.");
  1.1422 +  },
  1.1423 +}; // DownloadSaver
  1.1424 +
  1.1425 +/**
  1.1426 + * Creates a new DownloadSaver object from its serializable representation.
  1.1427 + *
  1.1428 + * @param aSerializable
  1.1429 + *        Serializable representation of a DownloadSaver object.  If no initial
  1.1430 + *        state information for the saver object is needed, can be a string
  1.1431 + *        representing the class of the download operation, for example "copy".
  1.1432 + *
  1.1433 + * @return The newly created DownloadSaver object.
  1.1434 + */
  1.1435 +this.DownloadSaver.fromSerializable = function (aSerializable) {
  1.1436 +  let serializable = isString(aSerializable) ? { type: aSerializable }
  1.1437 +                                             : aSerializable;
  1.1438 +  let saver;
  1.1439 +  switch (serializable.type) {
  1.1440 +    case "copy":
  1.1441 +      saver = DownloadCopySaver.fromSerializable(serializable);
  1.1442 +      break;
  1.1443 +    case "legacy":
  1.1444 +      saver = DownloadLegacySaver.fromSerializable(serializable);
  1.1445 +      break;
  1.1446 +    default:
  1.1447 +      throw new Error("Unrecoginzed download saver type.");
  1.1448 +  }
  1.1449 +  return saver;
  1.1450 +};
  1.1451 +
  1.1452 +////////////////////////////////////////////////////////////////////////////////
  1.1453 +//// DownloadCopySaver
  1.1454 +
  1.1455 +/**
  1.1456 + * Saver object that simply copies the entire source file to the target.
  1.1457 + */
  1.1458 +this.DownloadCopySaver = function () {}
  1.1459 +
  1.1460 +this.DownloadCopySaver.prototype = {
  1.1461 +  __proto__: DownloadSaver.prototype,
  1.1462 +
  1.1463 +  /**
  1.1464 +   * BackgroundFileSaver object currently handling the download.
  1.1465 +   */
  1.1466 +  _backgroundFileSaver: null,
  1.1467 +
  1.1468 +  /**
  1.1469 +   * Indicates whether the "cancel" method has been called.  This is used to
  1.1470 +   * prevent the request from starting in case the operation is canceled before
  1.1471 +   * the BackgroundFileSaver instance has been created.
  1.1472 +   */
  1.1473 +  _canceled: false,
  1.1474 +
  1.1475 +  /**
  1.1476 +   * Save the SHA-256 hash in raw bytes of the downloaded file. This is null
  1.1477 +   * unless BackgroundFileSaver has successfully completed saving the file.
  1.1478 +   */
  1.1479 +  _sha256Hash: null,
  1.1480 +
  1.1481 +  /**
  1.1482 +   * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
  1.1483 +   * if the file is signed. This is empty if the file is unsigned, and null
  1.1484 +   * unless BackgroundFileSaver has successfully completed saving the file.
  1.1485 +   */
  1.1486 +  _signatureInfo: null,
  1.1487 +
  1.1488 +  /**
  1.1489 +   * True if the associated download has already been added to browsing history.
  1.1490 +   */
  1.1491 +  alreadyAddedToHistory: false,
  1.1492 +
  1.1493 +  /**
  1.1494 +   * String corresponding to the entityID property of the nsIResumableChannel
  1.1495 +   * used to execute the download, or null if the channel was not resumable or
  1.1496 +   * the saver was instructed not to keep partially downloaded data.
  1.1497 +   */
  1.1498 +  entityID: null,
  1.1499 +
  1.1500 +  /**
  1.1501 +   * Implements "DownloadSaver.execute".
  1.1502 +   */
  1.1503 +  execute: function DCS_execute(aSetProgressBytesFn, aSetPropertiesFn)
  1.1504 +  {
  1.1505 +    let copySaver = this;
  1.1506 +
  1.1507 +    this._canceled = false;
  1.1508 +
  1.1509 +    let download = this.download;
  1.1510 +    let targetPath = download.target.path;
  1.1511 +    let partFilePath = download.target.partFilePath;
  1.1512 +    let keepPartialData = download.tryToKeepPartialData;
  1.1513 +
  1.1514 +    return Task.spawn(function task_DCS_execute() {
  1.1515 +      // Add the download to history the first time it is started in this
  1.1516 +      // session.  If the download is restarted in a different session, a new
  1.1517 +      // history visit will be added.  We do this just to avoid the complexity
  1.1518 +      // of serializing this state between sessions, since adding a new visit
  1.1519 +      // does not have any noticeable side effect.
  1.1520 +      if (!this.alreadyAddedToHistory) {
  1.1521 +        this.addToHistory();
  1.1522 +        this.alreadyAddedToHistory = true;
  1.1523 +      }
  1.1524 +
  1.1525 +      // To reduce the chance that other downloads reuse the same final target
  1.1526 +      // file name, we should create a placeholder as soon as possible, before
  1.1527 +      // starting the network request.  The placeholder is also required in case
  1.1528 +      // we are using a ".part" file instead of the final target while the
  1.1529 +      // download is in progress.
  1.1530 +      try {
  1.1531 +        // If the file already exists, don't delete its contents yet.
  1.1532 +        let file = yield OS.File.open(targetPath, { write: true });
  1.1533 +        yield file.close();
  1.1534 +      } catch (ex if ex instanceof OS.File.Error) {
  1.1535 +        // Throw a DownloadError indicating that the operation failed because of
  1.1536 +        // the target file.  We cannot translate this into a specific result
  1.1537 +        // code, but we preserve the original message using the toString method.
  1.1538 +        let error = new DownloadError({ message: ex.toString() });
  1.1539 +        error.becauseTargetFailed = true;
  1.1540 +        throw error;
  1.1541 +      }
  1.1542 +
  1.1543 +      try {
  1.1544 +        let deferSaveComplete = Promise.defer();
  1.1545 +
  1.1546 +        if (this._canceled) {
  1.1547 +          // Don't create the BackgroundFileSaver object if we have been
  1.1548 +          // canceled meanwhile.
  1.1549 +          throw new DownloadError({ message: "Saver canceled." });
  1.1550 +        }
  1.1551 +
  1.1552 +        // Create the object that will save the file in a background thread.
  1.1553 +        let backgroundFileSaver = new BackgroundFileSaverStreamListener();
  1.1554 +        try {
  1.1555 +          // When the operation completes, reflect the status in the promise
  1.1556 +          // returned by this download execution function.
  1.1557 +          backgroundFileSaver.observer = {
  1.1558 +            onTargetChange: function () { },
  1.1559 +            onSaveComplete: (aSaver, aStatus) => {
  1.1560 +              // Send notifications now that we can restart if needed.
  1.1561 +              if (Components.isSuccessCode(aStatus)) {
  1.1562 +                // Save the hash before freeing backgroundFileSaver.
  1.1563 +                this._sha256Hash = aSaver.sha256Hash;
  1.1564 +                this._signatureInfo = aSaver.signatureInfo;
  1.1565 +                deferSaveComplete.resolve();
  1.1566 +              } else {
  1.1567 +                // Infer the origin of the error from the failure code, because
  1.1568 +                // BackgroundFileSaver does not provide more specific data.
  1.1569 +                let properties = { result: aStatus, inferCause: true };
  1.1570 +                deferSaveComplete.reject(new DownloadError(properties));
  1.1571 +              }
  1.1572 +              // Free the reference cycle, to release resources earlier.
  1.1573 +              backgroundFileSaver.observer = null;
  1.1574 +              this._backgroundFileSaver = null;
  1.1575 +            },
  1.1576 +          };
  1.1577 +
  1.1578 +          // Create a channel from the source, and listen to progress
  1.1579 +          // notifications.
  1.1580 +          let channel = NetUtil.newChannel(NetUtil.newURI(download.source.url));
  1.1581 +          if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
  1.1582 +            channel.setPrivate(download.source.isPrivate);
  1.1583 +          }
  1.1584 +          if (channel instanceof Ci.nsIHttpChannel &&
  1.1585 +              download.source.referrer) {
  1.1586 +            channel.referrer = NetUtil.newURI(download.source.referrer);
  1.1587 +          }
  1.1588 +
  1.1589 +          // If we have data that we can use to resume the download from where
  1.1590 +          // it stopped, try to use it.
  1.1591 +          let resumeAttempted = false;
  1.1592 +          let resumeFromBytes = 0;
  1.1593 +          if (channel instanceof Ci.nsIResumableChannel && this.entityID &&
  1.1594 +              partFilePath && keepPartialData) {
  1.1595 +            try {
  1.1596 +              let stat = yield OS.File.stat(partFilePath);
  1.1597 +              channel.resumeAt(stat.size, this.entityID);
  1.1598 +              resumeAttempted = true;
  1.1599 +              resumeFromBytes = stat.size;
  1.1600 +            } catch (ex if ex instanceof OS.File.Error &&
  1.1601 +                           ex.becauseNoSuchFile) { }
  1.1602 +          }
  1.1603 +
  1.1604 +          channel.notificationCallbacks = {
  1.1605 +            QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor]),
  1.1606 +            getInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink]),
  1.1607 +            onProgress: function DCSE_onProgress(aRequest, aContext, aProgress,
  1.1608 +                                                 aProgressMax)
  1.1609 +            {
  1.1610 +              let currentBytes = resumeFromBytes + aProgress;
  1.1611 +              let totalBytes = aProgressMax == -1 ? -1 : (resumeFromBytes +
  1.1612 +                                                          aProgressMax);
  1.1613 +              aSetProgressBytesFn(currentBytes, totalBytes, aProgress > 0 &&
  1.1614 +                                  partFilePath && keepPartialData);
  1.1615 +            },
  1.1616 +            onStatus: function () { },
  1.1617 +          };
  1.1618 +
  1.1619 +          // Open the channel, directing output to the background file saver.
  1.1620 +          backgroundFileSaver.QueryInterface(Ci.nsIStreamListener);
  1.1621 +          channel.asyncOpen({
  1.1622 +            onStartRequest: function (aRequest, aContext) {
  1.1623 +              backgroundFileSaver.onStartRequest(aRequest, aContext);
  1.1624 +
  1.1625 +              // Check if the request's response has been blocked by Windows
  1.1626 +              // Parental Controls with an HTTP 450 error code.
  1.1627 +              if (aRequest instanceof Ci.nsIHttpChannel &&
  1.1628 +                  aRequest.responseStatus == 450) {
  1.1629 +                // Set a flag that can be retrieved later when handling the
  1.1630 +                // cancellation so that the proper error can be thrown.
  1.1631 +                this.download._blockedByParentalControls = true;
  1.1632 +                aRequest.cancel(Cr.NS_BINDING_ABORTED);
  1.1633 +                return;
  1.1634 +              }
  1.1635 +
  1.1636 +              aSetPropertiesFn({ contentType: channel.contentType });
  1.1637 +
  1.1638 +              // Ensure we report the value of "Content-Length", if available,
  1.1639 +              // even if the download doesn't generate any progress events
  1.1640 +              // later.
  1.1641 +              if (channel.contentLength >= 0) {
  1.1642 +                aSetProgressBytesFn(0, channel.contentLength);
  1.1643 +              }
  1.1644 +
  1.1645 +              // If the URL we are downloading from includes a file extension
  1.1646 +              // that matches the "Content-Encoding" header, for example ".gz"
  1.1647 +              // with a "gzip" encoding, we should save the file in its encoded
  1.1648 +              // form.  In all other cases, we decode the body while saving.
  1.1649 +              if (channel instanceof Ci.nsIEncodedChannel &&
  1.1650 +                  channel.contentEncodings) {
  1.1651 +                let uri = channel.URI;
  1.1652 +                if (uri instanceof Ci.nsIURL && uri.fileExtension) {
  1.1653 +                  // Only the first, outermost encoding is considered.
  1.1654 +                  let encoding = channel.contentEncodings.getNext();
  1.1655 +                  if (encoding) {
  1.1656 +                    channel.applyConversion =
  1.1657 +                      gExternalHelperAppService.applyDecodingForExtension(
  1.1658 +                                                uri.fileExtension, encoding);
  1.1659 +                  }
  1.1660 +                }
  1.1661 +              }
  1.1662 +
  1.1663 +              if (keepPartialData) {
  1.1664 +                // If the source is not resumable, don't keep partial data even
  1.1665 +                // if we were asked to try and do it.
  1.1666 +                if (aRequest instanceof Ci.nsIResumableChannel) {
  1.1667 +                  try {
  1.1668 +                    // If reading the ID succeeds, the source is resumable.
  1.1669 +                    this.entityID = aRequest.entityID;
  1.1670 +                  } catch (ex if ex instanceof Components.Exception &&
  1.1671 +                                 ex.result == Cr.NS_ERROR_NOT_RESUMABLE) {
  1.1672 +                    keepPartialData = false;
  1.1673 +                  }
  1.1674 +                } else {
  1.1675 +                  keepPartialData = false;
  1.1676 +                }
  1.1677 +              }
  1.1678 +
  1.1679 +              // Enable hashing and signature verification before setting the
  1.1680 +              // target.
  1.1681 +              backgroundFileSaver.enableSha256();
  1.1682 +              backgroundFileSaver.enableSignatureInfo();
  1.1683 +              if (partFilePath) {
  1.1684 +                // If we actually resumed a request, append to the partial data.
  1.1685 +                if (resumeAttempted) {
  1.1686 +                  // TODO: Handle Cr.NS_ERROR_ENTITY_CHANGED
  1.1687 +                  backgroundFileSaver.enableAppend();
  1.1688 +                }
  1.1689 +
  1.1690 +                // Use a part file, determining if we should keep it on failure.
  1.1691 +                backgroundFileSaver.setTarget(new FileUtils.File(partFilePath),
  1.1692 +                                              keepPartialData);
  1.1693 +              } else {
  1.1694 +                // Set the final target file, and delete it on failure.
  1.1695 +                backgroundFileSaver.setTarget(new FileUtils.File(targetPath),
  1.1696 +                                              false);
  1.1697 +              }
  1.1698 +            }.bind(copySaver),
  1.1699 +
  1.1700 +            onStopRequest: function (aRequest, aContext, aStatusCode) {
  1.1701 +              try {
  1.1702 +                backgroundFileSaver.onStopRequest(aRequest, aContext,
  1.1703 +                                                  aStatusCode);
  1.1704 +              } finally {
  1.1705 +                // If the data transfer completed successfully, indicate to the
  1.1706 +                // background file saver that the operation can finish.  If the
  1.1707 +                // data transfer failed, the saver has been already stopped.
  1.1708 +                if (Components.isSuccessCode(aStatusCode)) {
  1.1709 +                  if (partFilePath) {
  1.1710 +                    // Move to the final target if we were using a part file.
  1.1711 +                    backgroundFileSaver.setTarget(
  1.1712 +                                        new FileUtils.File(targetPath), false);
  1.1713 +                  }
  1.1714 +                  backgroundFileSaver.finish(Cr.NS_OK);
  1.1715 +                }
  1.1716 +              }
  1.1717 +            }.bind(copySaver),
  1.1718 +
  1.1719 +            onDataAvailable: function (aRequest, aContext, aInputStream,
  1.1720 +                                       aOffset, aCount) {
  1.1721 +              backgroundFileSaver.onDataAvailable(aRequest, aContext,
  1.1722 +                                                  aInputStream, aOffset,
  1.1723 +                                                  aCount);
  1.1724 +            }.bind(copySaver),
  1.1725 +          }, null);
  1.1726 +
  1.1727 +          // We should check if we have been canceled in the meantime, after
  1.1728 +          // all the previous asynchronous operations have been executed and
  1.1729 +          // just before we set the _backgroundFileSaver property.
  1.1730 +          if (this._canceled) {
  1.1731 +            throw new DownloadError({ message: "Saver canceled." });
  1.1732 +          }
  1.1733 +
  1.1734 +          // If the operation succeeded, store the object to allow cancellation.
  1.1735 +          this._backgroundFileSaver = backgroundFileSaver;
  1.1736 +        } catch (ex) {
  1.1737 +          // In case an error occurs while setting up the chain of objects for
  1.1738 +          // the download, ensure that we release the resources of the saver.
  1.1739 +          backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
  1.1740 +          throw ex;
  1.1741 +        }
  1.1742 +
  1.1743 +        // We will wait on this promise in case no error occurred while setting
  1.1744 +        // up the chain of objects for the download.
  1.1745 +        yield deferSaveComplete.promise;
  1.1746 +      } catch (ex) {
  1.1747 +        // Ensure we always remove the placeholder for the final target file on
  1.1748 +        // failure, independently of which code path failed.  In some cases, the
  1.1749 +        // background file saver may have already removed the file.
  1.1750 +        try {
  1.1751 +          yield OS.File.remove(targetPath);
  1.1752 +        } catch (e2) {
  1.1753 +          // If we failed during the operation, we report the error but use the
  1.1754 +          // original one as the failure reason of the download.  Note that on
  1.1755 +          // Windows we may get an access denied error instead of a no such file
  1.1756 +          // error if the file existed before, and was recently deleted.
  1.1757 +          if (!(e2 instanceof OS.File.Error &&
  1.1758 +                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
  1.1759 +            Cu.reportError(e2);
  1.1760 +          }
  1.1761 +        }
  1.1762 +        throw ex;
  1.1763 +      }
  1.1764 +    }.bind(this));
  1.1765 +  },
  1.1766 +
  1.1767 +  /**
  1.1768 +   * Implements "DownloadSaver.cancel".
  1.1769 +   */
  1.1770 +  cancel: function DCS_cancel()
  1.1771 +  {
  1.1772 +    this._canceled = true;
  1.1773 +    if (this._backgroundFileSaver) {
  1.1774 +      this._backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
  1.1775 +      this._backgroundFileSaver = null;
  1.1776 +    }
  1.1777 +  },
  1.1778 +
  1.1779 +  /**
  1.1780 +   * Implements "DownloadSaver.removePartialData".
  1.1781 +   */
  1.1782 +  removePartialData: function ()
  1.1783 +  {
  1.1784 +    return Task.spawn(function task_DCS_removePartialData() {
  1.1785 +      if (this.download.target.partFilePath) {
  1.1786 +        try {
  1.1787 +          yield OS.File.remove(this.download.target.partFilePath);
  1.1788 +        } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { }
  1.1789 +      }
  1.1790 +    }.bind(this));
  1.1791 +  },
  1.1792 +
  1.1793 +  /**
  1.1794 +   * Implements "DownloadSaver.toSerializable".
  1.1795 +   */
  1.1796 +  toSerializable: function ()
  1.1797 +  {
  1.1798 +    // Simplify the representation if we don't have other details.
  1.1799 +    if (!this.entityID && !this._unknownProperties) {
  1.1800 +      return "copy";
  1.1801 +    }
  1.1802 +
  1.1803 +    let serializable = { type: "copy",
  1.1804 +                         entityID: this.entityID };
  1.1805 +    serializeUnknownProperties(this, serializable);
  1.1806 +    return serializable;
  1.1807 +  },
  1.1808 +
  1.1809 +  /**
  1.1810 +   * Implements "DownloadSaver.getSha256Hash"
  1.1811 +   */
  1.1812 +  getSha256Hash: function ()
  1.1813 +  {
  1.1814 +    return this._sha256Hash;
  1.1815 +  },
  1.1816 +
  1.1817 +  /*
  1.1818 +   * Implements DownloadSaver.getSignatureInfo.
  1.1819 +   */
  1.1820 +  getSignatureInfo: function ()
  1.1821 +  {
  1.1822 +    return this._signatureInfo;
  1.1823 +  }
  1.1824 +};
  1.1825 +
  1.1826 +/**
  1.1827 + * Creates a new DownloadCopySaver object, with its initial state derived from
  1.1828 + * its serializable representation.
  1.1829 + *
  1.1830 + * @param aSerializable
  1.1831 + *        Serializable representation of a DownloadCopySaver object.
  1.1832 + *
  1.1833 + * @return The newly created DownloadCopySaver object.
  1.1834 + */
  1.1835 +this.DownloadCopySaver.fromSerializable = function (aSerializable) {
  1.1836 +  let saver = new DownloadCopySaver();
  1.1837 +  if ("entityID" in aSerializable) {
  1.1838 +    saver.entityID = aSerializable.entityID;
  1.1839 +  }
  1.1840 +
  1.1841 +  deserializeUnknownProperties(saver, aSerializable, property =>
  1.1842 +    property != "entityID" && property != "type");
  1.1843 +
  1.1844 +  return saver;
  1.1845 +};
  1.1846 +
  1.1847 +////////////////////////////////////////////////////////////////////////////////
  1.1848 +//// DownloadLegacySaver
  1.1849 +
  1.1850 +/**
  1.1851 + * Saver object that integrates with the legacy nsITransfer interface.
  1.1852 + *
  1.1853 + * For more background on the process, see the DownloadLegacyTransfer object.
  1.1854 + */
  1.1855 +this.DownloadLegacySaver = function()
  1.1856 +{
  1.1857 +  this.deferExecuted = Promise.defer();
  1.1858 +  this.deferCanceled = Promise.defer();
  1.1859 +}
  1.1860 +
  1.1861 +this.DownloadLegacySaver.prototype = {
  1.1862 +  __proto__: DownloadSaver.prototype,
  1.1863 +
  1.1864 +  /**
  1.1865 +   * Save the SHA-256 hash in raw bytes of the downloaded file. This may be
  1.1866 +   * null when nsExternalHelperAppService (and thus BackgroundFileSaver) is not
  1.1867 +   * invoked.
  1.1868 +   */
  1.1869 +  _sha256Hash: null,
  1.1870 +
  1.1871 +  /**
  1.1872 +   * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
  1.1873 +   * if the file is signed. This is empty if the file is unsigned, and null
  1.1874 +   * unless BackgroundFileSaver has successfully completed saving the file.
  1.1875 +   */
  1.1876 +  _signatureInfo: null,
  1.1877 +
  1.1878 +  /**
  1.1879 +   * nsIRequest object associated to the status and progress updates we
  1.1880 +   * received.  This object is null before we receive the first status and
  1.1881 +   * progress update, and is also reset to null when the download is stopped.
  1.1882 +   */
  1.1883 +  request: null,
  1.1884 +
  1.1885 +  /**
  1.1886 +   * This deferred object contains a promise that is resolved as soon as this
  1.1887 +   * download finishes successfully, and is rejected in case the download is
  1.1888 +   * canceled or receives a failure notification through nsITransfer.
  1.1889 +   */
  1.1890 +  deferExecuted: null,
  1.1891 +
  1.1892 +  /**
  1.1893 +   * This deferred object contains a promise that is resolved if the download
  1.1894 +   * receives a cancellation request through the "cancel" method, and is never
  1.1895 +   * rejected.  The nsITransfer implementation will register a handler that
  1.1896 +   * actually causes the download cancellation.
  1.1897 +   */
  1.1898 +  deferCanceled: null,
  1.1899 +
  1.1900 +  /**
  1.1901 +   * This is populated with the value of the aSetProgressBytesFn argument of the
  1.1902 +   * "execute" method, and is null before the method is called.
  1.1903 +   */
  1.1904 +  setProgressBytesFn: null,
  1.1905 +
  1.1906 +  /**
  1.1907 +   * Called by the nsITransfer implementation while the download progresses.
  1.1908 +   *
  1.1909 +   * @param aCurrentBytes
  1.1910 +   *        Number of bytes transferred until now.
  1.1911 +   * @param aTotalBytes
  1.1912 +   *        Total number of bytes to be transferred, or -1 if unknown.
  1.1913 +   */
  1.1914 +  onProgressBytes: function DLS_onProgressBytes(aCurrentBytes, aTotalBytes)
  1.1915 +  {
  1.1916 +    // Ignore progress notifications until we are ready to process them.
  1.1917 +    if (!this.setProgressBytesFn) {
  1.1918 +      return;
  1.1919 +    }
  1.1920 +
  1.1921 +    let hasPartFile = !!this.download.target.partFilePath;
  1.1922 +
  1.1923 +    this.progressWasNotified = true;
  1.1924 +    this.setProgressBytesFn(aCurrentBytes, aTotalBytes,
  1.1925 +                            aCurrentBytes > 0 && hasPartFile);
  1.1926 +  },
  1.1927 +
  1.1928 +  /**
  1.1929 +   * Whether the onProgressBytes function has been called at least once.
  1.1930 +   */
  1.1931 +  progressWasNotified: false,
  1.1932 +
  1.1933 +  /**
  1.1934 +   * Called by the nsITransfer implementation when the request has started.
  1.1935 +   *
  1.1936 +   * @param aRequest
  1.1937 +   *        nsIRequest associated to the status update.
  1.1938 +   * @param aAlreadyAddedToHistory
  1.1939 +   *        Indicates that the nsIExternalHelperAppService component already
  1.1940 +   *        added the download to the browsing history, unless it was started
  1.1941 +   *        from a private browsing window.  When this parameter is false, the
  1.1942 +   *        download is added to the browsing history here.  Private downloads
  1.1943 +   *        are never added to history even if this parameter is false.
  1.1944 +   */
  1.1945 +  onTransferStarted: function (aRequest, aAlreadyAddedToHistory)
  1.1946 +  {
  1.1947 +    // Store the entity ID to use for resuming if required.
  1.1948 +    if (this.download.tryToKeepPartialData &&
  1.1949 +        aRequest instanceof Ci.nsIResumableChannel) {
  1.1950 +      try {
  1.1951 +        // If reading the ID succeeds, the source is resumable.
  1.1952 +        this.entityID = aRequest.entityID;
  1.1953 +      } catch (ex if ex instanceof Components.Exception &&
  1.1954 +                     ex.result == Cr.NS_ERROR_NOT_RESUMABLE) { }
  1.1955 +    }
  1.1956 +
  1.1957 +    // For legacy downloads, we must update the referrer at this time.
  1.1958 +    if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
  1.1959 +      this.download.source.referrer = aRequest.referrer.spec;
  1.1960 +    }
  1.1961 +
  1.1962 +    if (!aAlreadyAddedToHistory) {
  1.1963 +      this.addToHistory();
  1.1964 +    }
  1.1965 +  },
  1.1966 +
  1.1967 +  /**
  1.1968 +   * Called by the nsITransfer implementation when the request has finished.
  1.1969 +   *
  1.1970 +   * @param aRequest
  1.1971 +   *        nsIRequest associated to the status update.
  1.1972 +   * @param aStatus
  1.1973 +   *        Status code received by the nsITransfer implementation.
  1.1974 +   */
  1.1975 +  onTransferFinished: function DLS_onTransferFinished(aRequest, aStatus)
  1.1976 +  {
  1.1977 +    // Store a reference to the request, used when handling completion.
  1.1978 +    this.request = aRequest;
  1.1979 +
  1.1980 +    if (Components.isSuccessCode(aStatus)) {
  1.1981 +      this.deferExecuted.resolve();
  1.1982 +    } else {
  1.1983 +      // Infer the origin of the error from the failure code, because more
  1.1984 +      // specific data is not available through the nsITransfer implementation.
  1.1985 +      let properties = { result: aStatus, inferCause: true };
  1.1986 +      this.deferExecuted.reject(new DownloadError(properties));
  1.1987 +    }
  1.1988 +  },
  1.1989 +
  1.1990 +  /**
  1.1991 +   * When the first execution of the download finished, it can be restarted by
  1.1992 +   * using a DownloadCopySaver object instead of the original legacy component
  1.1993 +   * that executed the download.
  1.1994 +   */
  1.1995 +  firstExecutionFinished: false,
  1.1996 +
  1.1997 +  /**
  1.1998 +   * In case the download is restarted after the first execution finished, this
  1.1999 +   * property contains a reference to the DownloadCopySaver that is executing
  1.2000 +   * the new download attempt.
  1.2001 +   */
  1.2002 +  copySaver: null,
  1.2003 +
  1.2004 +  /**
  1.2005 +   * String corresponding to the entityID property of the nsIResumableChannel
  1.2006 +   * used to execute the download, or null if the channel was not resumable or
  1.2007 +   * the saver was instructed not to keep partially downloaded data.
  1.2008 +   */
  1.2009 +  entityID: null,
  1.2010 +
  1.2011 +  /**
  1.2012 +   * Implements "DownloadSaver.execute".
  1.2013 +   */
  1.2014 +  execute: function DLS_execute(aSetProgressBytesFn)
  1.2015 +  {
  1.2016 +    // Check if this is not the first execution of the download.  The Download
  1.2017 +    // object guarantees that this function is not re-entered during execution.
  1.2018 +    if (this.firstExecutionFinished) {
  1.2019 +      if (!this.copySaver) {
  1.2020 +        this.copySaver = new DownloadCopySaver();
  1.2021 +        this.copySaver.download = this.download;
  1.2022 +        this.copySaver.entityID = this.entityID;
  1.2023 +        this.copySaver.alreadyAddedToHistory = true;
  1.2024 +      }
  1.2025 +      return this.copySaver.execute.apply(this.copySaver, arguments);
  1.2026 +    }
  1.2027 +
  1.2028 +    this.setProgressBytesFn = aSetProgressBytesFn;
  1.2029 +
  1.2030 +    return Task.spawn(function task_DLS_execute() {
  1.2031 +      try {
  1.2032 +        // Wait for the component that executes the download to finish.
  1.2033 +        yield this.deferExecuted.promise;
  1.2034 +
  1.2035 +        // At this point, the "request" property has been populated.  Ensure we
  1.2036 +        // report the value of "Content-Length", if available, even if the
  1.2037 +        // download didn't generate any progress events.
  1.2038 +        if (!this.progressWasNotified &&
  1.2039 +            this.request instanceof Ci.nsIChannel &&
  1.2040 +            this.request.contentLength >= 0) {
  1.2041 +          aSetProgressBytesFn(0, this.request.contentLength);
  1.2042 +        }
  1.2043 +
  1.2044 +        // If the component executing the download provides the path of a
  1.2045 +        // ".part" file, it means that it expects the listener to move the file
  1.2046 +        // to its final target path when the download succeeds.  In this case,
  1.2047 +        // an empty ".part" file is created even if no data was received from
  1.2048 +        // the source.
  1.2049 +        if (this.download.target.partFilePath) {
  1.2050 +          yield OS.File.move(this.download.target.partFilePath,
  1.2051 +                             this.download.target.path);
  1.2052 +        } else {
  1.2053 +          // The download implementation may not have created the target file if
  1.2054 +          // no data was received from the source.  In this case, ensure that an
  1.2055 +          // empty file is created as expected.
  1.2056 +          try {
  1.2057 +            // This atomic operation is more efficient than an existence check.
  1.2058 +            let file = yield OS.File.open(this.download.target.path,
  1.2059 +                                          { create: true });
  1.2060 +            yield file.close();
  1.2061 +          } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { }
  1.2062 +        }
  1.2063 +      } catch (ex) {
  1.2064 +        // Ensure we always remove the final target file on failure,
  1.2065 +        // independently of which code path failed.  In some cases, the
  1.2066 +        // component executing the download may have already removed the file.
  1.2067 +        try {
  1.2068 +          yield OS.File.remove(this.download.target.path);
  1.2069 +        } catch (e2) {
  1.2070 +          // If we failed during the operation, we report the error but use the
  1.2071 +          // original one as the failure reason of the download.  Note that on
  1.2072 +          // Windows we may get an access denied error instead of a no such file
  1.2073 +          // error if the file existed before, and was recently deleted.
  1.2074 +          if (!(e2 instanceof OS.File.Error &&
  1.2075 +                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
  1.2076 +            Cu.reportError(e2);
  1.2077 +          }
  1.2078 +        }
  1.2079 +        // In case the operation failed, ensure we stop downloading data.  Since
  1.2080 +        // we never re-enter this function, deferCanceled is always available.
  1.2081 +        this.deferCanceled.resolve();
  1.2082 +        throw ex;
  1.2083 +      } finally {
  1.2084 +        // We don't need the reference to the request anymore.  We must also set
  1.2085 +        // deferCanceled to null in order to free any indirect references it
  1.2086 +        // may hold to the request.
  1.2087 +        this.request = null;
  1.2088 +        this.deferCanceled = null;
  1.2089 +        // Allow the download to restart through a DownloadCopySaver.
  1.2090 +        this.firstExecutionFinished = true;
  1.2091 +      }
  1.2092 +    }.bind(this));
  1.2093 +  },
  1.2094 +
  1.2095 +  /**
  1.2096 +   * Implements "DownloadSaver.cancel".
  1.2097 +   */
  1.2098 +  cancel: function DLS_cancel()
  1.2099 +  {
  1.2100 +    // We may be using a DownloadCopySaver to handle resuming.
  1.2101 +    if (this.copySaver) {
  1.2102 +      return this.copySaver.cancel.apply(this.copySaver, arguments);
  1.2103 +    }
  1.2104 +
  1.2105 +    // If the download hasn't stopped already, resolve deferCanceled so that the
  1.2106 +    // operation is canceled as soon as a cancellation handler is registered.
  1.2107 +    // Note that the handler might not have been registered yet.
  1.2108 +    if (this.deferCanceled) {
  1.2109 +      this.deferCanceled.resolve();
  1.2110 +    }
  1.2111 +  },
  1.2112 +
  1.2113 +  /**
  1.2114 +   * Implements "DownloadSaver.removePartialData".
  1.2115 +   */
  1.2116 +  removePartialData: function ()
  1.2117 +  {
  1.2118 +    // DownloadCopySaver and DownloadLeagcySaver use the same logic for removing
  1.2119 +    // partially downloaded data, though this implementation isn't shared by
  1.2120 +    // other saver types, thus it isn't found on their shared prototype.
  1.2121 +    return DownloadCopySaver.prototype.removePartialData.call(this);
  1.2122 +  },
  1.2123 +
  1.2124 +  /**
  1.2125 +   * Implements "DownloadSaver.toSerializable".
  1.2126 +   */
  1.2127 +  toSerializable: function ()
  1.2128 +  {
  1.2129 +    // This object depends on legacy components that are created externally,
  1.2130 +    // thus it cannot be rebuilt during deserialization.  To support resuming
  1.2131 +    // across different browser sessions, this object is transformed into a
  1.2132 +    // DownloadCopySaver for the purpose of serialization.
  1.2133 +    return DownloadCopySaver.prototype.toSerializable.call(this);
  1.2134 +  },
  1.2135 +
  1.2136 +  /**
  1.2137 +   * Implements "DownloadSaver.getSha256Hash".
  1.2138 +   */
  1.2139 +  getSha256Hash: function ()
  1.2140 +  {
  1.2141 +    if (this.copySaver) {
  1.2142 +      return this.copySaver.getSha256Hash();
  1.2143 +    }
  1.2144 +    return this._sha256Hash;
  1.2145 +  },
  1.2146 +
  1.2147 +  /**
  1.2148 +   * Called by the nsITransfer implementation when the hash is available.
  1.2149 +   */
  1.2150 +  setSha256Hash: function (hash)
  1.2151 +  {
  1.2152 +    this._sha256Hash = hash;
  1.2153 +  },
  1.2154 +
  1.2155 +  /**
  1.2156 +   * Implements "DownloadSaver.getSignatureInfo".
  1.2157 +   */
  1.2158 +  getSignatureInfo: function ()
  1.2159 +  {
  1.2160 +    if (this.copySaver) {
  1.2161 +      return this.copySaver.getSignatureInfo();
  1.2162 +    }
  1.2163 +    return this._signatureInfo;
  1.2164 +  },
  1.2165 +
  1.2166 +  /**
  1.2167 +   * Called by the nsITransfer implementation when the hash is available.
  1.2168 +   */
  1.2169 +  setSignatureInfo: function (signatureInfo)
  1.2170 +  {
  1.2171 +    this._signatureInfo = signatureInfo;
  1.2172 +  },
  1.2173 +};
  1.2174 +
  1.2175 +/**
  1.2176 + * Returns a new DownloadLegacySaver object.  This saver type has a
  1.2177 + * deserializable form only when creating a new object in memory, because it
  1.2178 + * cannot be serialized to disk.
  1.2179 + */
  1.2180 +this.DownloadLegacySaver.fromSerializable = function () {
  1.2181 +  return new DownloadLegacySaver();
  1.2182 +};

mercurial