зеркало из https://github.com/mozilla/gecko-dev.git
349 строки
9.3 KiB
JavaScript
349 строки
9.3 KiB
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";
|
|
|
|
var EXPORTED_SYMBOLS = ["DownloadNotifications"];
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"Downloads",
|
|
"resource://gre/modules/Downloads.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"FileUtils",
|
|
"resource://gre/modules/FileUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"Notifications",
|
|
"resource://gre/modules/Notifications.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"Services",
|
|
"resource://gre/modules/Services.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"Snackbars",
|
|
"resource://gre/modules/Snackbars.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"UITelemetry",
|
|
"resource://gre/modules/UITelemetry.jsm"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
this,
|
|
"ParentalControls",
|
|
"@mozilla.org/parental-controls-service;1",
|
|
"nsIParentalControlsService"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "strings", () =>
|
|
Services.strings.createBundle("chrome://browser/locale/browser.properties")
|
|
);
|
|
|
|
Object.defineProperty(this, "window", {
|
|
get: () => Services.wm.getMostRecentWindow("navigator:browser"),
|
|
});
|
|
|
|
const kButtons = {
|
|
PAUSE: new DownloadNotificationButton(
|
|
"pause",
|
|
"drawable://pause",
|
|
"alertDownloadsPause"
|
|
),
|
|
RESUME: new DownloadNotificationButton(
|
|
"resume",
|
|
"drawable://play",
|
|
"alertDownloadsResume"
|
|
),
|
|
CANCEL: new DownloadNotificationButton(
|
|
"cancel",
|
|
"drawable://close",
|
|
"alertDownloadsCancel"
|
|
),
|
|
};
|
|
|
|
var notifications = new Map();
|
|
|
|
var DownloadNotifications = {
|
|
_notificationKey: "downloads",
|
|
|
|
observe: function(subject, topic, data) {
|
|
if (topic === "chrome-document-loaded") {
|
|
this.init();
|
|
}
|
|
},
|
|
|
|
init: function() {
|
|
Downloads.getList(Downloads.ALL)
|
|
.then(list => list.addView(this))
|
|
.then(() => (this._viewAdded = true), Cu.reportError);
|
|
|
|
// All click, cancel, and button presses will be handled by this handler as part of the Notifications callback API.
|
|
Notifications.registerHandler(this._notificationKey, this);
|
|
},
|
|
|
|
onDownloadAdded: function(download) {
|
|
// Don't create notifications for pre-existing succeeded downloads.
|
|
// We still add notifications for canceled downloads in case the
|
|
// user decides to retry the download.
|
|
if (download.succeeded && !this._viewAdded) {
|
|
return;
|
|
}
|
|
|
|
if (!ParentalControls.isAllowed(ParentalControls.DOWNLOAD)) {
|
|
download.cancel().catch(Cu.reportError);
|
|
download.removePartialData().catch(Cu.reportError);
|
|
Snackbars.show(
|
|
strings.GetStringFromName("downloads.disabledInGuest"),
|
|
Snackbars.LENGTH_LONG
|
|
);
|
|
return;
|
|
}
|
|
|
|
let notification = new DownloadNotification(download);
|
|
notifications.set(download, notification);
|
|
notification.showOrUpdate();
|
|
|
|
// If this is a new download, show a snackbar as well.
|
|
if (this._viewAdded) {
|
|
Snackbars.show(
|
|
strings.GetStringFromName("alertDownloadsToast"),
|
|
Snackbars.LENGTH_LONG
|
|
);
|
|
}
|
|
},
|
|
|
|
onDownloadChanged: function(download) {
|
|
let notification = notifications.get(download);
|
|
|
|
if (download.succeeded) {
|
|
let file = new FileUtils.File(download.target.path);
|
|
|
|
Snackbars.show(
|
|
strings.formatStringFromName("alertDownloadSucceeded", [file.leafName]),
|
|
Snackbars.LENGTH_LONG,
|
|
{
|
|
action: {
|
|
label: strings.GetStringFromName("helperapps.open"),
|
|
callback: () => {
|
|
UITelemetry.addEvent("launch.1", "toast", null, "downloads");
|
|
try {
|
|
file.launch();
|
|
} catch (ex) {
|
|
this.showInAboutDownloads(download);
|
|
}
|
|
if (notification) {
|
|
notification.hide();
|
|
}
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
if (notification) {
|
|
notification.showOrUpdate();
|
|
}
|
|
},
|
|
|
|
onDownloadRemoved: function(download) {
|
|
let notification = notifications.get(download);
|
|
if (!notification) {
|
|
Cu.reportError("Download doesn't have a notification.");
|
|
return;
|
|
}
|
|
|
|
notification.hide();
|
|
notifications.delete(download);
|
|
},
|
|
|
|
_findDownloadForCookie: function(cookie) {
|
|
return Downloads.getList(Downloads.ALL)
|
|
.then(list => list.getAll())
|
|
.then(downloads => {
|
|
for (let download of downloads) {
|
|
let cookie2 = getCookieFromDownload(download);
|
|
if (cookie2 === cookie) {
|
|
return download;
|
|
}
|
|
}
|
|
|
|
throw new Error("Couldn't find download for " + cookie);
|
|
});
|
|
},
|
|
|
|
onCancel: function(cookie) {
|
|
// TODO: I'm not sure what we do here...
|
|
},
|
|
|
|
showInAboutDownloads: function(download) {
|
|
let hash = "#" + window.encodeURIComponent(download.target.path);
|
|
|
|
// Force using string equality to find a tab
|
|
window.BrowserApp.selectOrAddTab("about:downloads" + hash, null, {
|
|
startsWith: true,
|
|
});
|
|
},
|
|
|
|
onClick: function(cookie) {
|
|
this._findDownloadForCookie(cookie)
|
|
.then(download => {
|
|
if (download.succeeded) {
|
|
// We don't call Download.launch(), because there's (currently) no way to
|
|
// tell if the file was actually launched or not, and we want to show
|
|
// about:downloads if the launch failed.
|
|
let file = new FileUtils.File(download.target.path);
|
|
try {
|
|
file.launch();
|
|
} catch (ex) {
|
|
this.showInAboutDownloads(download);
|
|
}
|
|
} else {
|
|
this.showInAboutDownloads(download);
|
|
}
|
|
})
|
|
.catch(Cu.reportError);
|
|
},
|
|
|
|
onButtonClick: function(button, cookie) {
|
|
this._findDownloadForCookie(cookie)
|
|
.then(download => {
|
|
if (button === kButtons.PAUSE.buttonId) {
|
|
download.cancel().catch(Cu.reportError);
|
|
} else if (button === kButtons.RESUME.buttonId) {
|
|
download.start().catch(Cu.reportError);
|
|
} else if (button === kButtons.CANCEL.buttonId) {
|
|
download.cancel().catch(Cu.reportError);
|
|
download.removePartialData().catch(Cu.reportError);
|
|
}
|
|
})
|
|
.catch(Cu.reportError);
|
|
},
|
|
};
|
|
|
|
function getCookieFromDownload(download) {
|
|
// Arbitrary value used to truncate long Data URLs. See bug 1497526
|
|
const maxUrlLength = 1024;
|
|
return (
|
|
download.target.path +
|
|
download.source.url.slice(-maxUrlLength) +
|
|
download.startTime
|
|
);
|
|
}
|
|
|
|
function DownloadNotification(download) {
|
|
this.download = download;
|
|
this._fileName = OS.Path.basename(download.target.path);
|
|
|
|
this.id = null;
|
|
}
|
|
|
|
DownloadNotification.prototype = {
|
|
_updateFromDownload: function() {
|
|
this._downloading = !this.download.stopped;
|
|
this._paused = this.download.canceled && this.download.hasPartialData;
|
|
this._succeeded = this.download.succeeded;
|
|
|
|
this._show = this._downloading || this._paused || this._succeeded;
|
|
},
|
|
|
|
get options() {
|
|
if (!this._show) {
|
|
return null;
|
|
}
|
|
|
|
let options = {
|
|
icon: "drawable://alert_download",
|
|
cookie: getCookieFromDownload(this.download),
|
|
handlerKey: DownloadNotifications._notificationKey,
|
|
};
|
|
|
|
if (this._downloading) {
|
|
options.icon = "drawable://alert_download_animation";
|
|
if (this.download.currentBytes == 0) {
|
|
this._updateOptionsForStatic(options, "alertDownloadsStart2");
|
|
} else {
|
|
let buttons = this.download.hasPartialData
|
|
? [kButtons.PAUSE, kButtons.CANCEL]
|
|
: [kButtons.CANCEL];
|
|
this._updateOptionsForOngoing(options, buttons);
|
|
}
|
|
} else if (this._paused) {
|
|
this._updateOptionsForOngoing(options, [
|
|
kButtons.RESUME,
|
|
kButtons.CANCEL,
|
|
]);
|
|
} else if (this._succeeded) {
|
|
options.persistent = false;
|
|
this._updateOptionsForStatic(options, "alertDownloadsDone2");
|
|
}
|
|
|
|
return options;
|
|
},
|
|
|
|
_updateOptionsForStatic: function(options, titleName) {
|
|
options.title = strings.GetStringFromName(titleName);
|
|
options.message = this._fileName;
|
|
},
|
|
|
|
_updateOptionsForOngoing: function(options, buttons) {
|
|
options.title = this._fileName;
|
|
options.message = this.download.progress + "%";
|
|
options.buttons = buttons;
|
|
options.ongoing = true;
|
|
options.progress = this.download.progress;
|
|
options.persistent = true;
|
|
},
|
|
|
|
showOrUpdate: function() {
|
|
this._updateFromDownload();
|
|
|
|
if (this._show) {
|
|
if (!this.id) {
|
|
this.id = Notifications.create(this.options);
|
|
} else if (!this.options.ongoing) {
|
|
// We need to explictly cancel ongoing notifications,
|
|
// since updating them to be non-ongoing doesn't seem
|
|
// to work. See bug 1130834.
|
|
Notifications.cancel(this.id);
|
|
this.id = Notifications.create(this.options);
|
|
} else {
|
|
Notifications.update(this.id, this.options);
|
|
}
|
|
} else {
|
|
this.hide();
|
|
}
|
|
},
|
|
|
|
hide: function() {
|
|
if (this.id) {
|
|
Notifications.cancel(this.id);
|
|
this.id = null;
|
|
}
|
|
},
|
|
};
|
|
|
|
function DownloadNotificationButton(
|
|
buttonId,
|
|
iconUrl,
|
|
titleStringName,
|
|
onClicked
|
|
) {
|
|
this.buttonId = buttonId;
|
|
this.title = strings.GetStringFromName(titleStringName);
|
|
this.icon = iconUrl;
|
|
}
|