import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,193 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = [
"DownloadImport",
];
// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm")
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
/**
* These values come from the previous interface
* nsIDownloadManager, which has now been deprecated.
* These are the only types of download states that
* we will import.
*/
const DOWNLOAD_NOTSTARTED = -1;
const DOWNLOAD_DOWNLOADING = 0;
const DOWNLOAD_PAUSED = 4;
const DOWNLOAD_QUEUED = 5;
// DownloadImport
/**
* Provides an object that has a method to import downloads
* from the previous SQLite storage format.
*
* @param aList A DownloadList where each successfully
* imported download will be added.
* @param aPath The path to the database file.
*/
this.DownloadImport = function (aList, aPath)
{
this.list = aList;
this.path = aPath;
}
this.DownloadImport.prototype = {
/**
* Imports unfinished downloads from the previous SQLite storage
* format (supporting schemas 7 and up), to the new Download object
* format. Each imported download will be added to the DownloadList
*
* @return {Promise}
* @resolves When the operation has completed (i.e., every download
* from the previous database has been read and added to
* the DownloadList)
*/
import: function () {
return Task.spawn(function* task_DI_import() {
let connection = yield Sqlite.openConnection({ path: this.path });
try {
let schemaVersion = yield connection.getSchemaVersion();
// We don't support schemas older than version 7 (from 2007)
// - Version 7 added the columns mimeType, preferredApplication
// and preferredAction in 2007
// - Version 8 added the column autoResume in 2007
// (if we encounter version 7 we will treat autoResume = false)
// - Version 9 is the last known version, which added a unique
// GUID text column that is not used here
if (schemaVersion < 7) {
throw new Error("Unable to import in-progress downloads because "
+ "the existing profile is too old.");
}
let rows = yield connection.execute("SELECT * FROM moz_downloads");
for (let row of rows) {
try {
// Get the DB row data
let source = row.getResultByName("source");
let target = row.getResultByName("target");
let tempPath = row.getResultByName("tempPath");
let startTime = row.getResultByName("startTime");
let state = row.getResultByName("state");
let referrer = row.getResultByName("referrer");
let maxBytes = row.getResultByName("maxBytes");
let mimeType = row.getResultByName("mimeType");
let preferredApplication = row.getResultByName("preferredApplication");
let preferredAction = row.getResultByName("preferredAction");
let entityID = row.getResultByName("entityID");
let autoResume = false;
try {
autoResume = (row.getResultByName("autoResume") == 1);
} catch (ex) {
// autoResume wasn't present in schema version 7
}
if (!source) {
throw new Error("Attempted to import a row with an empty " +
"source column.");
}
let resumeDownload = false;
switch (state) {
case DOWNLOAD_NOTSTARTED:
case DOWNLOAD_QUEUED:
case DOWNLOAD_DOWNLOADING:
resumeDownload = true;
break;
case DOWNLOAD_PAUSED:
resumeDownload = autoResume;
break;
default:
// We won't import downloads in other states
continue;
}
// Transform the data
let targetPath = NetUtil.newURI(target)
.QueryInterface(Ci.nsIFileURL).file.path;
let launchWhenSucceeded = (preferredAction != Ci.nsIMIMEInfo.saveToDisk);
let downloadOptions = {
source: {
url: source,
referrer: referrer
},
target: {
path: targetPath,
partFilePath: tempPath,
},
saver: {
type: "copy",
entityID: entityID
},
startTime: new Date(startTime / 1000),
totalBytes: maxBytes,
hasPartialData: !!tempPath,
tryToKeepPartialData: true,
launchWhenSucceeded: launchWhenSucceeded,
contentType: mimeType,
launcherPath: preferredApplication
};
// Paused downloads that should not be auto-resumed are considered
// in a "canceled" state.
if (!resumeDownload) {
downloadOptions.canceled = true;
}
let download = yield Downloads.createDownload(downloadOptions);
yield this.list.add(download);
if (resumeDownload) {
download.start().catch(() => {});
} else {
yield download.refresh();
}
} catch (ex) {
Cu.reportError("Error importing download: " + ex);
}
}
} catch (ex) {
Cu.reportError(ex);
} finally {
yield connection.close();
}
}.bind(this));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,309 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This component implements the XPCOM interfaces required for integration with
* the legacy download components.
*
* New code is expected to use the "Downloads.jsm" module directly, without
* going through the interfaces implemented in this XPCOM component. These
* interfaces are only maintained for backwards compatibility with components
* that still work synchronously on the main thread.
*/
"use strict";
// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
// DownloadLegacyTransfer
/**
* 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
* download, and then make the download visible to the user by hooking it up to
* an nsITransfer instance.
*
* Since nsITransfer instances may be created before the download system is
* initialized, and initialization as well as other operations are asynchronous,
* this implementation is able to delay all progress and status notifications it
* receives until the associated Download object is finally created.
*
* Conversely, the DownloadLegacySaver object can also receive execution and
* cancellation requests asynchronously, before or after it is connected to
* this nsITransfer instance. For that reason, those requests are communicated
* in a potentially deferred way, using promise objects.
*
* The component that executes the download implements nsICancelable to receive
* 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 = Promise.defer();
}
DownloadLegacyTransfer.prototype = {
classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsIWebProgressListener2,
Ci.nsITransfer]),
// nsIWebProgressListener
onStateChange: function DLT_onStateChange(aWebProgress, aRequest, aStateFlags,
aStatus)
{
if (!Components.isSuccessCode(aStatus)) {
this._componentFailed = true;
}
if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) &&
(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
let blockedByParentalControls = false;
// If it is a failed download, aRequest.responseStatus doesn't exist.
// (missing file on the server, network failure to download)
try {
// If the request's response has been blocked by Windows Parental Controls
// with an HTTP 450 error code, we must cancel the request synchronously.
blockedByParentalControls = aRequest instanceof Ci.nsIHttpChannel &&
aRequest.responseStatus == 450;
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
aRequest.cancel(Cr.NS_BINDING_ABORTED);
}
}
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 => {
// 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(
aRequest,
this._cancelable instanceof Ci.nsIHelperAppLauncher);
// To handle asynchronous cancellation properly, we should hook up the
// handler only after we have been notified that the main request
// started. We will wait until the main request stopped before
// notifying that the download has been canceled. Since the request has
// not completed yet, deferCanceled is guaranteed to be set.
return download.saver.deferCanceled.promise.then(() => {
// Only cancel if the object executing the download is still running.
if (this._cancelable && !this._componentFailed) {
this._cancelable.cancel(Cr.NS_ERROR_ABORT);
if (this._cancelable instanceof Ci.nsIWebBrowserPersist) {
// This component will not send the STATE_STOP notification.
download.saver.onTransferFinished(aRequest, Cr.NS_ERROR_ABORT);
this._cancelable = null;
}
}
});
}).then(null, 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 => {
// 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(aRequest, aStatus);
}).then(null, Cu.reportError);
// Release the reference to the component executing the download.
this._cancelable = null;
}
},
onProgressChange: function DLT_onProgressChange(aWebProgress, aRequest,
aCurSelfProgress,
aMaxSelfProgress,
aCurTotalProgress,
aMaxTotalProgress)
{
this.onProgressChange64(aWebProgress, aRequest, aCurSelfProgress,
aMaxSelfProgress, aCurTotalProgress,
aMaxTotalProgress);
},
onLocationChange: function () { },
onStatusChange: function DLT_onStatusChange(aWebProgress, aRequest, aStatus,
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(aRequest, aStatus);
}).then(null, Cu.reportError);
}
},
onSecurityChange: function () { },
// 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);
}).then(null, Cu.reportError);
},
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;
},
// nsITransfer
init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
aTempFile, aCancelable, aIsPrivate)
{
this._cancelable = aCancelable;
let launchWhenSucceeded = false, contentType = null, launcherPath = null;
if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
launchWhenSucceeded =
aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk;
contentType = aMIMEInfo.type;
let appHandler = aMIMEInfo.preferredApplicationHandler;
if (aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp &&
appHandler instanceof Ci.nsILocalHandlerApp) {
launcherPath = appHandler.executable.path;
}
}
// Create a new Download object associated to a DownloadLegacySaver, and
// wait for it to be available. This operation may cause the entire
// download system to initialize before the object is created.
Downloads.createDownload({
source: { url: aSource.spec, isPrivate: aIsPrivate },
target: { path: aTarget.QueryInterface(Ci.nsIFileURL).file.path,
partFilePath: aTempFile && aTempFile.path },
saver: "legacy",
launchWhenSucceeded: launchWhenSucceeded,
contentType: contentType,
launcherPath: launcherPath
}).then(function DLT_I_onDownload(aDownload) {
// Legacy components keep partial data when they use a ".part" file.
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);
// Add the download to the list, allowing it to be seen and canceled.
return Downloads.getList(Downloads.ALL).then(list => list.add(aDownload));
}.bind(this)).then(null, Cu.reportError);
},
setSha256Hash: function (hash)
{
this._sha256Hash = hash;
},
setSignatureInfo: function (signatureInfo)
{
this._signatureInfo = signatureInfo;
},
setRedirects: function (redirects)
{
this._redirects = redirects;
},
// Private methods and properties
/**
* This deferred object contains a promise that is resolved with the Download
* object associated with this nsITransfer instance, when it is available.
*/
_deferDownload: null,
/**
* Reference to the component that is executing the download. This component
* allows cancellation through its nsICancelable interface.
*/
_cancelable: null,
/**
* Indicates that the component that executes the download has notified a
* failure condition. In this case, we should never use the component methods
* that cancel the download.
*/
_componentFailed: false,
/**
* Save the SHA-256 hash in raw bytes of the downloaded file.
*/
_sha256Hash: null,
/**
* Save the signature info in a serialized protobuf of the downloaded file.
*/
_signatureInfo: null,
};
// Module
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadLegacyTransfer]);

View file

@ -0,0 +1,559 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This file includes the following constructors and global objects:
*
* DownloadList
* Represents a collection of Download objects that can be viewed and managed by
* the user interface, and persisted across sessions.
*
* DownloadCombinedList
* Provides a unified, unordered list combining public and private downloads.
*
* DownloadSummary
* Provides an aggregated view on the contents of a DownloadList.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"DownloadList",
"DownloadCombinedList",
"DownloadSummary",
];
// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
// DownloadList
/**
* Represents a collection of Download objects that can be viewed and managed by
* the user interface, and persisted across sessions.
*/
this.DownloadList = function ()
{
this._downloads = [];
this._views = new Set();
}
this.DownloadList.prototype = {
/**
* Array of Download objects currently in the list.
*/
_downloads: null,
/**
* Retrieves a snapshot of the downloads that are currently in the list. The
* returned array does not change when downloads are added or removed, though
* the Download objects it contains are still updated in real time.
*
* @return {Promise}
* @resolves An array of Download objects.
* @rejects JavaScript exception.
*/
getAll: function DL_getAll() {
return Promise.resolve(Array.slice(this._downloads, 0));
},
/**
* Adds a new download to the end of the items list.
*
* @note When a download is added to the list, its "onchange" event is
* registered by the list, thus it cannot be used to monitor the
* download. To receive change notifications for downloads that are
* added to the list, use the addView method to register for
* onDownloadChanged notifications.
*
* @param aDownload
* The Download object to add.
*
* @return {Promise}
* @resolves When the download has been added.
* @rejects JavaScript exception.
*/
add: function DL_add(aDownload) {
this._downloads.push(aDownload);
aDownload.onchange = this._change.bind(this, aDownload);
this._notifyAllViews("onDownloadAdded", aDownload);
return Promise.resolve();
},
/**
* Removes a download from the list. If the download was already removed,
* this method has no effect.
*
* This method does not change the state of the download, to allow adding it
* to another list, or control it directly. If you want to dispose of the
* download object, you should cancel it afterwards, and remove any partially
* downloaded data if needed.
*
* @param aDownload
* The Download object to remove.
*
* @return {Promise}
* @resolves When the download has been removed.
* @rejects JavaScript exception.
*/
remove: function DL_remove(aDownload) {
let index = this._downloads.indexOf(aDownload);
if (index != -1) {
this._downloads.splice(index, 1);
aDownload.onchange = null;
this._notifyAllViews("onDownloadRemoved", aDownload);
}
return Promise.resolve();
},
/**
* This function is called when "onchange" events of downloads occur.
*
* @param aDownload
* The Download object that changed.
*/
_change: function DL_change(aDownload) {
this._notifyAllViews("onDownloadChanged", aDownload);
},
/**
* Set of currently registered views.
*/
_views: null,
/**
* Adds a view that will be notified of changes to downloads. The newly added
* view will receive onDownloadAdded notifications for all the downloads that
* are already in the list.
*
* @param aView
* The view object to add. The following methods may be defined:
* {
* onDownloadAdded: function (aDownload) {
* // Called after aDownload is added to the end of the list.
* },
* onDownloadChanged: function (aDownload) {
* // Called after the properties of aDownload change.
* },
* onDownloadRemoved: function (aDownload) {
* // Called after aDownload is removed from the list.
* },
* }
*
* @return {Promise}
* @resolves When the view has been registered and all the onDownloadAdded
* notifications for the existing downloads have been sent.
* @rejects JavaScript exception.
*/
addView: function DL_addView(aView)
{
this._views.add(aView);
if ("onDownloadAdded" in aView) {
for (let download of this._downloads) {
try {
aView.onDownloadAdded(download);
} catch (ex) {
Cu.reportError(ex);
}
}
}
return Promise.resolve();
},
/**
* Removes a view that was previously added using addView.
*
* @param aView
* The view object to remove.
*
* @return {Promise}
* @resolves When the view has been removed. At this point, the removed view
* will not receive any more notifications.
* @rejects JavaScript exception.
*/
removeView: function DL_removeView(aView)
{
this._views.delete(aView);
return Promise.resolve();
},
/**
* Notifies all the views of a download addition, change, or removal.
*
* @param aMethodName
* String containing the name of the method to call on the view.
* @param aDownload
* The Download object that changed.
*/
_notifyAllViews: function (aMethodName, aDownload) {
for (let view of this._views) {
try {
if (aMethodName in view) {
view[aMethodName](aDownload);
}
} catch (ex) {
Cu.reportError(ex);
}
}
},
/**
* Removes downloads from the list that have finished, have failed, or have
* been canceled without keeping partial data. A filter function may be
* specified to remove only a subset of those downloads.
*
* This method finalizes each removed download, ensuring that any partially
* downloaded data associated with it is also removed.
*
* @param aFilterFn
* The filter function is called with each download as its only
* argument, and should return true to remove the download and false
* to keep it. This parameter may be null or omitted to have no
* additional filter.
*/
removeFinished: function DL_removeFinished(aFilterFn) {
Task.spawn(function* () {
let list = yield this.getAll();
for (let download of list) {
// Remove downloads that have been canceled, even if the cancellation
// operation hasn't completed yet so we don't check "stopped" here.
// Failed downloads with partial data are also removed.
if (download.stopped && (!download.hasPartialData || download.error) &&
(!aFilterFn || aFilterFn(download))) {
// Remove the download first, so that the views don't get the change
// notifications that may occur during finalization.
yield this.remove(download);
// Ensure that the download is stopped and no partial data is kept.
// This works even if the download state has changed meanwhile. We
// don't need to wait for the procedure to be complete before
// processing the other downloads in the list.
download.finalize(true).then(null, Cu.reportError);
}
}
}.bind(this)).then(null, Cu.reportError);
},
};
// DownloadCombinedList
/**
* Provides a unified, unordered list combining public and private downloads.
*
* Download objects added to this list are also added to one of the two
* underlying lists, based on their "source.isPrivate" property. Views on this
* list will receive notifications for both public and private downloads.
*
* @param aPublicList
* Underlying DownloadList containing public downloads.
* @param aPrivateList
* Underlying DownloadList containing private downloads.
*/
this.DownloadCombinedList = function (aPublicList, aPrivateList)
{
DownloadList.call(this);
this._publicList = aPublicList;
this._privateList = aPrivateList;
aPublicList.addView(this).then(null, Cu.reportError);
aPrivateList.addView(this).then(null, Cu.reportError);
}
this.DownloadCombinedList.prototype = {
__proto__: DownloadList.prototype,
/**
* Underlying DownloadList containing public downloads.
*/
_publicList: null,
/**
* Underlying DownloadList containing private downloads.
*/
_privateList: null,
/**
* Adds a new download to the end of the items list.
*
* @note When a download is added to the list, its "onchange" event is
* registered by the list, thus it cannot be used to monitor the
* download. To receive change notifications for downloads that are
* added to the list, use the addView method to register for
* onDownloadChanged notifications.
*
* @param aDownload
* The Download object to add.
*
* @return {Promise}
* @resolves When the download has been added.
* @rejects JavaScript exception.
*/
add: function (aDownload)
{
if (aDownload.source.isPrivate) {
return this._privateList.add(aDownload);
}
return this._publicList.add(aDownload);
},
/**
* Removes a download from the list. If the download was already removed,
* this method has no effect.
*
* This method does not change the state of the download, to allow adding it
* to another list, or control it directly. If you want to dispose of the
* download object, you should cancel it afterwards, and remove any partially
* downloaded data if needed.
*
* @param aDownload
* The Download object to remove.
*
* @return {Promise}
* @resolves When the download has been removed.
* @rejects JavaScript exception.
*/
remove: function (aDownload)
{
if (aDownload.source.isPrivate) {
return this._privateList.remove(aDownload);
}
return this._publicList.remove(aDownload);
},
// DownloadList view
onDownloadAdded: function (aDownload)
{
this._downloads.push(aDownload);
this._notifyAllViews("onDownloadAdded", aDownload);
},
onDownloadChanged: function (aDownload)
{
this._notifyAllViews("onDownloadChanged", aDownload);
},
onDownloadRemoved: function (aDownload)
{
let index = this._downloads.indexOf(aDownload);
if (index != -1) {
this._downloads.splice(index, 1);
}
this._notifyAllViews("onDownloadRemoved", aDownload);
},
};
// DownloadSummary
/**
* Provides an aggregated view on the contents of a DownloadList.
*/
this.DownloadSummary = function ()
{
this._downloads = [];
this._views = new Set();
}
this.DownloadSummary.prototype = {
/**
* Array of Download objects that are currently part of the summary.
*/
_downloads: null,
/**
* Underlying DownloadList whose contents should be summarized.
*/
_list: null,
/**
* This method may be called once to bind this object to a DownloadList.
*
* Views on the summarized data can be registered before this object is bound
* to an actual list. This allows the summary to be used without requiring
* the initialization of the DownloadList first.
*
* @param aList
* Underlying DownloadList whose contents should be summarized.
*
* @return {Promise}
* @resolves When the view on the underlying list has been registered.
* @rejects JavaScript exception.
*/
bindToList: function (aList)
{
if (this._list) {
throw new Error("bindToList may be called only once.");
}
return aList.addView(this).then(() => {
// Set the list reference only after addView has returned, so that we don't
// send a notification to our views for each download that is added.
this._list = aList;
this._onListChanged();
});
},
/**
* Set of currently registered views.
*/
_views: null,
/**
* Adds a view that will be notified of changes to the summary. The newly
* added view will receive an initial onSummaryChanged notification.
*
* @param aView
* The view object to add. The following methods may be defined:
* {
* onSummaryChanged: function () {
* // Called after any property of the summary has changed.
* },
* }
*
* @return {Promise}
* @resolves When the view has been registered and the onSummaryChanged
* notification has been sent.
* @rejects JavaScript exception.
*/
addView: function (aView)
{
this._views.add(aView);
if ("onSummaryChanged" in aView) {
try {
aView.onSummaryChanged();
} catch (ex) {
Cu.reportError(ex);
}
}
return Promise.resolve();
},
/**
* Removes a view that was previously added using addView.
*
* @param aView
* The view object to remove.
*
* @return {Promise}
* @resolves When the view has been removed. At this point, the removed view
* will not receive any more notifications.
* @rejects JavaScript exception.
*/
removeView: function (aView)
{
this._views.delete(aView);
return Promise.resolve();
},
/**
* Indicates whether all the downloads are currently stopped.
*/
allHaveStopped: true,
/**
* Indicates the total number of bytes to be transferred before completing all
* the downloads that are currently in progress.
*
* For downloads that do not have a known final size, the number of bytes
* currently transferred is reported as part of this property.
*
* This is zero if no downloads are currently in progress.
*/
progressTotalBytes: 0,
/**
* Number of bytes currently transferred as part of all the downloads that are
* currently in progress.
*
* This is zero if no downloads are currently in progress.
*/
progressCurrentBytes: 0,
/**
* This function is called when any change in the list of downloads occurs,
* and will recalculate the summary and notify the views in case the
* aggregated properties are different.
*/
_onListChanged: function () {
let allHaveStopped = true;
let progressTotalBytes = 0;
let progressCurrentBytes = 0;
// Recalculate the aggregated state. See the description of the individual
// properties for an explanation of the summarization logic.
for (let download of this._downloads) {
if (!download.stopped) {
allHaveStopped = false;
progressTotalBytes += download.hasProgress ? download.totalBytes
: download.currentBytes;
progressCurrentBytes += download.currentBytes;
}
}
// Exit now if the properties did not change.
if (this.allHaveStopped == allHaveStopped &&
this.progressTotalBytes == progressTotalBytes &&
this.progressCurrentBytes == progressCurrentBytes) {
return;
}
this.allHaveStopped = allHaveStopped;
this.progressTotalBytes = progressTotalBytes;
this.progressCurrentBytes = progressCurrentBytes;
// Notify all the views that our properties changed.
for (let view of this._views) {
try {
if ("onSummaryChanged" in view) {
view.onSummaryChanged();
}
} catch (ex) {
Cu.reportError(ex);
}
}
},
// DownloadList view
onDownloadAdded: function (aDownload)
{
this._downloads.push(aDownload);
if (this._list) {
this._onListChanged();
}
},
onDownloadChanged: function (aDownload)
{
this._onListChanged();
},
onDownloadRemoved: function (aDownload)
{
let index = this._downloads.indexOf(aDownload);
if (index != -1) {
this._downloads.splice(index, 1);
}
this._onListChanged();
},
};

View file

@ -0,0 +1,275 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DownloadPlatform.h"
#include "nsAutoPtr.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsINestedURI.h"
#include "nsIProtocolHandler.h"
#include "nsIURI.h"
#include "nsIFile.h"
#include "nsIObserverService.h"
#include "nsISupportsPrimitives.h"
#include "nsDirectoryServiceDefs.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
#ifdef XP_WIN
#include <shlobj.h>
#include <urlmon.h>
#include "nsILocalFileWin.h"
#endif
#ifdef XP_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#include "../../../../xpcom/io/CocoaFileUtils.h"
#endif
#ifdef MOZ_WIDGET_ANDROID
#include "FennecJNIWrappers.h"
#endif
#ifdef MOZ_WIDGET_GTK
#include <gtk/gtk.h>
#endif
using namespace mozilla;
DownloadPlatform *DownloadPlatform::gDownloadPlatformService = nullptr;
NS_IMPL_ISUPPORTS(DownloadPlatform, mozIDownloadPlatform);
DownloadPlatform* DownloadPlatform::GetDownloadPlatform()
{
if (!gDownloadPlatformService) {
gDownloadPlatformService = new DownloadPlatform();
}
NS_ADDREF(gDownloadPlatformService);
#if defined(MOZ_WIDGET_GTK)
g_type_init();
#endif
return gDownloadPlatformService;
}
#ifdef MOZ_ENABLE_GIO
static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
{
GError *err = nullptr;
g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err);
if (err) {
#ifdef DEBUG
NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
#endif
g_error_free(err);
}
}
#endif
#ifdef XP_MACOSX
// Caller is responsible for freeing any result (CF Create Rule)
CFURLRef CreateCFURLFromNSIURI(nsIURI *aURI) {
nsAutoCString spec;
if (aURI) {
aURI->GetSpec(spec);
}
CFStringRef urlStr = ::CFStringCreateWithCString(kCFAllocatorDefault,
spec.get(),
kCFStringEncodingUTF8);
if (!urlStr) {
return NULL;
}
CFURLRef url = ::CFURLCreateWithString(kCFAllocatorDefault,
urlStr,
NULL);
::CFRelease(urlStr);
return url;
}
#endif
nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer, nsIFile* aTarget,
const nsACString& aContentType, bool aIsPrivate)
{
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) \
|| defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GONK)
nsAutoString path;
if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)
// On Windows and Gtk, add the download to the system's "recent documents"
// list, with a pref to disable.
{
bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);
#ifdef MOZ_WIDGET_ANDROID
if (jni::IsFennec() && addToRecentDocs) {
java::DownloadsIntegration::ScanMedia(path, aContentType);
}
#else
if (addToRecentDocs && !aIsPrivate) {
#ifdef XP_WIN
::SHAddToRecentDocs(SHARD_PATHW, path.get());
#elif defined(MOZ_WIDGET_GTK)
GtkRecentManager* manager = gtk_recent_manager_get_default();
gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
nullptr, nullptr);
if (uri) {
gtk_recent_manager_add_item(manager, uri);
g_free(uri);
}
#endif
}
#endif
#ifdef MOZ_ENABLE_GIO
// Use GIO to store the source URI for later display in the file manager.
GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
nsCString source_uri;
nsresult rv = aSource->GetSpec(source_uri);
NS_ENSURE_SUCCESS(rv, rv);
GFileInfo *file_info = g_file_info_new();
g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get());
g_file_set_attributes_async(gio_file,
file_info,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
nullptr, gio_set_metadata_done, nullptr);
g_object_unref(file_info);
g_object_unref(gio_file);
#endif
}
#endif
#ifdef XP_MACOSX
// On OS X, make the downloads stack bounce.
CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
NS_ConvertUTF16toUTF8(path).get(),
kCFStringEncodingUTF8);
CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
observedObject, nullptr, TRUE);
::CFRelease(observedObject);
// Add OS X origin and referrer file metadata
CFStringRef pathCFStr = NULL;
if (!path.IsEmpty()) {
pathCFStr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
(const UniChar*)path.get(),
path.Length());
}
if (pathCFStr) {
bool isFromWeb = IsURLPossiblyFromWeb(aSource);
CFURLRef sourceCFURL = CreateCFURLFromNSIURI(aSource);
CFURLRef referrerCFURL = CreateCFURLFromNSIURI(aReferrer);
CocoaFileUtils::AddOriginMetadataToFile(pathCFStr,
sourceCFURL,
referrerCFURL);
CocoaFileUtils::AddQuarantineMetadataToFile(pathCFStr,
sourceCFURL,
referrerCFURL,
isFromWeb);
::CFRelease(pathCFStr);
if (sourceCFURL) {
::CFRelease(sourceCFURL);
}
if (referrerCFURL) {
::CFRelease(referrerCFURL);
}
}
#endif
}
#endif
return NS_OK;
}
nsresult DownloadPlatform::MapUrlToZone(const nsAString& aURL,
uint32_t* aZone)
{
#ifdef XP_WIN
RefPtr<IInternetSecurityManager> inetSecMgr;
if (FAILED(CoCreateInstance(CLSID_InternetSecurityManager, NULL,
CLSCTX_ALL, IID_IInternetSecurityManager,
getter_AddRefs(inetSecMgr)))) {
return NS_ERROR_UNEXPECTED;
}
DWORD zone;
if (inetSecMgr->MapUrlToZone(PromiseFlatString(aURL).get(),
&zone, 0) != S_OK) {
return NS_ERROR_UNEXPECTED;
} else {
*aZone = zone;
}
return NS_OK;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
// Check if a URI is likely to be web-based, by checking its URI flags.
// If in doubt (e.g. if anything fails during the check) claims things
// are from the web.
bool DownloadPlatform::IsURLPossiblyFromWeb(nsIURI* aURI)
{
nsCOMPtr<nsIIOService> ios = do_GetIOService();
nsCOMPtr<nsIURI> uri = aURI;
if (!ios) {
return true;
}
while (uri) {
// We're not using nsIIOService::ProtocolHasFlags because it doesn't
// take per-URI flags into account. We're also not using
// NS_URIChainHasFlags because we're checking for *any* of 3 flags
// to be present on *all* of the nested URIs, which it can't do.
nsAutoCString scheme;
nsresult rv = uri->GetScheme(scheme);
if (NS_FAILED(rv)) {
return true;
}
nsCOMPtr<nsIProtocolHandler> ph;
rv = ios->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
if (NS_FAILED(rv)) {
return true;
}
uint32_t flags;
rv = ph->DoGetProtocolFlags(uri, &flags);
if (NS_FAILED(rv)) {
return true;
}
// If not dangerous to load, not a UI resource and not a local file,
// assume this is from the web:
if (!(flags & nsIProtocolHandler::URI_DANGEROUS_TO_LOAD) &&
!(flags & nsIProtocolHandler::URI_IS_UI_RESOURCE) &&
!(flags & nsIProtocolHandler::URI_IS_LOCAL_FILE)) {
return true;
}
// Otherwise, check if the URI is nested, and if so go through
// the loop again:
nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
uri = nullptr;
if (nestedURI) {
rv = nestedURI->GetInnerURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return true;
}
}
}
return false;
}

View file

@ -0,0 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __DownloadPlatform_h__
#define __DownloadPlatform_h__
#include "mozIDownloadPlatform.h"
#include "nsCOMPtr.h"
class nsIURI;
class DownloadPlatform : public mozIDownloadPlatform
{
protected:
virtual ~DownloadPlatform() { }
public:
NS_DECL_ISUPPORTS
NS_DECL_MOZIDOWNLOADPLATFORM
DownloadPlatform() { }
static DownloadPlatform *gDownloadPlatformService;
static DownloadPlatform* GetDownloadPlatform();
private:
static bool IsURLPossiblyFromWeb(nsIURI* aURI);
};
#endif

View file

@ -0,0 +1,203 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Handles serialization of Download objects and persistence into a file, so
* that the state of downloads can be restored across sessions.
*
* The file is stored in JSON format, without indentation. With indentation
* applied, the file would look like this:
*
* {
* "list": [
* {
* "source": "http://www.example.com/download.txt",
* "target": "/home/user/Downloads/download.txt"
* },
* {
* "source": {
* "url": "http://www.example.com/download.txt",
* "referrer": "http://www.example.com/referrer.html"
* },
* "target": "/home/user/Downloads/download-2.txt"
* }
* ]
* }
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"DownloadStore",
];
// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm")
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
return new TextDecoder();
});
XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
return new TextEncoder();
});
// DownloadStore
/**
* Handles serialization of Download objects and persistence into a file, so
* that the state of downloads can be restored across sessions.
*
* @param aList
* DownloadList object to be populated or serialized.
* @param aPath
* String containing the file path where data should be saved.
*/
this.DownloadStore = function (aList, aPath)
{
this.list = aList;
this.path = aPath;
}
this.DownloadStore.prototype = {
/**
* DownloadList object to be populated or serialized.
*/
list: null,
/**
* String containing the file path where data should be saved.
*/
path: "",
/**
* This function is called with a Download object as its first argument, and
* should return true if the item should be saved.
*/
onsaveitem: () => true,
/**
* Loads persistent downloads from the file to the list.
*
* @return {Promise}
* @resolves When the operation finished successfully.
* @rejects JavaScript exception.
*/
load: function DS_load()
{
return Task.spawn(function* task_DS_load() {
let bytes;
try {
bytes = yield OS.File.read(this.path);
} catch (ex) {
if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
throw ex;
}
// If the file does not exist, there are no downloads to load.
return;
}
let storeData = JSON.parse(gTextDecoder.decode(bytes));
// Create live downloads based on the static snapshot.
for (let downloadData of storeData.list) {
try {
let download = yield Downloads.createDownload(downloadData);
try {
if (!download.succeeded && !download.canceled && !download.error) {
// Try to restart the download if it was in progress during the
// previous session. Ignore errors.
download.start().catch(() => {});
} else {
// If the download was not in progress, try to update the current
// progress from disk. This is relevant in case we retained
// partially downloaded data.
yield download.refresh();
}
} finally {
// Add the download to the list if we succeeded in creating it,
// after we have updated its initial state.
yield this.list.add(download);
}
} catch (ex) {
// If an item is unrecognized, don't prevent others from being loaded.
Cu.reportError(ex);
}
}
}.bind(this));
},
/**
* Saves persistent downloads from the list to the file.
*
* If an error occurs, the previous file is not deleted.
*
* @return {Promise}
* @resolves When the operation finished successfully.
* @rejects JavaScript exception.
*/
save: function DS_save()
{
return Task.spawn(function* task_DS_save() {
let downloads = yield this.list.getAll();
// Take a static snapshot of the current state of all the downloads.
let storeData = { list: [] };
let atLeastOneDownload = false;
for (let download of downloads) {
try {
if (!this.onsaveitem(download)) {
continue;
}
let serializable = download.toSerializable();
if (!serializable) {
// This item cannot be persisted across sessions.
continue;
}
storeData.list.push(serializable);
atLeastOneDownload = true;
} catch (ex) {
// If an item cannot be converted to a serializable form, don't
// prevent others from being saved.
Cu.reportError(ex);
}
}
if (atLeastOneDownload) {
// Create or overwrite the file if there are downloads to save.
let bytes = gTextEncoder.encode(JSON.stringify(storeData));
yield OS.File.writeAtomic(this.path, bytes,
{ tmpPath: this.path + ".tmp" });
} else {
// Remove the file if there are no downloads to save at all.
try {
yield OS.File.remove(this.path);
} catch (ex) {
if (!(ex instanceof OS.File.Error) ||
!(ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
throw ex;
}
// On Windows, we may get an access denied error instead of a no such
// file error if the file existed before, and was recently deleted.
}
}
}.bind(this));
},
};

View file

@ -0,0 +1,243 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Provides functions to handle status and messages in the user interface.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"DownloadUIHelper",
];
// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
const kStringBundleUrl =
"chrome://mozapps/locale/downloads/downloads.properties";
const kStringsRequiringFormatting = {
fileExecutableSecurityWarning: true,
cancelDownloadsOKTextMultiple: true,
quitCancelDownloadsAlertMsgMultiple: true,
quitCancelDownloadsAlertMsgMacMultiple: true,
offlineCancelDownloadsAlertMsgMultiple: true,
leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2: true
};
// DownloadUIHelper
/**
* Provides functions to handle status and messages in the user interface.
*/
this.DownloadUIHelper = {
/**
* Returns an object that can be used to display prompts related to downloads.
*
* The prompts may be either anchored to a specified window, or anchored to
* the most recently active window, for example if the prompt is displayed in
* response to global notifications that are not associated with any window.
*
* @param aParent
* If specified, should reference the nsIDOMWindow to which the prompts
* should be attached. If omitted, the prompts will be attached to the
* most recently active window.
*
* @return A DownloadPrompter object.
*/
getPrompter: function (aParent)
{
return new DownloadPrompter(aParent || null);
},
};
/**
* Returns an object whose keys are the string names from the downloads string
* bundle, and whose values are either the translated strings or functions
* returning formatted strings.
*/
XPCOMUtils.defineLazyGetter(DownloadUIHelper, "strings", function () {
let strings = {};
let sb = Services.strings.createBundle(kStringBundleUrl);
let enumerator = sb.getSimpleEnumeration();
while (enumerator.hasMoreElements()) {
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
let stringName = string.key;
if (stringName in kStringsRequiringFormatting) {
strings[stringName] = function () {
// Convert "arguments" to a real array before calling into XPCOM.
return sb.formatStringFromName(stringName,
Array.slice(arguments, 0),
arguments.length);
};
} else {
strings[stringName] = string.value;
}
}
return strings;
});
// DownloadPrompter
/**
* Allows displaying prompts related to downloads.
*
* @param aParent
* The nsIDOMWindow to which prompts should be attached, or null to
* attach prompts to the most recently active window.
*/
this.DownloadPrompter = function (aParent)
{
if (AppConstants.MOZ_B2G) {
// On B2G there is no prompter implementation.
this._prompter = null;
} else {
this._prompter = Services.ww.getNewPrompter(aParent);
}
}
this.DownloadPrompter.prototype = {
/**
* Constants with the different type of prompts.
*/
ON_QUIT: "prompt-on-quit",
ON_OFFLINE: "prompt-on-offline",
ON_LEAVE_PRIVATE_BROWSING: "prompt-on-leave-private-browsing",
/**
* nsIPrompt instance for displaying messages.
*/
_prompter: null,
/**
* Displays a warning message box that informs that the specified file is
* executable, and asks whether the user wants to launch it. The user is
* given the option of disabling future instances of this warning.
*
* @param aPath
* String containing the full path to the file to be opened.
*
* @return {Promise}
* @resolves Boolean indicating whether the launch operation can continue.
* @rejects JavaScript exception.
*/
confirmLaunchExecutable: function (aPath)
{
const kPrefAlertOnEXEOpen = "browser.download.manager.alertOnEXEOpen";
try {
// Always launch in case we have no prompter implementation.
if (!this._prompter) {
return Promise.resolve(true);
}
try {
if (!Services.prefs.getBoolPref(kPrefAlertOnEXEOpen)) {
return Promise.resolve(true);
}
} catch (ex) {
// If the preference does not exist, continue with the prompt.
}
let leafName = OS.Path.basename(aPath);
let s = DownloadUIHelper.strings;
let checkState = { value: false };
let shouldLaunch = this._prompter.confirmCheck(
s.fileExecutableSecurityWarningTitle,
s.fileExecutableSecurityWarning(leafName, leafName),
s.fileExecutableSecurityWarningDontAsk,
checkState);
if (shouldLaunch) {
Services.prefs.setBoolPref(kPrefAlertOnEXEOpen, !checkState.value);
}
return Promise.resolve(shouldLaunch);
} catch (ex) {
return Promise.reject(ex);
}
},
/**
* Displays a warning message box that informs that there are active
* downloads, and asks whether the user wants to cancel them or not.
*
* @param aDownloadsCount
* The current downloads count.
* @param aPromptType
* The type of prompt notification depending on the observer.
*
* @return False to cancel the downloads and continue, true to abort the
* operation.
*/
confirmCancelDownloads: function DP_confirmCancelDownload(aDownloadsCount,
aPromptType)
{
// Always continue in case we have no prompter implementation, or if there
// are no active downloads.
if (!this._prompter || aDownloadsCount <= 0) {
return false;
}
let s = DownloadUIHelper.strings;
let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
let okButton = aDownloadsCount > 1 ? s.cancelDownloadsOKTextMultiple(aDownloadsCount)
: s.cancelDownloadsOKText;
let title, message, cancelButton;
switch (aPromptType) {
case this.ON_QUIT:
title = s.quitCancelDownloadsAlertTitle;
if (AppConstants.platform != "macosx") {
message = aDownloadsCount > 1
? s.quitCancelDownloadsAlertMsgMultiple(aDownloadsCount)
: s.quitCancelDownloadsAlertMsg;
cancelButton = s.dontQuitButtonWin;
} else {
message = aDownloadsCount > 1
? s.quitCancelDownloadsAlertMsgMacMultiple(aDownloadsCount)
: s.quitCancelDownloadsAlertMsgMac;
cancelButton = s.dontQuitButtonMac;
}
break;
case this.ON_OFFLINE:
title = s.offlineCancelDownloadsAlertTitle;
message = aDownloadsCount > 1
? s.offlineCancelDownloadsAlertMsgMultiple(aDownloadsCount)
: s.offlineCancelDownloadsAlertMsg;
cancelButton = s.dontGoOfflineButton;
break;
case this.ON_LEAVE_PRIVATE_BROWSING:
title = s.leavePrivateBrowsingCancelDownloadsAlertTitle;
message = aDownloadsCount > 1
? s.leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2(aDownloadsCount)
: s.leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2;
cancelButton = s.dontLeavePrivateBrowsingButton2;
break;
}
let rv = this._prompter.confirmEx(title, message, buttonFlags, okButton,
cancelButton, null, null, {});
return (rv == 1);
}
};

View file

@ -0,0 +1,305 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Main entry point to get references to all the back-end objects.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"Downloads",
];
// Globals
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Integration.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/DownloadCore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadCombinedList",
"resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadList",
"resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadSummary",
"resource://gre/modules/DownloadList.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
"resource://gre/modules/DownloadUIHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
"resource://gre/modules/DownloadIntegration.jsm");
// Downloads
/**
* This object is exposed directly to the consumers of this JavaScript module,
* and provides the only entry point to get references to back-end objects.
*/
this.Downloads = {
/**
* Work on downloads that were not started from a private browsing window.
*/
get PUBLIC() {
return "{Downloads.PUBLIC}";
},
/**
* Work on downloads that were started from a private browsing window.
*/
get PRIVATE() {
return "{Downloads.PRIVATE}";
},
/**
* Work on both Downloads.PRIVATE and Downloads.PUBLIC downloads.
*/
get ALL() {
return "{Downloads.ALL}";
},
/**
* Creates a new Download object.
*
* @param aProperties
* Provides the initial properties for the newly created download.
* This matches the serializable representation of a Download object.
* Some of the most common properties in this object include:
* {
* source: String containing the URI for the download source.
* Alternatively, may be an nsIURI, a DownloadSource object,
* or an object with the following properties:
* {
* url: String containing the URI for the download source.
* isPrivate: Indicates whether the download originated from a
* private window. If omitted, the download is public.
* referrer: String containing the referrer URI of the download
* source. Can be omitted or null if no referrer should
* be sent or the download source is not HTTP.
* },
* target: String containing the path of the target file.
* Alternatively, may be an nsIFile, a DownloadTarget object,
* or an object with the following properties:
* {
* path: String containing the path of the target file.
* },
* saver: String representing the class of the download operation.
* If omitted, defaults to "copy". Alternatively, may be the
* serializable representation of a DownloadSaver object.
* }
*
* @return {Promise}
* @resolves The newly created Download object.
* @rejects JavaScript exception.
*/
createDownload: function D_createDownload(aProperties)
{
try {
return Promise.resolve(Download.fromSerializable(aProperties));
} catch (ex) {
return Promise.reject(ex);
}
},
/**
* Downloads data from a remote network location to a local file.
*
* This download method does not provide user interface, or the ability to
* cancel or restart the download programmatically. For that, you should
* obtain a reference to a Download object using the createDownload function.
*
* Since the download cannot be restarted, any partially downloaded data will
* not be kept in case the download fails.
*
* @param aSource
* String containing the URI for the download source. Alternatively,
* may be an nsIURI or a DownloadSource object.
* @param aTarget
* String containing the path of the target file. Alternatively, may
* be an nsIFile or a DownloadTarget object.
* @param aOptions
* An optional object used to control the behavior of this function.
* You may pass an object with a subset of the following fields:
* {
* isPrivate: Indicates whether the download originated from a
* private window.
* }
*
* @return {Promise}
* @resolves When the download has finished successfully.
* @rejects JavaScript exception if the download failed.
*/
fetch: function (aSource, aTarget, aOptions) {
return this.createDownload({
source: aSource,
target: aTarget,
}).then(function D_SD_onSuccess(aDownload) {
if (aOptions && ("isPrivate" in aOptions)) {
aDownload.source.isPrivate = aOptions.isPrivate;
}
return aDownload.start();
});
},
/**
* Retrieves the specified type of DownloadList object. There is one download
* list for each type, and this method always retrieves a reference to the
* same download list when called with the same argument.
*
* Calling this function may cause the list of public downloads to be reloaded
* from the previous session, if it wasn't loaded already.
*
* @param aType
* This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
* Downloads added to the Downloads.PUBLIC and Downloads.PRIVATE lists
* are reflected in the Downloads.ALL list, and downloads added to the
* Downloads.ALL list are also added to either the Downloads.PUBLIC or
* the Downloads.PRIVATE list based on their properties.
*
* @return {Promise}
* @resolves The requested DownloadList or DownloadCombinedList object.
* @rejects JavaScript exception.
*/
getList: function (aType)
{
if (!this._promiseListsInitialized) {
this._promiseListsInitialized = Task.spawn(function* () {
let publicList = new DownloadList();
let privateList = new DownloadList();
let combinedList = new DownloadCombinedList(publicList, privateList);
try {
yield DownloadIntegration.addListObservers(publicList, false);
yield DownloadIntegration.addListObservers(privateList, true);
yield DownloadIntegration.initializePublicDownloadList(publicList);
} catch (ex) {
Cu.reportError(ex);
}
let publicSummary = yield this.getSummary(Downloads.PUBLIC);
let privateSummary = yield this.getSummary(Downloads.PRIVATE);
let combinedSummary = yield this.getSummary(Downloads.ALL);
yield publicSummary.bindToList(publicList);
yield privateSummary.bindToList(privateList);
yield combinedSummary.bindToList(combinedList);
this._lists[Downloads.PUBLIC] = publicList;
this._lists[Downloads.PRIVATE] = privateList;
this._lists[Downloads.ALL] = combinedList;
}.bind(this));
}
return this._promiseListsInitialized.then(() => this._lists[aType]);
},
/**
* Promise resolved when the initialization of the download lists has
* completed, or null if initialization has never been requested.
*/
_promiseListsInitialized: null,
/**
* After initialization, this object is populated with one key for each type
* of download list that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
* or Downloads.ALL). The values are the DownloadList objects.
*/
_lists: {},
/**
* Retrieves the specified type of DownloadSummary object. There is one
* download summary for each type, and this method always retrieves a
* reference to the same download summary when called with the same argument.
*
* Calling this function does not cause the list of public downloads to be
* reloaded from the previous session. The summary will behave as if no
* downloads are present until the getList method is called.
*
* @param aType
* This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
*
* @return {Promise}
* @resolves The requested DownloadList or DownloadCombinedList object.
* @rejects JavaScript exception.
*/
getSummary: function (aType)
{
if (aType != Downloads.PUBLIC && aType != Downloads.PRIVATE &&
aType != Downloads.ALL) {
throw new Error("Invalid aType argument.");
}
if (!(aType in this._summaries)) {
this._summaries[aType] = new DownloadSummary();
}
return Promise.resolve(this._summaries[aType]);
},
/**
* This object is populated by the getSummary method with one key for each
* type of object that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
* or Downloads.ALL). The values are the DownloadSummary objects.
*/
_summaries: {},
/**
* Returns the system downloads directory asynchronously.
* Mac OSX:
* User downloads directory
* XP/2K:
* My Documents/Downloads
* Vista and others:
* User downloads directory
* Linux:
* XDG user dir spec, with a fallback to Home/Downloads
* Android:
* standard downloads directory i.e. /sdcard
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
getSystemDownloadsDirectory: function D_getSystemDownloadsDirectory() {
return DownloadIntegration.getSystemDownloadsDirectory();
},
/**
* Returns the preferred downloads directory based on the user preferences
* in the current profile asynchronously.
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
getPreferredDownloadsDirectory: function D_getPreferredDownloadsDirectory() {
return DownloadIntegration.getPreferredDownloadsDirectory();
},
/**
* Returns the temporary directory where downloads are placed before the
* final location is chosen, or while the document is opened temporarily
* with an external application. This may or may not be the system temporary
* directory, based on the platform asynchronously.
*
* @return {Promise}
* @resolves The downloads directory string path.
*/
getTemporaryDownloadsDirectory: function D_getTemporaryDownloadsDirectory() {
return DownloadIntegration.getTemporaryDownloadsDirectory();
},
/**
* Constructor for a DownloadError object. When you catch an exception during
* a download, you can use this to verify if "ex instanceof Downloads.Error",
* before reading the exception properties with the error details.
*/
Error: DownloadError,
};

View file

@ -0,0 +1,2 @@
component {1b4c85df-cbdd-4bb6-b04e-613caece083c} DownloadLegacy.js
contract @mozilla.org/transfer;1 {1b4c85df-cbdd-4bb6-b04e-613caece083c}

View file

@ -0,0 +1,31 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
SOURCES += [
'DownloadPlatform.cpp',
]
EXTRA_COMPONENTS += [
'DownloadLegacy.js',
'Downloads.manifest',
]
EXTRA_JS_MODULES += [
'DownloadCore.jsm',
'DownloadImport.jsm',
'DownloadList.jsm',
'Downloads.jsm',
'DownloadStore.jsm',
'DownloadUIHelper.jsm',
]
EXTRA_PP_JS_MODULES += [
'DownloadIntegration.jsm',
]
FINAL_LIBRARY = 'xul'
CXXFLAGS += CONFIG['TK_CFLAGS']