Skip to content
Snippets Groups Projects
Commit ab4de11d authored by Dan Ballard's avatar Dan Ballard Committed by Pier Angelo Vendrame
Browse files

TB 40701: Add security warning when downloading a file

Shown in the downloads panel, about:downloads and places.xhtml.
parent 56174fb2
Branches
Tags
1 merge request!1500TB 43415: Rebased onto 134.0a1
Showing
with 312 additions and 15 deletions
/* import-globals-from /browser/base/content/utilityOverlay.js */
const PREF_SHOW_DOWNLOAD_WARNING = "browser.download.showTorWarning";
/**
* Manages an instance of a tor warning.
*/
export class DownloadsTorWarning {
/**
* Observer for showing or hiding the warning.
*
* @type {function}
*/
#torWarningPrefObserver;
/**
* Whether the warning is active.
*
* @type {boolean}
*/
#active = false;
/**
* The moz-message-bar element that should show the warning.
*
* @type {MozMessageBar}
*/
#warningElement;
/**
* The dismiss button for the warning.
*
* @type {HTMLButton}
*/
#dismissButton;
/**
* Attach to an instance of the tor warning.
*
* @param {MozMessageBar} warningElement - The warning element to initialize
* and attach to.
* @param {boolean} isChrome - Whether the element belongs to the chrome.
* Otherwise it belongs to content.
* @param {function} moveFocus - Callback to move the focus out of the warning
* when it is hidden.
* @param {function} [onLinkClick] - Callback that is called when a link is
* about to open.
*/
constructor(warningElement, isChrome, moveFocus, onLinkClick) {
const doc = warningElement.ownerDocument;
this.#warningElement = warningElement;
warningElement.setAttribute(
"data-l10n-id",
"downloads-tor-warning-message-bar"
);
warningElement.setAttribute("data-l10n-attrs", "heading, message");
// Observe changes to the tor warning pref.
this.#torWarningPrefObserver = () => {
if (Services.prefs.getBoolPref(PREF_SHOW_DOWNLOAD_WARNING)) {
warningElement.hidden = false;
} else {
const hadFocus = warningElement.contains(doc.activeElement);
warningElement.hidden = true;
if (hadFocus) {
moveFocus();
}
}
};
const tailsLink = doc.createElement("a");
tailsLink.setAttribute("slot", "support-link");
tailsLink.href = "https://tails.net/";
tailsLink.target = "_blank";
tailsLink.setAttribute("data-l10n-id", "downloads-tor-warning-tails-link");
if (isChrome) {
// Intercept clicks on the tails link.
tailsLink.addEventListener("click", event => {
event.preventDefault();
onLinkClick?.();
doc.defaultView.openWebLinkIn(tailsLink.href, "tab");
});
}
const dismissButton = doc.createElement("button");
dismissButton.setAttribute("slot", "actions");
dismissButton.setAttribute(
"data-l10n-id",
"downloads-tor-warning-dismiss-button"
);
if (isChrome) {
dismissButton.classList.add("footer-button");
}
dismissButton.addEventListener("click", () => {
Services.prefs.setBoolPref(PREF_SHOW_DOWNLOAD_WARNING, false);
});
warningElement.append(tailsLink);
warningElement.append(dismissButton);
this.#dismissButton = dismissButton;
}
/**
* Whether the warning is hidden by the preference.
*
* @type {boolean}
*/
get hidden() {
return this.#warningElement.hidden;
}
/**
* The dismiss button for the warning.
*
* @type {HTMLButton}
*/
get dismissButton() {
return this.#dismissButton;
}
/**
* Activate the instance.
*/
activate() {
if (this.#active) {
return;
}
this.#active = true;
Services.prefs.addObserver(
PREF_SHOW_DOWNLOAD_WARNING,
this.#torWarningPrefObserver
);
// Initialize.
this.#torWarningPrefObserver();
}
/**
* Deactivate the instance.
*/
deactivate() {
if (!this.#active) {
return;
}
this.#active = false;
Services.prefs.removeObserver(
PREF_SHOW_DOWNLOAD_WARNING,
this.#torWarningPrefObserver
);
}
}
......@@ -8,18 +8,52 @@ const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
"resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
);
const { DownloadsTorWarning } = ChromeUtils.importESModule(
"resource:///modules/DownloadsTorWarning.sys.mjs"
);
var ContentAreaDownloadsView = {
init() {
let box = document.getElementById("downloadsListBox");
const torWarning = new DownloadsTorWarning(
document.getElementById("aboutDownloadsTorWarning"),
false,
() => {
// 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.
box.focus({ preventFocusRing: true });
}
);
torWarning.activate();
window.addEventListener("unload", () => {
torWarning.deactivate();
});
let suppressionFlag = DownloadsCommon.SUPPRESS_CONTENT_AREA_DOWNLOADS_OPEN;
box.addEventListener(
"InitialDownloadsLoaded",
() => {
// 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 (torWarning.hidden) {
document
.getElementById("downloadsListBox")
.focus({ focusVisible: false });
}
// Pause the indicator if the browser is active.
if (document.visibilityState === "visible") {
DownloadsCommon.getIndicatorData(window).attentionSuppressed |=
......
......
......@@ -33,6 +33,7 @@
<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"/>
......@@ -48,6 +49,8 @@
</keyset>
#endif
<html:moz-message-bar id="aboutDownloadsTorWarning"></html:moz-message-bar>
<richlistbox flex="1"
seltype="multiple"
id="downloadsListBox"
......
......
......@@ -94,6 +94,17 @@
padding: 0.62em;
}
#downloadsPanelTorWarning {
margin-block-end: var(--arrowpanel-menuitem-padding-block);
}
#downloadsPanelTorWarningWrapper {
/* The wrapper element has its `width` attribute set by mozilla localisers.
* We want to ensure the element occupies the available width when the
* localiser width is smaller. See tor-browser#43312. */
min-width: 100%;
}
#downloadsHistory,
#downloadsFooterButtons {
margin: 0;
......
......
......@@ -40,6 +40,7 @@ ChromeUtils.defineESModuleGetters(this, {
FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
DownloadsTorWarning: "resource:///modules/DownloadsTorWarning.sys.mjs",
});
const { Integration } = ChromeUtils.importESModule(
......@@ -73,6 +74,13 @@ var DownloadsPanel = {
/** The panel will be shown as soon as data is available. */
_waitingDataForOpen: false,
/**
* Tracks whether to show the tor warning or not.
*
* @type {?DownloadsTorWarning}
*/
_torWarning: null,
/**
* Starts loading the download data in background, without opening the panel.
* Use showPanel instead to load the data and open the panel at the same time.
......@@ -89,6 +97,21 @@ var DownloadsPanel = {
);
}
if (!this._torWarning) {
this._torWarning = new DownloadsTorWarning(
document.getElementById("downloadsPanelTorWarning"),
true,
() => {
// Re-assign focus that was lost.
this._focusPanel(true);
},
() => {
this.hidePanel();
}
);
}
this._torWarning.activate();
if (this._initialized) {
DownloadsCommon.log("DownloadsPanel is already initialized.");
return;
......@@ -157,6 +180,8 @@ var DownloadsPanel = {
DownloadIntegration.downloadSpamProtection.unregister(window);
}
this._torWarning?.deactivate();
this._initialized = false;
DownloadsSummary.active = false;
......@@ -551,8 +576,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.panel.state != "open") {
return;
......@@ -565,10 +593,21 @@ 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.
if (!this._torWarning?.hidden) {
this._torWarning.dismissButton.focus(focusOptions);
return;
}
if (DownloadsView.richListBox.itemCount > 0) {
if (DownloadsView.canChangeSelectedItem) {
DownloadsView.richListBox.selectedIndex = 0;
......
......
......@@ -102,6 +102,25 @@
disablekeynav="true">
<panelview id="downloadsPanel-mainView">
<!-- We add a wrapper around the #downloadsPanelTorWarning and give it the
- same Fluent ID as #downloadsListBox. This Fluent message allows
- Firefox localisers to set the width of the #downloadsListBox using
- the style attribute. We want the same width set for our downloads
- warning. Otherwise the warning will occupy its max-content width.
- NOTE: We require a wrapper element since #downloadsPanelTorWarning
- needs its own Fluent attributes.
- NOTE: This only works if #downloadsPanelTorWarningWrapper and
- #downloadsListBox share the same padding relative to their common
- ancestor.
- See tor-browser#43312. -->
<html:div
id="downloadsPanelTorWarningWrapper"
data-l10n-id="downloads-panel-items"
data-l10n-attrs="style"
>
<html:moz-message-bar id="downloadsPanelTorWarning">
</html:moz-message-bar>
</html:div>
<vbox class="panel-view-body-unscrollable">
<richlistbox id="downloadsListBox"
data-l10n-id="downloads-panel-items"
......
......
......@@ -15,6 +15,7 @@ EXTRA_JS_MODULES += [
"DownloadsCommon.sys.mjs",
"DownloadSpamProtection.sys.mjs",
"DownloadsTaskbar.sys.mjs",
"DownloadsTorWarning.sys.mjs",
"DownloadsViewableInternally.sys.mjs",
"DownloadsViewUI.sys.mjs",
]
......
......
......@@ -47,3 +47,7 @@ tree[is="places-tree"] > treechildren::-moz-tree-cell {
.places-tooltip-box {
display: block;
}
#placesDownloadsTorWarning:not(.downloads-visible) {
display: none;
}
......@@ -17,6 +17,7 @@ ChromeUtils.defineESModuleGetters(this, {
PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
DownloadsTorWarning: "resource:///modules/DownloadsTorWarning.sys.mjs",
});
XPCOMUtils.defineLazyScriptGetter(
this,
......@@ -157,6 +158,20 @@ var PlacesOrganizer = {
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
const torWarning = new DownloadsTorWarning(
document.getElementById("placesDownloadsTorWarning"),
true,
() => {
document
.getElementById("downloadsListBox")
.focus({ preventFocusRing: true });
}
);
torWarning.activate();
window.addEventListener("unload", () => {
torWarning.deactivate();
});
ContentArea.setContentViewForQueryString(
DOWNLOADS_QUERY,
() =>
......@@ -1402,9 +1417,21 @@ var ContentArea = {
oldView.associatedElement.hidden = true;
aNewView.associatedElement.hidden = false;
// Hide the Tor warning when not in the downloads view.
const isDownloads = aNewView.associatedElement.id === "downloadsListBox";
const torWarningMessage = document.getElementById(
"placesDownloadsTorWarning"
);
const torWarningLoosingFocus =
torWarningMessage.contains(document.activeElement) && !isDownloads;
torWarningMessage.classList.toggle("downloads-visible", isDownloads);
// If the content area inactivated view was focused, move focus
// to the new view.
if (document.activeElement == oldView.associatedElement) {
if (
document.activeElement == oldView.associatedElement ||
torWarningLoosingFocus
) {
aNewView.associatedElement.focus();
}
}
......
......
......@@ -64,6 +64,7 @@
<html:link rel="localization" href="browser/places.ftl"/>
<html:link rel="localization" href="browser/downloads.ftl"/>
<html:link rel="localization" href="browser/editBookmarkOverlay.ftl"/>
<html:link rel="localization" href="toolkit/global/tor-browser.ftl"/>
</linkset>
<script src="chrome://browser/content/places/places.js"/>
......@@ -362,6 +363,8 @@
</tree>
<splitter collapse="none" persist="state"></splitter>
<vbox id="contentView">
<html:moz-message-bar id="placesDownloadsTorWarning">
</html:moz-message-bar>
<vbox id="placesViewsBox" flex="1">
<tree id="placeContent"
class="placesTree"
......
......
......@@ -25,3 +25,7 @@
text-align: center;
color: var(--text-color-deemphasized);
}
#aboutDownloadsTorWarning {
margin-block-end: 8px;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment