Backed out 2 changesets (bug 1864896) for causing node failures. CLOSED TREE
[gecko.git] / browser / components / newtab / lib / DownloadsManager.sys.mjs
blobf095645d413b15580f21b4585e7cb309c7788411
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { actionTypes as at } from "resource://activity-stream/common/Actions.sys.mjs";
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
11   DownloadsViewUI: "resource:///modules/DownloadsViewUI.sys.mjs",
12   FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
13   NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
14 });
16 const DOWNLOAD_CHANGED_DELAY_TIME = 1000; // time in ms to delay timer for downloads changed events
18 export class DownloadsManager {
19   constructor(store) {
20     this._downloadData = null;
21     this._store = null;
22     this._downloadItems = new Map();
23     this._downloadTimer = null;
24   }
26   setTimeout(callback, delay) {
27     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
28     timer.initWithCallback(callback, delay, Ci.nsITimer.TYPE_ONE_SHOT);
29     return timer;
30   }
32   formatDownload(download) {
33     let referrer = download.source.referrerInfo?.originalReferrer?.spec || null;
34     return {
35       hostname: new URL(download.source.url).hostname,
36       url: download.source.url,
37       path: download.target.path,
38       title: lazy.DownloadsViewUI.getDisplayName(download),
39       description:
40         lazy.DownloadsViewUI.getSizeWithUnits(download) ||
41         lazy.DownloadsCommon.strings.sizeUnknown,
42       referrer,
43       date_added: download.endTime,
44     };
45   }
47   init(store) {
48     this._store = store;
49     this._downloadData = lazy.DownloadsCommon.getData(
50       null /* null for non-private downloads */,
51       true,
52       false,
53       true
54     );
55     this._downloadData.addView(this);
56   }
58   onDownloadAdded(download) {
59     if (!this._downloadItems.has(download.source.url)) {
60       this._downloadItems.set(download.source.url, download);
62       // On startup, all existing downloads fire this notification, so debounce them
63       if (this._downloadTimer) {
64         this._downloadTimer.delay = DOWNLOAD_CHANGED_DELAY_TIME;
65       } else {
66         this._downloadTimer = this.setTimeout(() => {
67           this._downloadTimer = null;
68           this._store.dispatch({ type: at.DOWNLOAD_CHANGED });
69         }, DOWNLOAD_CHANGED_DELAY_TIME);
70       }
71     }
72   }
74   onDownloadRemoved(download) {
75     if (this._downloadItems.has(download.source.url)) {
76       this._downloadItems.delete(download.source.url);
77       this._store.dispatch({ type: at.DOWNLOAD_CHANGED });
78     }
79   }
81   async getDownloads(
82     threshold,
83     {
84       numItems = this._downloadItems.size,
85       onlySucceeded = false,
86       onlyExists = false,
87     }
88   ) {
89     if (!threshold) {
90       return [];
91     }
92     let results = [];
94     // Only get downloads within the time threshold specified and sort by recency
95     const downloadThreshold = Date.now() - threshold;
96     let downloads = [...this._downloadItems.values()]
97       .filter(download => download.endTime > downloadThreshold)
98       .sort((download1, download2) => download1.endTime < download2.endTime);
100     for (const download of downloads) {
101       // Ignore blocked links, but allow long (data:) uris to avoid high CPU
102       if (
103         download.source.url.length < 10000 &&
104         lazy.NewTabUtils.blockedLinks.isBlocked(download.source)
105       ) {
106         continue;
107       }
109       // Only include downloads where the file still exists
110       if (onlyExists) {
111         // Refresh download to ensure the 'exists' attribute is up to date
112         await download.refresh();
113         if (!download.target.exists) {
114           continue;
115         }
116       }
117       // Only include downloads that were completed successfully
118       if (onlySucceeded) {
119         if (!download.succeeded) {
120           continue;
121         }
122       }
123       const formattedDownloadForHighlights = this.formatDownload(download);
124       results.push(formattedDownloadForHighlights);
125       if (results.length === numItems) {
126         break;
127       }
128     }
129     return results;
130   }
132   uninit() {
133     if (this._downloadData) {
134       this._downloadData.removeView(this);
135       this._downloadData = null;
136     }
137     if (this._downloadTimer) {
138       this._downloadTimer.cancel();
139       this._downloadTimer = null;
140     }
141   }
143   onAction(action) {
144     let doDownloadAction = callback => {
145       let download = this._downloadItems.get(action.data.url);
146       if (download) {
147         callback(download);
148       }
149     };
151     switch (action.type) {
152       case at.COPY_DOWNLOAD_LINK:
153         doDownloadAction(download => {
154           lazy.DownloadsCommon.copyDownloadLink(download);
155         });
156         break;
157       case at.REMOVE_DOWNLOAD_FILE:
158         doDownloadAction(download => {
159           lazy.DownloadsCommon.deleteDownload(download).catch(console.error);
160         });
161         break;
162       case at.SHOW_DOWNLOAD_FILE:
163         doDownloadAction(download => {
164           lazy.DownloadsCommon.showDownloadedFile(
165             new lazy.FileUtils.File(download.target.path)
166           );
167         });
168         break;
169       case at.OPEN_DOWNLOAD_FILE:
170         const win = action._target.browser.ownerGlobal;
171         const openWhere =
172           action.data.event && win.whereToOpenLink(action.data.event);
173         doDownloadAction(download => {
174           lazy.DownloadsCommon.openDownload(download, {
175             // Replace "current" or unknown value with "tab" as the default behavior
176             // for opening downloads when handled internally
177             openWhere: ["window", "tab", "tabshifted"].includes(openWhere)
178               ? openWhere
179               : "tab",
180           });
181         });
182         break;
183       case at.UNINIT:
184         this.uninit();
185         break;
186     }
187   }