toolkit/components/jsdownloads/src/DownloadLegacy.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial