forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			295 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
	
		
			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/. */
 | |
| 
 | |
| /**
 | |
|  * Provides functions to prevent multiple automatic downloads.
 | |
|  */
 | |
| 
 | |
| import {
 | |
|   Download,
 | |
|   DownloadError,
 | |
| } from "resource://gre/modules/DownloadCore.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
 | |
|   DownloadList: "resource://gre/modules/DownloadList.sys.mjs",
 | |
|   Downloads: "resource://gre/modules/Downloads.sys.mjs",
 | |
|   DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Each window tracks download spam independently, so one of these objects is
 | |
|  * constructed for each window. This is responsible for tracking the spam and
 | |
|  * updating the window's downloads UI accordingly.
 | |
|  */
 | |
| class WindowSpamProtection {
 | |
|   constructor(window) {
 | |
|     this._window = window;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * This map stores blocked spam downloads for the window, keyed by the
 | |
|    * download's source URL. This is done so we can track the number of times a
 | |
|    * given download has been blocked.
 | |
|    * @type {Map<String, DownloadSpam>}
 | |
|    */
 | |
|   _downloadSpamForUrl = new Map();
 | |
| 
 | |
|   /**
 | |
|    * This set stores views that are waiting to have download notification
 | |
|    * listeners attached. They will be attached when the spamList is created
 | |
|    * (i.e. when the first spam download is blocked).
 | |
|    * @type {Set<Object>}
 | |
|    */
 | |
|   _pendingViews = new Set();
 | |
| 
 | |
|   /**
 | |
|    * Set to true when we first start _blocking downloads in the window. This is
 | |
|    * used to lazily load the spamList. Spam downloads are rare enough that many
 | |
|    * sessions will have no blocked downloads. So we don't want to create a
 | |
|    * DownloadList unless we actually need it.
 | |
|    * @type {Boolean}
 | |
|    */
 | |
|   _blocking = false;
 | |
| 
 | |
|   /**
 | |
|    * A per-window DownloadList for blocked spam downloads. Registered views will
 | |
|    * be sent notifications about downloads in this list, so that blocked spam
 | |
|    * downloads can be represented in the UI. If spam downloads haven't been
 | |
|    * blocked in the window, this will be undefined. See DownloadList.sys.mjs.
 | |
|    * @type {DownloadList | undefined}
 | |
|    */
 | |
|   get spamList() {
 | |
|     if (!this._blocking) {
 | |
|       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<Window, WindowSpamProtection>}
 | |
|    */
 | |
|   _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.
 | |
|  * @extends 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;
 | |
|   }
 | |
| }
 | 
