1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/jsdownloads/src/DownloadLegacy.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,301 @@ 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: */ 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 component implements the XPCOM interfaces required for integration with 1.12 + * the legacy download components. 1.13 + * 1.14 + * New code is expected to use the "Downloads.jsm" module directly, without 1.15 + * going through the interfaces implemented in this XPCOM component. These 1.16 + * interfaces are only maintained for backwards compatibility with components 1.17 + * that still work synchronously on the main thread. 1.18 + */ 1.19 + 1.20 +"use strict"; 1.21 + 1.22 +//////////////////////////////////////////////////////////////////////////////// 1.23 +//// Globals 1.24 + 1.25 +const Cc = Components.classes; 1.26 +const Ci = Components.interfaces; 1.27 +const Cu = Components.utils; 1.28 +const Cr = Components.results; 1.29 + 1.30 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.31 + 1.32 +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", 1.33 + "resource://gre/modules/Downloads.jsm"); 1.34 +XPCOMUtils.defineLazyModuleGetter(this, "Promise", 1.35 + "resource://gre/modules/Promise.jsm"); 1.36 + 1.37 +//////////////////////////////////////////////////////////////////////////////// 1.38 +//// DownloadLegacyTransfer 1.39 + 1.40 +/** 1.41 + * nsITransfer implementation that provides a bridge to a Download object. 1.42 + * 1.43 + * Legacy downloads work differently than the JavaScript implementation. In the 1.44 + * latter, the caller only provides the properties for the Download object and 1.45 + * the entire process is handled by the "start" method. In the legacy 1.46 + * implementation, the caller must create a separate object to execute the 1.47 + * download, and then make the download visible to the user by hooking it up to 1.48 + * an nsITransfer instance. 1.49 + * 1.50 + * Since nsITransfer instances may be created before the download system is 1.51 + * initialized, and initialization as well as other operations are asynchronous, 1.52 + * this implementation is able to delay all progress and status notifications it 1.53 + * receives until the associated Download object is finally created. 1.54 + * 1.55 + * Conversely, the DownloadLegacySaver object can also receive execution and 1.56 + * cancellation requests asynchronously, before or after it is connected to 1.57 + * this nsITransfer instance. For that reason, those requests are communicated 1.58 + * in a potentially deferred way, using promise objects. 1.59 + * 1.60 + * The component that executes the download implements nsICancelable to receive 1.61 + * cancellation requests, but after cancellation it cannot be reused again. 1.62 + * 1.63 + * Since the components that execute the download may be different and they 1.64 + * don't always give consistent results, this bridge takes care of enforcing the 1.65 + * expectations, for example by ensuring the target file exists when the 1.66 + * download is successful, even if the source has a size of zero bytes. 1.67 + */ 1.68 +function DownloadLegacyTransfer() 1.69 +{ 1.70 + this._deferDownload = Promise.defer(); 1.71 +} 1.72 + 1.73 +DownloadLegacyTransfer.prototype = { 1.74 + classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"), 1.75 + 1.76 + ////////////////////////////////////////////////////////////////////////////// 1.77 + //// nsISupports 1.78 + 1.79 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, 1.80 + Ci.nsIWebProgressListener2, 1.81 + Ci.nsITransfer]), 1.82 + 1.83 + ////////////////////////////////////////////////////////////////////////////// 1.84 + //// nsIWebProgressListener 1.85 + 1.86 + onStateChange: function DLT_onStateChange(aWebProgress, aRequest, aStateFlags, 1.87 + aStatus) 1.88 + { 1.89 + if (!Components.isSuccessCode(aStatus)) { 1.90 + this._componentFailed = true; 1.91 + } 1.92 + 1.93 + if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) && 1.94 + (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) { 1.95 + 1.96 + // If the request's response has been blocked by Windows Parental Controls 1.97 + // with an HTTP 450 error code, we must cancel the request synchronously. 1.98 + let blockedByParentalControls = aRequest instanceof Ci.nsIHttpChannel && 1.99 + aRequest.responseStatus == 450; 1.100 + if (blockedByParentalControls) { 1.101 + aRequest.cancel(Cr.NS_BINDING_ABORTED); 1.102 + } 1.103 + 1.104 + // The main request has just started. Wait for the associated Download 1.105 + // object to be available before notifying. 1.106 + this._deferDownload.promise.then(download => { 1.107 + // If the request was blocked, now that we have the download object we 1.108 + // should set a flag that can be retrieved later when handling the 1.109 + // cancellation so that the proper error can be thrown. 1.110 + if (blockedByParentalControls) { 1.111 + download._blockedByParentalControls = true; 1.112 + } 1.113 + 1.114 + download.saver.onTransferStarted( 1.115 + aRequest, 1.116 + this._cancelable instanceof Ci.nsIHelperAppLauncher); 1.117 + 1.118 + // To handle asynchronous cancellation properly, we should hook up the 1.119 + // handler only after we have been notified that the main request 1.120 + // started. We will wait until the main request stopped before 1.121 + // notifying that the download has been canceled. Since the request has 1.122 + // not completed yet, deferCanceled is guaranteed to be set. 1.123 + return download.saver.deferCanceled.promise.then(() => { 1.124 + // Only cancel if the object executing the download is still running. 1.125 + if (this._cancelable && !this._componentFailed) { 1.126 + this._cancelable.cancel(Cr.NS_ERROR_ABORT); 1.127 + if (this._cancelable instanceof Ci.nsIWebBrowserPersist) { 1.128 + // This component will not send the STATE_STOP notification. 1.129 + download.saver.onTransferFinished(aRequest, Cr.NS_ERROR_ABORT); 1.130 + this._cancelable = null; 1.131 + } 1.132 + } 1.133 + }); 1.134 + }).then(null, Cu.reportError); 1.135 + } else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) && 1.136 + (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) { 1.137 + // The last file has been received, or the download failed. Wait for the 1.138 + // associated Download object to be available before notifying. 1.139 + this._deferDownload.promise.then(download => { 1.140 + // At this point, the hash has been set and we need to copy it to the 1.141 + // DownloadSaver. 1.142 + if (Components.isSuccessCode(aStatus)) { 1.143 + download.saver.setSha256Hash(this._sha256Hash); 1.144 + download.saver.setSignatureInfo(this._signatureInfo); 1.145 + } 1.146 + download.saver.onTransferFinished(aRequest, aStatus); 1.147 + }).then(null, Cu.reportError); 1.148 + 1.149 + // Release the reference to the component executing the download. 1.150 + this._cancelable = null; 1.151 + } 1.152 + }, 1.153 + 1.154 + onProgressChange: function DLT_onProgressChange(aWebProgress, aRequest, 1.155 + aCurSelfProgress, 1.156 + aMaxSelfProgress, 1.157 + aCurTotalProgress, 1.158 + aMaxTotalProgress) 1.159 + { 1.160 + this.onProgressChange64(aWebProgress, aRequest, aCurSelfProgress, 1.161 + aMaxSelfProgress, aCurTotalProgress, 1.162 + aMaxTotalProgress); 1.163 + }, 1.164 + 1.165 + onLocationChange: function () { }, 1.166 + 1.167 + onStatusChange: function DLT_onStatusChange(aWebProgress, aRequest, aStatus, 1.168 + aMessage) 1.169 + { 1.170 + // The status change may optionally be received in addition to the state 1.171 + // change, but if no network request actually started, it is possible that 1.172 + // we only receive a status change with an error status code. 1.173 + if (!Components.isSuccessCode(aStatus)) { 1.174 + this._componentFailed = true; 1.175 + 1.176 + // Wait for the associated Download object to be available. 1.177 + this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) { 1.178 + aDownload.saver.onTransferFinished(aRequest, aStatus); 1.179 + }).then(null, Cu.reportError); 1.180 + } 1.181 + }, 1.182 + 1.183 + onSecurityChange: function () { }, 1.184 + 1.185 + ////////////////////////////////////////////////////////////////////////////// 1.186 + //// nsIWebProgressListener2 1.187 + 1.188 + onProgressChange64: function DLT_onProgressChange64(aWebProgress, aRequest, 1.189 + aCurSelfProgress, 1.190 + aMaxSelfProgress, 1.191 + aCurTotalProgress, 1.192 + aMaxTotalProgress) 1.193 + { 1.194 + // Wait for the associated Download object to be available. 1.195 + this._deferDownload.promise.then(function DLT_OPC64_onDownload(aDownload) { 1.196 + aDownload.saver.onProgressBytes(aCurTotalProgress, aMaxTotalProgress); 1.197 + }).then(null, Cu.reportError); 1.198 + }, 1.199 + 1.200 + onRefreshAttempted: function DLT_onRefreshAttempted(aWebProgress, aRefreshURI, 1.201 + aMillis, aSameURI) 1.202 + { 1.203 + // Indicate that refreshes and redirects are allowed by default. However, 1.204 + // note that download components don't usually call this method at all. 1.205 + return true; 1.206 + }, 1.207 + 1.208 + ////////////////////////////////////////////////////////////////////////////// 1.209 + //// nsITransfer 1.210 + 1.211 + init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, 1.212 + aTempFile, aCancelable, aIsPrivate) 1.213 + { 1.214 + this._cancelable = aCancelable; 1.215 + 1.216 + let launchWhenSucceeded = false, contentType = null, launcherPath = null; 1.217 + 1.218 + if (aMIMEInfo instanceof Ci.nsIMIMEInfo) { 1.219 + launchWhenSucceeded = 1.220 + aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk; 1.221 + contentType = aMIMEInfo.type; 1.222 + 1.223 + let appHandler = aMIMEInfo.preferredApplicationHandler; 1.224 + if (aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp && 1.225 + appHandler instanceof Ci.nsILocalHandlerApp) { 1.226 + launcherPath = appHandler.executable.path; 1.227 + } 1.228 + } 1.229 + 1.230 + // Create a new Download object associated to a DownloadLegacySaver, and 1.231 + // wait for it to be available. This operation may cause the entire 1.232 + // download system to initialize before the object is created. 1.233 + Downloads.createDownload({ 1.234 + source: { url: aSource.spec, isPrivate: aIsPrivate }, 1.235 + target: { path: aTarget.QueryInterface(Ci.nsIFileURL).file.path, 1.236 + partFilePath: aTempFile && aTempFile.path }, 1.237 + saver: "legacy", 1.238 + launchWhenSucceeded: launchWhenSucceeded, 1.239 + contentType: contentType, 1.240 + launcherPath: launcherPath 1.241 + }).then(function DLT_I_onDownload(aDownload) { 1.242 + // Legacy components keep partial data when they use a ".part" file. 1.243 + if (aTempFile) { 1.244 + aDownload.tryToKeepPartialData = true; 1.245 + } 1.246 + 1.247 + // Start the download before allowing it to be controlled. Ignore errors. 1.248 + aDownload.start().then(null, () => {}); 1.249 + 1.250 + // Start processing all the other events received through nsITransfer. 1.251 + this._deferDownload.resolve(aDownload); 1.252 + 1.253 + // Add the download to the list, allowing it to be seen and canceled. 1.254 + return Downloads.getList(Downloads.ALL).then(list => list.add(aDownload)); 1.255 + }.bind(this)).then(null, Cu.reportError); 1.256 + }, 1.257 + 1.258 + setSha256Hash: function (hash) 1.259 + { 1.260 + this._sha256Hash = hash; 1.261 + }, 1.262 + 1.263 + setSignatureInfo: function (signatureInfo) 1.264 + { 1.265 + this._signatureInfo = signatureInfo; 1.266 + }, 1.267 + 1.268 + ////////////////////////////////////////////////////////////////////////////// 1.269 + //// Private methods and properties 1.270 + 1.271 + /** 1.272 + * This deferred object contains a promise that is resolved with the Download 1.273 + * object associated with this nsITransfer instance, when it is available. 1.274 + */ 1.275 + _deferDownload: null, 1.276 + 1.277 + /** 1.278 + * Reference to the component that is executing the download. This component 1.279 + * allows cancellation through its nsICancelable interface. 1.280 + */ 1.281 + _cancelable: null, 1.282 + 1.283 + /** 1.284 + * Indicates that the component that executes the download has notified a 1.285 + * failure condition. In this case, we should never use the component methods 1.286 + * that cancel the download. 1.287 + */ 1.288 + _componentFailed: false, 1.289 + 1.290 + /** 1.291 + * Save the SHA-256 hash in raw bytes of the downloaded file. 1.292 + */ 1.293 + _sha256Hash: null, 1.294 + 1.295 + /** 1.296 + * Save the signature info in a serialized protobuf of the downloaded file. 1.297 + */ 1.298 + _signatureInfo: null, 1.299 +}; 1.300 + 1.301 +//////////////////////////////////////////////////////////////////////////////// 1.302 +//// Module 1.303 + 1.304 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadLegacyTransfer]);