--- a/toolkit/components/jsdownloads/src/DownloadLegacy.js
+++ b/toolkit/components/jsdownloads/src/DownloadLegacy.js
@@ -15,18 +15,16 @@
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
- "resource://gre/modules/PromiseUtils.jsm");
/**
* nsITransfer implementation that provides a bridge to a Download object.
*
* Legacy downloads work differently than the JavaScript implementation. In the
* latter, the caller only provides the properties for the Download object and
* the entire process is handled by the "start" method. In the legacy
* implementation, the caller must create a separate object to execute the
@@ -47,17 +45,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
* cancellation requests, but after cancellation it cannot be reused again.
*
* Since the components that execute the download may be different and they
* don't always give consistent results, this bridge takes care of enforcing the
* expectations, for example by ensuring the target file exists when the
* download is successful, even if the source has a size of zero bytes.
*/
function DownloadLegacyTransfer() {
- this._deferDownload = PromiseUtils.defer();
+ this._promiseDownload = new Promise(r => this._resolveDownload = r);
}
DownloadLegacyTransfer.prototype = {
classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsIWebProgressListener2,
@@ -88,17 +86,17 @@ DownloadLegacyTransfer.prototype = {
}
if (blockedByParentalControls) {
aRequest.cancel(Cr.NS_BINDING_ABORTED);
}
// The main request has just started. Wait for the associated Download
// object to be available before notifying.
- this._deferDownload.promise.then(download => {
+ this._promiseDownload.then(download => {
// If the request was blocked, now that we have the download object we
// should set a flag that can be retrieved later when handling the
// cancellation so that the proper error can be thrown.
if (blockedByParentalControls) {
download._blockedByParentalControls = true;
}
download.saver.onTransferStarted(
@@ -121,17 +119,17 @@ DownloadLegacyTransfer.prototype = {
}
}
});
}).catch(Cu.reportError);
} else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
// The last file has been received, or the download failed. Wait for the
// associated Download object to be available before notifying.
- this._deferDownload.promise.then(download => {
+ this._promiseDownload.then(download => {
// At this point, the hash has been set and we need to copy it to the
// DownloadSaver.
if (Components.isSuccessCode(aStatus)) {
download.saver.setSha256Hash(this._sha256Hash);
download.saver.setSignatureInfo(this._signatureInfo);
download.saver.setRedirects(this._redirects);
}
download.saver.onTransferFinished(aStatus);
@@ -160,35 +158,65 @@ DownloadLegacyTransfer.prototype = {
aMessage) {
// The status change may optionally be received in addition to the state
// change, but if no network request actually started, it is possible that
// we only receive a status change with an error status code.
if (!Components.isSuccessCode(aStatus)) {
this._componentFailed = true;
// Wait for the associated Download object to be available.
- this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) {
- aDownload.saver.onTransferFinished(aStatus);
+ this._promiseDownload.then(download => {
+ download.saver.onTransferFinished(aStatus);
}).catch(Cu.reportError);
}
},
onSecurityChange() { },
// nsIWebProgressListener2
onProgressChange64: function DLT_onProgressChange64(aWebProgress, aRequest,
aCurSelfProgress,
aMaxSelfProgress,
aCurTotalProgress,
aMaxTotalProgress) {
- // Wait for the associated Download object to be available.
- this._deferDownload.promise.then(function DLT_OPC64_onDownload(aDownload) {
- aDownload.saver.onProgressBytes(aCurTotalProgress, aMaxTotalProgress);
+ // Since this progress function is invoked frequently, we use a slightly
+ // more complex solution that optimizes the case where we already have an
+ // associated Download object, avoiding the Promise overhead.
+ if (this._download) {
+ this._hasDelayedProgress = false;
+ this._download.saver.onProgressBytes(aCurTotalProgress,
+ aMaxTotalProgress);
+ return;
+ }
+
+ // If we don't have a Download object yet, store the most recent progress
+ // notification to send later. We must do this because there is no guarantee
+ // that a future notification will be sent if the download stalls.
+ this._delayedCurTotalProgress = aCurTotalProgress;
+ this._delayedMaxTotalProgress = aMaxTotalProgress;
+
+ // Do not enqueue multiple callbacks for the progress report.
+ if (this._hasDelayedProgress) {
+ return;
+ }
+ this._hasDelayedProgress = true;
+
+ this._promiseDownload.then(download => {
+ // Check whether an immediate progress report has been already processed
+ // before we could send the delayed progress report.
+ if (!this._hasDelayedProgress) {
+ return;
+ }
+ download.saver.onProgressBytes(this._delayedCurTotalProgress,
+ this._delayedMaxTotalProgress);
}).catch(Cu.reportError);
},
+ _hasDelayedProgress: false,
+ _delayedCurTotalProgress: 0,
+ _delayedMaxTotalProgress: 0,
// nsIWebProgressListener2
onRefreshAttempted: function DLT_onRefreshAttempted(aWebProgress, aRefreshURI,
aMillis, aSameURI) {
// Indicate that refreshes and redirects are allowed by default. However,
// note that download components don't usually call this method at all.
return true;
},
@@ -228,17 +256,18 @@ DownloadLegacyTransfer.prototype = {
if (aTempFile) {
aDownload.tryToKeepPartialData = true;
}
// Start the download before allowing it to be controlled. Ignore errors.
aDownload.start().catch(() => {});
// Start processing all the other events received through nsITransfer.
- this._deferDownload.resolve(aDownload);
+ this._download = aDownload;
+ this._resolveDownload(aDownload);
// Add the download to the list, allowing it to be seen and canceled.
return Downloads.getList(Downloads.ALL).then(list => list.add(aDownload));
}).catch(Cu.reportError);
},
setSha256Hash(hash) {
this._sha256Hash = hash;
@@ -248,20 +277,30 @@ DownloadLegacyTransfer.prototype = {
this._signatureInfo = signatureInfo;
},
setRedirects(redirects) {
this._redirects = redirects;
},
/**
- * This deferred object contains a promise that is resolved with the Download
- * object associated with this nsITransfer instance, when it is available.
+ * Download object associated with this nsITransfer instance. This is not
+ * available immediately when the nsITransfer instance is created.
*/
- _deferDownload: null,
+ _download: null,
+
+ /**
+ * Promise that resolves to the Download object associated with this
+ * nsITransfer instance after the _resolveDownload method is invoked.
+ *
+ * Waiting on this promise using "then" ensures that the callbacks are invoked
+ * in the correct order even if enqueued before the object is available.
+ */
+ _promiseDownload: null,
+ _resolveDownload: null,
/**
* Reference to the component that is executing the download. This component
* allows cancellation through its nsICancelable interface.
*/
_cancelable: null,
/**