summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/lib/DownloadsManager.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/lib/DownloadsManager.sys.mjs')
-rw-r--r--browser/components/newtab/lib/DownloadsManager.sys.mjs188
1 files changed, 188 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/DownloadsManager.sys.mjs b/browser/components/newtab/lib/DownloadsManager.sys.mjs
new file mode 100644
index 0000000000..f095645d41
--- /dev/null
+++ b/browser/components/newtab/lib/DownloadsManager.sys.mjs
@@ -0,0 +1,188 @@
+/* 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/. */
+
+import { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
+ DownloadsViewUI: "resource:///modules/DownloadsViewUI.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
+});
+
+const DOWNLOAD_CHANGED_DELAY_TIME = 1000; // time in ms to delay timer for downloads changed events
+
+export class DownloadsManager {
+ constructor(store) {
+ this._downloadData = null;
+ this._store = null;
+ this._downloadItems = new Map();
+ this._downloadTimer = null;
+ }
+
+ setTimeout(callback, delay) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(callback, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+ }
+
+ formatDownload(download) {
+ let referrer = download.source.referrerInfo?.originalReferrer?.spec || null;
+ return {
+ hostname: new URL(download.source.url).hostname,
+ url: download.source.url,
+ path: download.target.path,
+ title: lazy.DownloadsViewUI.getDisplayName(download),
+ description:
+ lazy.DownloadsViewUI.getSizeWithUnits(download) ||
+ lazy.DownloadsCommon.strings.sizeUnknown,
+ referrer,
+ date_added: download.endTime,
+ };
+ }
+
+ init(store) {
+ this._store = store;
+ this._downloadData = lazy.DownloadsCommon.getData(
+ null /* null for non-private downloads */,
+ true,
+ false,
+ true
+ );
+ this._downloadData.addView(this);
+ }
+
+ onDownloadAdded(download) {
+ if (!this._downloadItems.has(download.source.url)) {
+ this._downloadItems.set(download.source.url, download);
+
+ // On startup, all existing downloads fire this notification, so debounce them
+ if (this._downloadTimer) {
+ this._downloadTimer.delay = DOWNLOAD_CHANGED_DELAY_TIME;
+ } else {
+ this._downloadTimer = this.setTimeout(() => {
+ this._downloadTimer = null;
+ this._store.dispatch({ type: at.DOWNLOAD_CHANGED });
+ }, DOWNLOAD_CHANGED_DELAY_TIME);
+ }
+ }
+ }
+
+ onDownloadRemoved(download) {
+ if (this._downloadItems.has(download.source.url)) {
+ this._downloadItems.delete(download.source.url);
+ this._store.dispatch({ type: at.DOWNLOAD_CHANGED });
+ }
+ }
+
+ async getDownloads(
+ threshold,
+ {
+ numItems = this._downloadItems.size,
+ onlySucceeded = false,
+ onlyExists = false,
+ }
+ ) {
+ if (!threshold) {
+ return [];
+ }
+ let results = [];
+
+ // Only get downloads within the time threshold specified and sort by recency
+ const downloadThreshold = Date.now() - threshold;
+ let downloads = [...this._downloadItems.values()]
+ .filter(download => download.endTime > downloadThreshold)
+ .sort((download1, download2) => download1.endTime < download2.endTime);
+
+ for (const download of downloads) {
+ // Ignore blocked links, but allow long (data:) uris to avoid high CPU
+ if (
+ download.source.url.length < 10000 &&
+ lazy.NewTabUtils.blockedLinks.isBlocked(download.source)
+ ) {
+ continue;
+ }
+
+ // Only include downloads where the file still exists
+ if (onlyExists) {
+ // Refresh download to ensure the 'exists' attribute is up to date
+ await download.refresh();
+ if (!download.target.exists) {
+ continue;
+ }
+ }
+ // Only include downloads that were completed successfully
+ if (onlySucceeded) {
+ if (!download.succeeded) {
+ continue;
+ }
+ }
+ const formattedDownloadForHighlights = this.formatDownload(download);
+ results.push(formattedDownloadForHighlights);
+ if (results.length === numItems) {
+ break;
+ }
+ }
+ return results;
+ }
+
+ uninit() {
+ if (this._downloadData) {
+ this._downloadData.removeView(this);
+ this._downloadData = null;
+ }
+ if (this._downloadTimer) {
+ this._downloadTimer.cancel();
+ this._downloadTimer = null;
+ }
+ }
+
+ onAction(action) {
+ let doDownloadAction = callback => {
+ let download = this._downloadItems.get(action.data.url);
+ if (download) {
+ callback(download);
+ }
+ };
+
+ switch (action.type) {
+ case at.COPY_DOWNLOAD_LINK:
+ doDownloadAction(download => {
+ lazy.DownloadsCommon.copyDownloadLink(download);
+ });
+ break;
+ case at.REMOVE_DOWNLOAD_FILE:
+ doDownloadAction(download => {
+ lazy.DownloadsCommon.deleteDownload(download).catch(console.error);
+ });
+ break;
+ case at.SHOW_DOWNLOAD_FILE:
+ doDownloadAction(download => {
+ lazy.DownloadsCommon.showDownloadedFile(
+ new lazy.FileUtils.File(download.target.path)
+ );
+ });
+ break;
+ case at.OPEN_DOWNLOAD_FILE:
+ const win = action._target.browser.ownerGlobal;
+ const openWhere =
+ action.data.event && win.whereToOpenLink(action.data.event);
+ doDownloadAction(download => {
+ lazy.DownloadsCommon.openDownload(download, {
+ // Replace "current" or unknown value with "tab" as the default behavior
+ // for opening downloads when handled internally
+ openWhere: ["window", "tab", "tabshifted"].includes(openWhere)
+ ? openWhere
+ : "tab",
+ });
+ });
+ break;
+ case at.UNINIT:
+ this.uninit();
+ break;
+ }
+ }
+}