Commit 3b5c8885 authored by Dan Ballard's avatar Dan Ballard Committed by Pier Angelo Vendrame
Browse files

Bug 40701: Add security warning when downloading a file

Shown in the downloads panel, about:downloads and places.xhtml.
parent cf6e0f82
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",
+72 −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") {
@@ -41,6 +62,53 @@ var ContentAreaDownloadsView = {
    if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
      view.place = "place:transition=7&sort=4";
    }

    torWarningMessage.querySelector(
      ".downloads-tor-warning-title"
    ).textContent = this._getTorString("torbutton.download.warning.title");

    const tailsLink = document.createElement("a");
    tailsLink.href = "https://tails.net/";
    tailsLink.target = "_blank";
    tailsLink.textContent = this._getTorString(
      "torbutton.download.warning.tails_brand_name"
    );

    const [beforeLink, afterLink] = this._getTorString(
      "torbutton.download.warning.description"
    ).split("%S");

    torWarningMessage
      .querySelector(".downloads-tor-warning-description")
      .append(beforeLink, tailsLink, afterLink);

    torWarningMessage.querySelector(
      ".downloads-tor-warning-dismiss-button"
    ).textContent = this._getTorString("torbutton.download.warning.dismiss");
  },

  /**
   * Get a string from the properties bundle.
   *
   * @param {string} name - The string name.
   *
   * @return {string} The string.
   */
  _getTorString(name) {
    if (!this._stringBundle) {
      this._stringBundle = Services.strings.createBundle(
        "chrome://torbutton/locale/torbutton.properties"
      );
    }
    try {
      return this._stringBundle.GetStringFromName(name);
    } catch {}
    if (!this._fallbackStringBundle) {
      this._fallbackStringBundle = Services.strings.createBundle(
        "resource://torbutton/locale/en-US/torbutton.properties"
      );
    }
    return this._fallbackStringBundle.GetStringFromName(name);
  },
};

+17 −0
Original line number Diff line number Diff line
@@ -36,6 +36,23 @@
  </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">
      </html:p>
      <html:p id="aboutDownloadsTorWarningDescription"
              class="downloads-tor-warning-description">
      </html:p>
      <html:button class="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,
+117 −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,30 @@ 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 {
          // Re-assign focus if it is about to be lost.
          if (torWarningMessage.contains(document.activeElement)) {
            this._focusPanel(true);
          }
          torWarningMessage.hidden = 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 +166,42 @@ var DownloadsPanel = {
      DownloadsSummary
    );

    if (!this._torWarningInitialized) {
      torWarningMessage.querySelector(
        ".downloads-tor-warning-title"
      ).textContent = this._getTorString("torbutton.download.warning.title");

      const tailsLink = document.createElement("a");
      tailsLink.href = "https://tails.net";
      tailsLink.textContent = this._getTorString(
        "torbutton.download.warning.tails_brand_name"
      );
      tailsLink.addEventListener("click", event => {
        event.preventDefault();
        this.hidePanel();
        openWebLinkIn(tailsLink.href, "tab");
      });

      const [beforeLink, afterLink] = this._getTorString(
        "torbutton.download.warning.description"
      ).split("%S");

      torWarningMessage
        .querySelector(".downloads-tor-warning-description")
        .append(beforeLink, tailsLink, afterLink);

      let dismissButton = torWarningMessage.querySelector(
        ".downloads-tor-warning-dismiss-button"
      );
      dismissButton.textContent = this._getTorString(
        "torbutton.download.warning.dismiss"
      );
      dismissButton.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 +241,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 +583,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,6 +600,8 @@ var DownloadsPanel = {
      ) {
        return;
      }
    }

    let focusOptions = {};
    if (this._preventFocusRing) {
      focusOptions.focusVisible = false;
@@ -643,6 +725,30 @@ var DownloadsPanel = {
      }
    }, 0);
  },

  /**
   * Get a string from the properties bundle.
   *
   * @param {string} name - The string name.
   *
   * @return {string} The string.
   */
  _getTorString(name) {
    if (!this._stringBundle) {
      this._stringBundle = Services.strings.createBundle(
        "chrome://torbutton/locale/torbutton.properties"
      );
    }
    try {
      return this._stringBundle.GetStringFromName(name);
    } catch {}
    if (!this._fallbackStringBundle) {
      this._fallbackStringBundle = Services.strings.createBundle(
        "resource://torbutton/locale/en-US/torbutton.properties"
      );
    }
    return this._fallbackStringBundle.GetStringFromName(name);
  },
};

XPCOMUtils.defineConstant(this, "DownloadsPanel", DownloadsPanel);
Loading