return undefined; } if (!this._spamList) { this._spamList = new lazy.DownloadList(); } return this._spamList; } /** * A per-window downloads indicator whose state depends on notifications from * DownloadLists registered in the window (for example, the visual state of * the downloads toolbar button). See DownloadsCommon.sys.mjs for more details. * * @type {DownloadsIndicatorData} */ get indicator() { if (!this._indicator) { this._indicator = lazy.DownloadsCommon.getIndicatorData(this._window); } return this._indicator; } /** * Add a blocked download to the spamList or increment the count of an * existing blocked download, then notify listeners about this. * * @param {string} url */ addDownloadSpam(url) { this._blocking = true; // Start listening on registered downloads views, if any exist. this._maybeAddViews(); // If this URL is already paired with a DownloadSpam object, increment its // blocked downloads count by 1 and don't open the downloads panel. if (this._downloadSpamForUrl.has(url)) { let downloadSpam = this._downloadSpamForUrl.get(url); downloadSpam.blockedDownloadsCount += 1; this.indicator.onDownloadStateChanged(downloadSpam); return; } // Otherwise, create a new DownloadSpam object for the URL, add it to the // spamList, and open the downloads panel. let downloadSpam = new DownloadSpam(url); this.spamList.add(downloadSpam); this._downloadSpamForUrl.set(url, downloadSpam); this._notifyDownloadSpamAdded(downloadSpam); } /** * Notify the downloads panel that a new download has been added to the * spamList. This is invoked when a new DownloadSpam object is created. * * @param {DownloadSpam} downloadSpam */ _notifyDownloadSpamAdded(downloadSpam) { let hasActiveDownloads = lazy.DownloadsCommon.summarizeDownloads( this.indicator._activeDownloads() ).numDownloading; if ( !hasActiveDownloads && this._window === lazy.BrowserWindowTracker.getTopWindow() ) { // If there are no active downloads, open the downloads panel. this._window.DownloadsPanel.showPanel(); } else { // Otherwise, flash a taskbar/dock icon notification if available. this._window.getAttention(); } this.indicator.onDownloadAdded(downloadSpam); } /** * Remove the download spam data for a given source URL. * * @param {string} url */ removeDownloadSpamForUrl(url) { if (this._downloadSpamForUrl.has(url)) { let downloadSpam = this._downloadSpamForUrl.get(url); this.spamList.remove(downloadSpam); this.indicator.onDownloadRemoved(downloadSpam); this._downloadSpamForUrl.delete(url); } } /** * Set up a downloads view (e.g. the downloads panel) to receive notifications * about downloads in the spamList. * * @param {object} view An object that implements handlers for download * related notifications, like onDownloadAdded. */ registerView(view) { if (!view || this.spamList?._views.has(view)) { return; } this._pendingViews.add(view); this._maybeAddViews(); } /** * If any downloads have been blocked in the window, add download notification * listeners for each downloads view that has been registered. */ _maybeAddViews() { if (this.spamList) { for (let view of this._pendingViews) { if (!this.spamList._views.has(view)) { this.spamList.addView(view); } } this._pendingViews.clear(); } } /** * Remove download notification listeners for all views. This is invoked when * the window is closed. */ removeAllViews() { if (this.spamList) { for (let view of this.spamList._views) { this.spamList.removeView(view); } } this._pendingViews.clear(); } } /** * Responsible for detecting events related to downloads spam and notifying the * relevant window's WindowSpamProtection object. This is a singleton object, * constructed by DownloadIntegration.sys.mjs when the first download is blocked. */ export class DownloadSpamProtection { /** * Stores spam protection data per-window. * * @type {WeakMap} */ _forWindowMap = new WeakMap(); /** * Add download spam data for a given source URL in the window where the * download was blocked. This is invoked when a download is blocked by * nsExternalAppHandler::IsDownloadSpam * * @param {string} url * @param {Window} window */ update(url, window) { if (window == null) { lazy.DownloadsCommon.log( "Download spam blocked in a non-chrome window. URL: ", url ); return; } // Get the spam protection object for a given window or create one if it // does not already exist. Also attach notification listeners to any pending // downloads views. let wsp = this._forWindowMap.get(window) ?? new WindowSpamProtection(window); this._forWindowMap.set(window, wsp); wsp.addDownloadSpam(url); } /** * Get the spam list for a given window (provided it exists). * * @param {Window} window * @returns {DownloadList} */ getSpamListForWindow(window) { return this._forWindowMap.get(window)?.spamList; } /** * Remove the download spam data for a given source URL in the passed window, * if any exists. * * @param {string} url * @param {Window} window */ removeDownloadSpamForWindow(url, window) { let wsp = this._forWindowMap.get(window); wsp?.removeDownloadSpamForUrl(url); } /** * Create the spam protection object for a given window (if not already * created) and prepare to start listening for notifications on the passed * downloads view. The bulk of resources won't be expended until a download is * blocked. To add multiple views, call this method multiple times. * * @param {object} view An object that implements handlers for download * related notifications, like onDownloadAdded. * @param {Window} window */ register(view, window) { let wsp = this._forWindowMap.get(window) ?? new WindowSpamProtection(window); // Try setting up the view now; it will be deferred if there's no spam. wsp.registerView(view); this._forWindowMap.set(window, wsp); } /** * Remove the spam protection object for a window when it is closed. * * @param {Window} window */ unregister(window) { let wsp = this._forWindowMap.get(window); if (wsp) { // Stop listening on the view if it was previously set up. wsp.removeAllViews(); this._forWindowMap.delete(window); } } } /** * Represents a special Download object for download spam. * * @augments Download */ class DownloadSpam extends Download { constructor(url) { super(); this.hasBlockedData = true; this.stopped = true; this.error = new DownloadError({ becauseBlockedByReputationCheck: true, reputationCheckVerdict: lazy.Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM, }); this.target = { path: "" }; this.source = { url }; this.blockedDownloadsCount = 1; } } PK