toolkit/components/jsdownloads/src/DownloadLegacy.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /**
     8  * This component implements the XPCOM interfaces required for integration with
     9  * the legacy download components.
    10  *
    11  * New code is expected to use the "Downloads.jsm" module directly, without
    12  * going through the interfaces implemented in this XPCOM component.  These
    13  * interfaces are only maintained for backwards compatibility with components
    14  * that still work synchronously on the main thread.
    15  */
    17 "use strict";
    19 ////////////////////////////////////////////////////////////////////////////////
    20 //// Globals
    22 const Cc = Components.classes;
    23 const Ci = Components.interfaces;
    24 const Cu = Components.utils;
    25 const Cr = Components.results;
    27 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    29 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
    30                                   "resource://gre/modules/Downloads.jsm");
    31 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    32                                   "resource://gre/modules/Promise.jsm");
    34 ////////////////////////////////////////////////////////////////////////////////
    35 //// DownloadLegacyTransfer
    37 /**
    38  * nsITransfer implementation that provides a bridge to a Download object.
    39  *
    40  * Legacy downloads work differently than the JavaScript implementation.  In the
    41  * latter, the caller only provides the properties for the Download object and
    42  * the entire process is handled by the "start" method.  In the legacy
    43  * implementation, the caller must create a separate object to execute the
    44  * download, and then make the download visible to the user by hooking it up to
    45  * an nsITransfer instance.
    46  *
    47  * Since nsITransfer instances may be created before the download system is
    48  * initialized, and initialization as well as other operations are asynchronous,
    49  * this implementation is able to delay all progress and status notifications it
    50  * receives until the associated Download object is finally created.
    51  *
    52  * Conversely, the DownloadLegacySaver object can also receive execution and
    53  * cancellation requests asynchronously, before or after it is connected to
    54  * this nsITransfer instance.  For that reason, those requests are communicated
    55  * in a potentially deferred way, using promise objects.
    56  *
    57  * The component that executes the download implements nsICancelable to receive
    58  * cancellation requests, but after cancellation it cannot be reused again.
    59  *
    60  * Since the components that execute the download may be different and they
    61  * don't always give consistent results, this bridge takes care of enforcing the
    62  * expectations, for example by ensuring the target file exists when the
    63  * download is successful, even if the source has a size of zero bytes.
    64  */
    65 function DownloadLegacyTransfer()
    66 {
    67   this._deferDownload = Promise.defer();
    68 }
    70 DownloadLegacyTransfer.prototype = {
    71   classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),
    73   //////////////////////////////////////////////////////////////////////////////
    74   //// nsISupports
    76   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
    77                                          Ci.nsIWebProgressListener2,
    78                                          Ci.nsITransfer]),
    80   //////////////////////////////////////////////////////////////////////////////
    81   //// nsIWebProgressListener
    83   onStateChange: function DLT_onStateChange(aWebProgress, aRequest, aStateFlags,
    84                                             aStatus)
    85   {
    86     if (!Components.isSuccessCode(aStatus)) {
    87       this._componentFailed = true;
    88     }
    90     if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) &&
    91         (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
    93       // If the request's response has been blocked by Windows Parental Controls
    94       // with an HTTP 450 error code, we must cancel the request synchronously.
    95       let blockedByParentalControls = aRequest instanceof Ci.nsIHttpChannel &&
    96                                       aRequest.responseStatus == 450;
    97       if (blockedByParentalControls) {
    98         aRequest.cancel(Cr.NS_BINDING_ABORTED);
    99       }
   101       // The main request has just started.  Wait for the associated Download
   102       // object to be available before notifying.
   103       this._deferDownload.promise.then(download => {
   104         // If the request was blocked, now that we have the download object we
   105         // should set a flag that can be retrieved later when handling the
   106         // cancellation so that the proper error can be thrown.
   107         if (blockedByParentalControls) {
   108           download._blockedByParentalControls = true;
   109         }
   111         download.saver.onTransferStarted(
   112                          aRequest,
   113                          this._cancelable instanceof Ci.nsIHelperAppLauncher);
   115         // To handle asynchronous cancellation properly, we should hook up the
   116         // handler only after we have been notified that the main request
   117         // started.  We will wait until the main request stopped before
   118         // notifying that the download has been canceled.  Since the request has
   119         // not completed yet, deferCanceled is guaranteed to be set.
   120         return download.saver.deferCanceled.promise.then(() => {
   121           // Only cancel if the object executing the download is still running.
   122           if (this._cancelable && !this._componentFailed) {
   123             this._cancelable.cancel(Cr.NS_ERROR_ABORT);
   124             if (this._cancelable instanceof Ci.nsIWebBrowserPersist) {
   125               // This component will not send the STATE_STOP notification.
   126               download.saver.onTransferFinished(aRequest, Cr.NS_ERROR_ABORT);
   127               this._cancelable = null;
   128             }
   129           }
   130         });
   131       }).then(null, Cu.reportError);
   132     } else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
   133         (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
   134       // The last file has been received, or the download failed.  Wait for the
   135       // associated Download object to be available before notifying.
   136       this._deferDownload.promise.then(download => {
   137         // At this point, the hash has been set and we need to copy it to the
   138         // DownloadSaver.
   139         if (Components.isSuccessCode(aStatus)) {
   140           download.saver.setSha256Hash(this._sha256Hash);
   141           download.saver.setSignatureInfo(this._signatureInfo);
   142         }
   143         download.saver.onTransferFinished(aRequest, aStatus);
   144       }).then(null, Cu.reportError);
   146       // Release the reference to the component executing the download.
   147       this._cancelable = null;
   148     }
   149   },
   151   onProgressChange: function DLT_onProgressChange(aWebProgress, aRequest,
   152                                                   aCurSelfProgress,
   153                                                   aMaxSelfProgress,
   154                                                   aCurTotalProgress,
   155                                                   aMaxTotalProgress)
   156   {
   157     this.onProgressChange64(aWebProgress, aRequest, aCurSelfProgress,
   158                             aMaxSelfProgress, aCurTotalProgress,
   159                             aMaxTotalProgress);
   160   },
   162   onLocationChange: function () { },
   164   onStatusChange: function DLT_onStatusChange(aWebProgress, aRequest, aStatus,
   165                                               aMessage)
   166   {
   167     // The status change may optionally be received in addition to the state
   168     // change, but if no network request actually started, it is possible that
   169     // we only receive a status change with an error status code.
   170     if (!Components.isSuccessCode(aStatus)) {
   171       this._componentFailed = true;
   173       // Wait for the associated Download object to be available.
   174       this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) {
   175         aDownload.saver.onTransferFinished(aRequest, aStatus);
   176       }).then(null, Cu.reportError);
   177     }
   178   },
   180   onSecurityChange: function () { },
   182   //////////////////////////////////////////////////////////////////////////////
   183   //// nsIWebProgressListener2
   185   onProgressChange64: function DLT_onProgressChange64(aWebProgress, aRequest,
   186                                                       aCurSelfProgress,
   187                                                       aMaxSelfProgress,
   188                                                       aCurTotalProgress,
   189                                                       aMaxTotalProgress)
   190   {
   191     // Wait for the associated Download object to be available.
   192     this._deferDownload.promise.then(function DLT_OPC64_onDownload(aDownload) {
   193       aDownload.saver.onProgressBytes(aCurTotalProgress, aMaxTotalProgress);
   194     }).then(null, Cu.reportError);
   195   },
   197   onRefreshAttempted: function DLT_onRefreshAttempted(aWebProgress, aRefreshURI,
   198                                                       aMillis, aSameURI)
   199   {
   200     // Indicate that refreshes and redirects are allowed by default.  However,
   201     // note that download components don't usually call this method at all.
   202     return true;
   203   },
   205   //////////////////////////////////////////////////////////////////////////////
   206   //// nsITransfer
   208   init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
   209                           aTempFile, aCancelable, aIsPrivate)
   210   {
   211     this._cancelable = aCancelable;
   213     let launchWhenSucceeded = false, contentType = null, launcherPath = null;
   215     if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
   216       launchWhenSucceeded =
   217                 aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk;
   218       contentType = aMIMEInfo.type;
   220       let appHandler = aMIMEInfo.preferredApplicationHandler;
   221       if (aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp &&
   222           appHandler instanceof Ci.nsILocalHandlerApp) {
   223         launcherPath = appHandler.executable.path;
   224       }
   225     }
   227     // Create a new Download object associated to a DownloadLegacySaver, and
   228     // wait for it to be available.  This operation may cause the entire
   229     // download system to initialize before the object is created.
   230     Downloads.createDownload({
   231       source: { url: aSource.spec, isPrivate: aIsPrivate },
   232       target: { path: aTarget.QueryInterface(Ci.nsIFileURL).file.path,
   233                 partFilePath: aTempFile && aTempFile.path },
   234       saver: "legacy",
   235       launchWhenSucceeded: launchWhenSucceeded,
   236       contentType: contentType,
   237       launcherPath: launcherPath
   238     }).then(function DLT_I_onDownload(aDownload) {
   239       // Legacy components keep partial data when they use a ".part" file.
   240       if (aTempFile) {
   241         aDownload.tryToKeepPartialData = true;
   242       }
   244       // Start the download before allowing it to be controlled.  Ignore errors.
   245       aDownload.start().then(null, () => {});
   247       // Start processing all the other events received through nsITransfer.
   248       this._deferDownload.resolve(aDownload);
   250       // Add the download to the list, allowing it to be seen and canceled.
   251       return Downloads.getList(Downloads.ALL).then(list => list.add(aDownload));
   252     }.bind(this)).then(null, Cu.reportError);
   253   },
   255   setSha256Hash: function (hash)
   256   {
   257     this._sha256Hash = hash;
   258   },
   260   setSignatureInfo: function (signatureInfo)
   261   {
   262     this._signatureInfo = signatureInfo;
   263   },
   265   //////////////////////////////////////////////////////////////////////////////
   266   //// Private methods and properties
   268   /**
   269    * This deferred object contains a promise that is resolved with the Download
   270    * object associated with this nsITransfer instance, when it is available.
   271    */
   272   _deferDownload: null,
   274   /**
   275    * Reference to the component that is executing the download.  This component
   276    * allows cancellation through its nsICancelable interface.
   277    */
   278   _cancelable: null,
   280   /**
   281    * Indicates that the component that executes the download has notified a
   282    * failure condition.  In this case, we should never use the component methods
   283    * that cancel the download.
   284    */
   285   _componentFailed: false,
   287   /**
   288    * Save the SHA-256 hash in raw bytes of the downloaded file.
   289    */
   290   _sha256Hash: null,
   292   /**
   293    * Save the signature info in a serialized protobuf of the downloaded file.
   294    */
   295   _signatureInfo: null,
   296 };
   298 ////////////////////////////////////////////////////////////////////////////////
   299 //// Module
   301 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadLegacyTransfer]);

mercurial