|
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/. */ |
|
6 |
|
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 */ |
|
16 |
|
17 "use strict"; |
|
18 |
|
19 //////////////////////////////////////////////////////////////////////////////// |
|
20 //// Globals |
|
21 |
|
22 const Cc = Components.classes; |
|
23 const Ci = Components.interfaces; |
|
24 const Cu = Components.utils; |
|
25 const Cr = Components.results; |
|
26 |
|
27 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
28 |
|
29 XPCOMUtils.defineLazyModuleGetter(this, "Downloads", |
|
30 "resource://gre/modules/Downloads.jsm"); |
|
31 XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
|
32 "resource://gre/modules/Promise.jsm"); |
|
33 |
|
34 //////////////////////////////////////////////////////////////////////////////// |
|
35 //// DownloadLegacyTransfer |
|
36 |
|
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 } |
|
69 |
|
70 DownloadLegacyTransfer.prototype = { |
|
71 classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"), |
|
72 |
|
73 ////////////////////////////////////////////////////////////////////////////// |
|
74 //// nsISupports |
|
75 |
|
76 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, |
|
77 Ci.nsIWebProgressListener2, |
|
78 Ci.nsITransfer]), |
|
79 |
|
80 ////////////////////////////////////////////////////////////////////////////// |
|
81 //// nsIWebProgressListener |
|
82 |
|
83 onStateChange: function DLT_onStateChange(aWebProgress, aRequest, aStateFlags, |
|
84 aStatus) |
|
85 { |
|
86 if (!Components.isSuccessCode(aStatus)) { |
|
87 this._componentFailed = true; |
|
88 } |
|
89 |
|
90 if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) && |
|
91 (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) { |
|
92 |
|
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 } |
|
100 |
|
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 } |
|
110 |
|
111 download.saver.onTransferStarted( |
|
112 aRequest, |
|
113 this._cancelable instanceof Ci.nsIHelperAppLauncher); |
|
114 |
|
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); |
|
145 |
|
146 // Release the reference to the component executing the download. |
|
147 this._cancelable = null; |
|
148 } |
|
149 }, |
|
150 |
|
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 }, |
|
161 |
|
162 onLocationChange: function () { }, |
|
163 |
|
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; |
|
172 |
|
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 }, |
|
179 |
|
180 onSecurityChange: function () { }, |
|
181 |
|
182 ////////////////////////////////////////////////////////////////////////////// |
|
183 //// nsIWebProgressListener2 |
|
184 |
|
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 }, |
|
196 |
|
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 }, |
|
204 |
|
205 ////////////////////////////////////////////////////////////////////////////// |
|
206 //// nsITransfer |
|
207 |
|
208 init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime, |
|
209 aTempFile, aCancelable, aIsPrivate) |
|
210 { |
|
211 this._cancelable = aCancelable; |
|
212 |
|
213 let launchWhenSucceeded = false, contentType = null, launcherPath = null; |
|
214 |
|
215 if (aMIMEInfo instanceof Ci.nsIMIMEInfo) { |
|
216 launchWhenSucceeded = |
|
217 aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk; |
|
218 contentType = aMIMEInfo.type; |
|
219 |
|
220 let appHandler = aMIMEInfo.preferredApplicationHandler; |
|
221 if (aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp && |
|
222 appHandler instanceof Ci.nsILocalHandlerApp) { |
|
223 launcherPath = appHandler.executable.path; |
|
224 } |
|
225 } |
|
226 |
|
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 } |
|
243 |
|
244 // Start the download before allowing it to be controlled. Ignore errors. |
|
245 aDownload.start().then(null, () => {}); |
|
246 |
|
247 // Start processing all the other events received through nsITransfer. |
|
248 this._deferDownload.resolve(aDownload); |
|
249 |
|
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 }, |
|
254 |
|
255 setSha256Hash: function (hash) |
|
256 { |
|
257 this._sha256Hash = hash; |
|
258 }, |
|
259 |
|
260 setSignatureInfo: function (signatureInfo) |
|
261 { |
|
262 this._signatureInfo = signatureInfo; |
|
263 }, |
|
264 |
|
265 ////////////////////////////////////////////////////////////////////////////// |
|
266 //// Private methods and properties |
|
267 |
|
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, |
|
273 |
|
274 /** |
|
275 * Reference to the component that is executing the download. This component |
|
276 * allows cancellation through its nsICancelable interface. |
|
277 */ |
|
278 _cancelable: null, |
|
279 |
|
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, |
|
286 |
|
287 /** |
|
288 * Save the SHA-256 hash in raw bytes of the downloaded file. |
|
289 */ |
|
290 _sha256Hash: null, |
|
291 |
|
292 /** |
|
293 * Save the signature info in a serialized protobuf of the downloaded file. |
|
294 */ |
|
295 _signatureInfo: null, |
|
296 }; |
|
297 |
|
298 //////////////////////////////////////////////////////////////////////////////// |
|
299 //// Module |
|
300 |
|
301 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadLegacyTransfer]); |