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