зеркало из https://github.com/mozilla/gecko-dev.git
Bug 899107 - Allow using the JavaScript API as the back-end for the Downloads Panel. r=mak
This commit is contained in:
Родитель
ceabe32d38
Коммит
9441f6c0be
|
@ -335,6 +335,9 @@ pref("browser.download.manager.quitBehavior", 0);
|
|||
pref("browser.download.manager.scanWhenDone", true);
|
||||
pref("browser.download.manager.resumeOnWakeDelay", 10000);
|
||||
|
||||
// Enables the asynchronous Downloads API in the Downloads Panel.
|
||||
pref("browser.download.useJSTransfer", false);
|
||||
|
||||
// This allows disabling the Downloads Panel in favor of the old interface.
|
||||
pref("browser.download.useToolkitUI", false);
|
||||
|
||||
|
|
|
@ -1106,7 +1106,15 @@ var gBrowserInit = {
|
|||
// If the user manually opens the download manager before the timeout, the
|
||||
// downloads will start right away, and getting the service again won't hurt.
|
||||
setTimeout(function() {
|
||||
Services.downloads;
|
||||
let DownloadsCommon =
|
||||
Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
// Open the data link without initalizing nsIDownloadManager.
|
||||
DownloadsCommon.initializeAllDataLinks();
|
||||
} else {
|
||||
// Initalizing nsIDownloadManager will trigger the data link.
|
||||
Services.downloads;
|
||||
}
|
||||
let DownloadTaskbarProgress =
|
||||
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
|
||||
DownloadTaskbarProgress.onBrowserWindowLoad(window);
|
||||
|
|
|
@ -51,8 +51,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
|||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
|
||||
"resource://gre/modules/DownloadUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm")
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||
|
@ -582,6 +586,20 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
|
|||
return parseFloat(sysInfo.getProperty("version")) >= 6;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns true if we should hook the panel to the JavaScript API for downloads
|
||||
* instead of the nsIDownloadManager back-end. In order for the logic to work
|
||||
* properly, this value never changes during the execution of the application,
|
||||
* even if the underlying preference value has changed. A restart is required
|
||||
* for the change to take effect.
|
||||
*/
|
||||
XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("browser.download.useJSTransfer");
|
||||
} catch (ex) { }
|
||||
return false;
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadsData
|
||||
|
||||
|
@ -617,6 +635,11 @@ function DownloadsDataCtor(aPrivate) {
|
|||
// Array of view objects that should be notified when the available download
|
||||
// data changes.
|
||||
this._views = [];
|
||||
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
// Maps Download objects to DownloadDataItem objects.
|
||||
this._downloadToDataItemMap = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
DownloadsDataCtor.prototype = {
|
||||
|
@ -632,8 +655,15 @@ DownloadsDataCtor.prototype = {
|
|||
initializeDataLink: function DD_initializeDataLink(aDownloadManagerService)
|
||||
{
|
||||
// Start receiving real-time events.
|
||||
aDownloadManagerService.addPrivacyAwareListener(this);
|
||||
Services.obs.addObserver(this, "download-manager-remove-download-guid", false);
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
let promiseList = this._isPrivate ? Downloads.getPrivateDownloadList()
|
||||
: Downloads.getPublicDownloadList();
|
||||
promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
|
||||
} else {
|
||||
aDownloadManagerService.addPrivacyAwareListener(this);
|
||||
Services.obs.addObserver(this, "download-manager-remove-download-guid",
|
||||
false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -641,6 +671,11 @@ DownloadsDataCtor.prototype = {
|
|||
*/
|
||||
terminateDataLink: function DD_terminateDataLink()
|
||||
{
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
Cu.reportError("terminateDataLink not applicable with useJSTransfer");
|
||||
return;
|
||||
}
|
||||
|
||||
this._terminateDataAccess();
|
||||
|
||||
// Stop receiving real-time events.
|
||||
|
@ -648,6 +683,84 @@ DownloadsDataCtor.prototype = {
|
|||
Services.downloads.removeListener(this);
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Integration with the asynchronous Downloads back-end
|
||||
|
||||
onDownloadAdded: function (aDownload)
|
||||
{
|
||||
let dataItem = new DownloadsDataItem(aDownload);
|
||||
this._downloadToDataItemMap.set(aDownload, dataItem);
|
||||
this.dataItems[dataItem.downloadGuid] = dataItem;
|
||||
|
||||
for (let view of this._views) {
|
||||
view.onDataItemAdded(dataItem, true);
|
||||
}
|
||||
|
||||
this._updateDataItemState(dataItem);
|
||||
},
|
||||
|
||||
onDownloadChanged: function (aDownload)
|
||||
{
|
||||
let dataItem = this._downloadToDataItemMap.get(aDownload);
|
||||
if (!dataItem) {
|
||||
Cu.reportError("Download doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateDataItemState(dataItem);
|
||||
},
|
||||
|
||||
onDownloadRemoved: function (aDownload)
|
||||
{
|
||||
let dataItem = this._downloadToDataItemMap.get(aDownload);
|
||||
if (!dataItem) {
|
||||
Cu.reportError("Download doesn't exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._downloadToDataItemMap.remove(aDownload);
|
||||
this.dataItems[dataItem.downloadGuid] = null;
|
||||
for (let view of this._views) {
|
||||
view.onDataItemRemoved(dataItem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the given data item and sends related notifications.
|
||||
*/
|
||||
_updateDataItemState: function (aDataItem)
|
||||
{
|
||||
let wasInProgress = aDataItem.inProgress;
|
||||
let wasDone = aDataItem.done;
|
||||
|
||||
aDataItem.updateFromJSDownload();
|
||||
|
||||
if (wasInProgress && !aDataItem.inProgress) {
|
||||
aDataItem.endTime = Date.now();
|
||||
}
|
||||
|
||||
for (let view of this._views) {
|
||||
try {
|
||||
view.getViewItem(aDataItem).onStateChange({});
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!aDataItem.newDownloadNotified) {
|
||||
aDataItem.newDownloadNotified = true;
|
||||
this._notifyDownloadEvent("start");
|
||||
}
|
||||
|
||||
if (!wasDone && aDataItem.done) {
|
||||
this._notifyDownloadEvent("finish");
|
||||
}
|
||||
|
||||
for (let view of this._views) {
|
||||
view.getViewItem(aDataItem).onProgressChange();
|
||||
}
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Registration of views
|
||||
|
||||
|
@ -1160,11 +1273,14 @@ XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
|
|||
*
|
||||
* @param aSource
|
||||
* Object containing the data with which the item should be initialized.
|
||||
* This should implement either nsIDownload or mozIStorageRow.
|
||||
* This should implement either nsIDownload or mozIStorageRow. If the
|
||||
* JavaScript API for downloads is enabled, this is a Download object.
|
||||
*/
|
||||
function DownloadsDataItem(aSource)
|
||||
{
|
||||
if (aSource instanceof Ci.nsIDownload) {
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
this._initFromJSDownload(aSource);
|
||||
} else if (aSource instanceof Ci.nsIDownload) {
|
||||
this._initFromDownload(aSource);
|
||||
} else {
|
||||
this._initFromDataRow(aSource);
|
||||
|
@ -1172,6 +1288,66 @@ function DownloadsDataItem(aSource)
|
|||
}
|
||||
|
||||
DownloadsDataItem.prototype = {
|
||||
/**
|
||||
* The JavaScript API does not need identifiers for Download objects, so they
|
||||
* are generated sequentially for the corresponding DownloadDataItem.
|
||||
*/
|
||||
get _autoIncrementId() ++DownloadsDataItem.prototype.__lastId,
|
||||
__lastId: 0,
|
||||
|
||||
/**
|
||||
* Initializes this object from the JavaScript API for downloads.
|
||||
*
|
||||
* The endTime property is initialized to the current date and time.
|
||||
*
|
||||
* @param aDownload
|
||||
* The Download object with the current state.
|
||||
*/
|
||||
_initFromJSDownload: function (aDownload)
|
||||
{
|
||||
this._download = aDownload;
|
||||
|
||||
this.downloadGuid = "id:" + this._autoIncrementId;
|
||||
this.file = aDownload.target.path;
|
||||
this.target = OS.Path.basename(aDownload.target.path);
|
||||
this.uri = aDownload.source.url;
|
||||
this.endTime = Date.now();
|
||||
|
||||
this.updateFromJSDownload();
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates this object from the JavaScript API for downloads.
|
||||
*/
|
||||
updateFromJSDownload: function ()
|
||||
{
|
||||
// Collapse state using the correct priority.
|
||||
if (this._download.succeeded) {
|
||||
this.state = nsIDM.DOWNLOAD_FINISHED;
|
||||
} else if (this._download.error &&
|
||||
this._download.error.becauseBlockedByParentalControls) {
|
||||
this.state = nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
|
||||
} else if (this._download.error) {
|
||||
this.state = nsIDM.DOWNLOAD_FAILED;
|
||||
} else if (this._download.canceled && this._download.hasPartialData) {
|
||||
this.state = nsIDM.DOWNLOAD_PAUSED;
|
||||
} else if (this._download.canceled) {
|
||||
this.state = nsIDM.DOWNLOAD_CANCELED;
|
||||
} else if (this._download.stopped) {
|
||||
this.state = nsIDM.DOWNLOAD_NOTSTARTED;
|
||||
} else {
|
||||
this.state = nsIDM.DOWNLOAD_DOWNLOADING;
|
||||
}
|
||||
|
||||
this.referrer = this._download.source.referrer;
|
||||
this.startTime = this._download.startTime;
|
||||
this.currBytes = this._download.currentBytes;
|
||||
this.maxBytes = this._download.totalBytes;
|
||||
this.resumable = this._download.hasPartialData;
|
||||
this.speed = 0;
|
||||
this.percentComplete = this._download.progress;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes this object from a download object of the Download Manager.
|
||||
*
|
||||
|
@ -1408,6 +1584,11 @@ DownloadsDataItem.prototype = {
|
|||
* @throws if the file cannot be opened.
|
||||
*/
|
||||
openLocalFile: function DDI_openLocalFile(aOwnerWindow) {
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
this._download.launch().then(null, Cu.reportError);
|
||||
return;
|
||||
}
|
||||
|
||||
this.getDownload(function(aDownload) {
|
||||
DownloadsCommon.openDownloadedFile(this.localFile,
|
||||
aDownload.MIMEInfo,
|
||||
|
@ -1427,6 +1608,15 @@ DownloadsDataItem.prototype = {
|
|||
* @throws if the download is not resumable or if has already done.
|
||||
*/
|
||||
togglePauseResume: function DDI_togglePauseResume() {
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
if (this._download.stopped) {
|
||||
this._download.start();
|
||||
} else {
|
||||
this._download.cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.inProgress || !this.resumable)
|
||||
throw new Error("The given download cannot be paused or resumed");
|
||||
|
||||
|
@ -1445,8 +1635,13 @@ DownloadsDataItem.prototype = {
|
|||
* @throws if we cannot.
|
||||
*/
|
||||
retry: function DDI_retry() {
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
this._download.start();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.canRetry)
|
||||
throw new Error("Cannot rerty this download");
|
||||
throw new Error("Cannot retry this download");
|
||||
|
||||
this.getDownload(function(aDownload) {
|
||||
aDownload.retry();
|
||||
|
@ -1473,6 +1668,12 @@ DownloadsDataItem.prototype = {
|
|||
* @throws if the download is already done.
|
||||
*/
|
||||
cancel: function() {
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
this._download.cancel();
|
||||
this._download.removePartialData().then(null, Cu.reportError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.inProgress)
|
||||
throw new Error("Cannot cancel this download");
|
||||
|
||||
|
@ -1486,6 +1687,16 @@ DownloadsDataItem.prototype = {
|
|||
* Remove the download.
|
||||
*/
|
||||
remove: function DDI_remove() {
|
||||
if (DownloadsCommon.useJSTransfer) {
|
||||
let promiseList = this._download.source.isPrivate
|
||||
? Downloads.getPrivateDownloadList()
|
||||
: Downloads.getPublicDownloadList();
|
||||
promiseList.then(list => list.remove(this._download))
|
||||
.then(() => this._download.finalize(true))
|
||||
.then(null, Cu.reportError);
|
||||
return;
|
||||
}
|
||||
|
||||
this.getDownload(function (aDownload) {
|
||||
if (this.inProgress) {
|
||||
aDownload.cancel();
|
||||
|
|
|
@ -86,10 +86,6 @@ DownloadsStartup.prototype = {
|
|||
{
|
||||
switch (aTopic) {
|
||||
case "profile-after-change":
|
||||
kObservedTopics.forEach(
|
||||
function (topic) Services.obs.addObserver(this, topic, true),
|
||||
this);
|
||||
|
||||
// Override Toolkit's nsIDownloadManagerUI implementation with our own.
|
||||
// This must be done at application startup and not in the manifest to
|
||||
// ensure that our implementation overrides the original one.
|
||||
|
@ -104,15 +100,21 @@ DownloadsStartup.prototype = {
|
|||
// when nsIDownloadManager will not be available anymore (bug 851471).
|
||||
let useJSTransfer = false;
|
||||
try {
|
||||
// For performance reasons, we don't want to load the DownloadsCommon
|
||||
// module during startup, so we read the preference value directly.
|
||||
useJSTransfer =
|
||||
Services.prefs.getBoolPref("browser.download.useJSTransfer");
|
||||
} catch (ex) {
|
||||
// This is a hidden preference that does not exist by default.
|
||||
}
|
||||
} catch (ex) { }
|
||||
if (useJSTransfer) {
|
||||
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
|
||||
.registerFactory(kTransferCid, "",
|
||||
kTransferContractId, null);
|
||||
} else {
|
||||
// The other notifications are handled internally by the JavaScript
|
||||
// API for downloads, no need to observe when that API is enabled.
|
||||
for (let topic of kObservedTopics) {
|
||||
Services.obs.addObserver(this, topic, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -159,6 +159,47 @@ add_task(function test_notifications_change()
|
|||
do_check_false(receivedOnDownloadChanged);
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that the reference to "this" is correct in the view callbacks.
|
||||
*/
|
||||
add_task(function test_notifications_this()
|
||||
{
|
||||
let list = yield promiseNewDownloadList();
|
||||
|
||||
// Check that we receive change notifications.
|
||||
let receivedOnDownloadAdded = false;
|
||||
let receivedOnDownloadChanged = false;
|
||||
let receivedOnDownloadRemoved = false;
|
||||
let view = {
|
||||
onDownloadAdded: function () {
|
||||
do_check_eq(this, view);
|
||||
receivedOnDownloadAdded = true;
|
||||
},
|
||||
onDownloadChanged: function () {
|
||||
// Only do this check once.
|
||||
if (!receivedOnDownloadChanged) {
|
||||
do_check_eq(this, view);
|
||||
receivedOnDownloadChanged = true;
|
||||
}
|
||||
},
|
||||
onDownloadRemoved: function () {
|
||||
do_check_eq(this, view);
|
||||
receivedOnDownloadRemoved = true;
|
||||
},
|
||||
};
|
||||
list.addView(view);
|
||||
|
||||
let download = yield promiseNewDownload();
|
||||
list.add(download);
|
||||
yield download.start();
|
||||
list.remove(download);
|
||||
|
||||
// Verify that we executed the checks.
|
||||
do_check_true(receivedOnDownloadAdded);
|
||||
do_check_true(receivedOnDownloadChanged);
|
||||
do_check_true(receivedOnDownloadRemoved);
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that download is removed on history expiration.
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче