mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			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;
 | 
						|
  }
 | 
						|
}
 |