Verified Commit 46181e81 authored by Dan Ballard's avatar Dan Ballard Committed by ma1
Browse files

Bug 40701: Add security warning when downloading a file

Shown in the downloads panel, about:downloads and places.xhtml.
parent 7e171262
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -216,6 +216,7 @@ var DownloadsView = {
 */
function DownloadsPlacesView(
  aRichListBox,
  torWarningMessageBar,
  aActive = true,
  aSuppressionFlag = DownloadsCommon.SUPPRESS_ALL_DOWNLOADS_OPEN
) {
@@ -244,6 +245,46 @@ function DownloadsPlacesView(
      aSuppressionFlag;
  }

  // Tor browser warning message/alert shown above the list.

  const PREF_SHOW_DOWNLOAD_WARNING = "browser.download.showTorWarning";
  // Observe changes to the tor warning pref.
  const torWarningPrefObserver = () => {
    if (Services.prefs.getBoolPref(PREF_SHOW_DOWNLOAD_WARNING)) {
      torWarningMessageBar.hidden = false;
    } else {
      // Re-assign focus if it is about to be lost.
      if (torWarningMessageBar.contains(document.activeElement)) {
        // Try and focus the downloads list.
        // NOTE: If #downloadsListBox is still hidden, this will do nothing.
        // But in this case there are no other focusable targets within the
        // view, so we just leave it up to the focus handler.
        this._richlistbox.focus({ preventFocusRing: true });
      }
      torWarningMessageBar.hidden = true;
    }
  };

  Services.prefs.addObserver(
    PREF_SHOW_DOWNLOAD_WARNING,
    torWarningPrefObserver
  );
  // Initialize.
  torWarningPrefObserver();

  window.addEventListener("unload", () => {
    Services.prefs.removeObserver(
      PREF_SHOW_DOWNLOAD_WARNING,
      torWarningPrefObserver
    );
  });

  torWarningMessageBar
    .querySelector(".downloads-tor-warning-dismiss-button")
    .addEventListener("click", event => {
      Services.prefs.setBoolPref(PREF_SHOW_DOWNLOAD_WARNING, false);
    });

  // Make sure to unregister the view if the window is closed.
  window.addEventListener(
    "unload",
+25 −4
Original line number Diff line number Diff line
@@ -10,6 +10,9 @@ const { PrivateBrowsingUtils } = ChromeUtils.importESModule(

var ContentAreaDownloadsView = {
  init() {
    const torWarningMessage = document.getElementById(
      "aboutDownloadsTorWarning"
    );
    let box = document.getElementById("downloadsListBox");
    let suppressionFlag = DownloadsCommon.SUPPRESS_CONTENT_AREA_DOWNLOADS_OPEN;
    box.addEventListener(
@@ -17,9 +20,22 @@ var ContentAreaDownloadsView = {
      () => {
        // Set focus to Downloads list once it is created
        // And prevent it from showing the focus ring around the richlistbox (Bug 1702694)
        // Prevent focusing the list whilst the tor browser warning is shown.
        // Some screen readers (tested with Orca and NVDA) will not read out
        // alerts if they are already present on page load. In that case, a
        // screen reader user may not be aware of the warning before they
        // interact with the downloads list, which we do not want.
        // Some hacky workarounds were tested with Orca to get it to read back
        // the alert before the focus is read, but this was inconsistent and the
        // experience was bad.
        // Without auto-focusing the downloads list, a screen reader should not
        // skip beyond the alert's content.
        if (torWarningMessage.hidden) {
          document
            .getElementById("downloadsListBox")
            .focus({ focusVisible: false });
        }

        // Pause the indicator if the browser is active.
        if (document.visibilityState === "visible") {
          DownloadsCommon.getIndicatorData(window).attentionSuppressed |=
@@ -28,7 +44,12 @@ var ContentAreaDownloadsView = {
      },
      { once: true }
    );
    let view = new DownloadsPlacesView(box, true, suppressionFlag);
    let view = new DownloadsPlacesView(
      box,
      torWarningMessage,
      true,
      suppressionFlag
    );
    document.addEventListener("visibilitychange", aEvent => {
      let indicator = DownloadsCommon.getIndicatorData(window);
      if (document.visibilityState === "visible") {
+32 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
  <linkset>
    <html:link rel="localization" href="toolkit/global/textActions.ftl"/>
    <html:link rel="localization" href="browser/downloads.ftl" />
    <html:link rel="localization" href="toolkit/global/tor-browser.ftl" />
  </linkset>

  <script src="chrome://global/content/globalOverlay.js"/>
@@ -36,6 +37,37 @@
  </keyset>
#endif

  <html:message-bar
    id="aboutDownloadsTorWarning"
    class="downloads-tor-warning-message-bar"
    role="alert"
    aria-labelledby="aboutDownloadsTorWarningTitle"
    aria-describedby="aboutDownloadsTorWarningDescription"
  >
    <html:div class="downloads-tor-warning-grid">
      <html:p
        id="aboutDownloadsTorWarningTitle"
        class="downloads-tor-warning-title"
        data-l10n-id="downloads-tor-warning-title"
      ></html:p>
      <html:p
        id="aboutDownloadsTorWarningDescription"
        class="downloads-tor-warning-description"
        data-l10n-id="downloads-tor-warning-description"
      >
        <html:a
          href="https://tails.net/"
          target="_blank"
          data-l10n-name="tails-link"
        ></html:a>
      </html:p>
      <html:button
        class="downloads-tor-warning-dismiss-button"
        data-l10n-id="downloads-tor-warning-dismiss-button"
      ></html:button>
    </html:div>
  </html:message-bar>

  <richlistbox flex="1"
               seltype="multiple"
               id="downloadsListBox"
+57 −0
Original line number Diff line number Diff line
@@ -93,6 +93,63 @@
  font: caption;
  min-width: 37em;
  padding: 0.62em;
  /* If we don't set a width, #downloadsPanelTorWarningDescription will request
   * its max-content width. */
  width: 37em;
}

#downloadsPanel-mainView {
  /* Fix the layout to ensure the #downloadsWarningDescription is given enough
   * vertical space. For tor-browser#40701.
   * TODO: May no longer be necessary after esr 115 due to bugzilla bug 1816455.
   */
  display: flex;
  flex-direction: column;
}

.downloads-tor-warning-grid {
  display: grid;
  grid-template:
    "title button" min-content
    "description button" auto
    / 1fr max-content;
  gap: 8px;
  margin-block: 8px;
  /* Some extra space between the text and the icon. */
  margin-inline-start: 8px;
}

.downloads-tor-warning-grid .downloads-tor-warning-title {
  grid-area: title;
  margin: 0;
}

.downloads-tor-warning-grid .downloads-tor-warning-description {
  grid-area: description;
  margin: 0;
}

.downloads-tor-warning-grid .downloads-tor-warning-dismiss-button {
  grid-area: button;
  align-self: center;
}

.downloads-tor-warning-description,
.downloads-tor-warning-title {
  line-height: 1.4;
}

.downloads-tor-warning-title {
  font-weight: bold;
}

#downloadsPanelTorWarning :is(
  .downloads-tor-warning-description,
  .downloads-tor-warning-title
) {
  padding-inline: 8px;
  margin-block-start: 8px;
  margin-block-end: 0;
}

#downloadsHistory,
+99 −11
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@

"use strict";

const PREF_SHOW_DOWNLOAD_WARNING = "browser.download.showTorWarning";

var { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);
@@ -77,6 +79,13 @@ var DownloadsPanel = {
   */
  _state: 0,

  /**
   * State of tor's download warning. It should only be initialized once (text assigned,
   * button commands assigned). This tracks if that has been performed and prevents
   * repeats.
   */
  _torWarningInitialized: false,

  /** The panel is not linked to downloads data yet. */
  get kStateUninitialized() {
    return 0;
@@ -110,6 +119,31 @@ var DownloadsPanel = {
      );
    }

    const torWarningMessage = document.getElementById(
      "downloadsPanelTorWarning"
    );
    if (!this._torWarningPrefObserver) {
      // Observe changes to the tor warning pref.
      this._torWarningPrefObserver = () => {
        if (Services.prefs.getBoolPref(PREF_SHOW_DOWNLOAD_WARNING)) {
          torWarningMessage.hidden = false;
        } else {
          const hadFocus = torWarningMessage.contains(document.activeElement);
          torWarningMessage.hidden = true;
          // Re-assign focus that was lost.
          if (hadFocus) {
            this._focusPanel(true);
          }
        }
      };
      Services.prefs.addObserver(
        PREF_SHOW_DOWNLOAD_WARNING,
        this._torWarningPrefObserver
      );
      // Initialize
      this._torWarningPrefObserver();
    }

    if (this._state != this.kStateUninitialized) {
      DownloadsCommon.log("DownloadsPanel is already initialized.");
      return;
@@ -133,6 +167,33 @@ var DownloadsPanel = {
      DownloadsSummary
    );

    if (!this._torWarningInitialized) {
      // Intercept clicks on the tails link.
      // NOTE: We listen for clicks on the parent instead of the
      // <a data-l10n-name="tails-link"> element because the latter may be
      // swapped for a new instance by Fluent when refreshing the parent.
      torWarningMessage
        .querySelector(".downloads-tor-warning-description")
        .addEventListener("click", event => {
          const tailsLink = event.target.closest(
            ".downloads-tor-warning-tails-link"
          );
          if (!tailsLink) {
            return;
          }
          event.preventDefault();
          this.hidePanel();
          openWebLinkIn(tailsLink.href, "tab");
        });

      torWarningMessage
        .querySelector(".downloads-tor-warning-dismiss-button")
        .addEventListener("click", event => {
          Services.prefs.setBoolPref(PREF_SHOW_DOWNLOAD_WARNING, false);
        });
      this._torWarningInitialized = true;
    }

    DownloadsCommon.log(
      "DownloadsView attached - the panel for this window",
      "should now see download items come in."
@@ -172,6 +233,14 @@ var DownloadsPanel = {
      DownloadIntegration.downloadSpamProtection.unregister(window);
    }

    if (this._torWarningPrefObserver) {
      Services.prefs.removeObserver(
        PREF_SHOW_DOWNLOAD_WARNING,
        this._torWarningPrefObserver
      );
      delete this._torWarningPrefObserver;
    }

    this._state = this.kStateUninitialized;

    DownloadsSummary.active = false;
@@ -506,8 +575,11 @@ var DownloadsPanel = {
  /**
   * Move focus to the main element in the downloads panel, unless another
   * element in the panel is already focused.
   *
   * @param {bool} [forceFocus=false] - Whether to force move the focus.
   */
  _focusPanel() {
  _focusPanel(forceFocus = false) {
    if (!forceFocus) {
      // We may be invoked while the panel is still waiting to be shown.
      if (this._state != this.kStateShown) {
        return;
@@ -520,10 +592,26 @@ var DownloadsPanel = {
      ) {
        return;
      }
    }

    let focusOptions = {};
    if (this._preventFocusRing) {
      focusOptions.focusVisible = false;
    }

    // Focus the "Got it" button if it is visible.
    // This should ensure that the alert is read aloud by Orca when the
    // downloads panel is opened. See tor-browser#42642.
    const torWarningMessage = document.getElementById(
      "downloadsPanelTorWarning"
    );
    if (!torWarningMessage.hidden) {
      torWarningMessage
        .querySelector(".downloads-tor-warning-dismiss-button")
        .focus(focusOptions);
      return;
    }

    if (DownloadsView.richListBox.itemCount > 0) {
      if (DownloadsView.canChangeSelectedItem) {
        DownloadsView.richListBox.selectedIndex = 0;
Loading