Skip to content
Snippets Groups Projects
Verified Commit c0eefd9b 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 354797f4
No related branches found
No related tags found
1 merge request!1223Bug 43167: Rebased stable onto 115.16.0esr
Showing
with 359 additions and 16 deletions
......@@ -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",
......
......@@ -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") {
......
......@@ -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"
......
......@@ -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,
......
......@@ -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;
......
......@@ -124,6 +124,37 @@
disablekeynav="true">
<panelview id="downloadsPanel-mainView">
<vbox id="downloadsPanelTorWarning">
<vbox
role="alert"
aria-labelledby="downloadsPanelTorWarningTitle"
aria-describedby="downloadsPanelTorWarningDescription"
>
<html:p
id="downloadsPanelTorWarningTitle"
class="downloads-tor-warning-title"
data-l10n-id="downloads-tor-warning-title"
></html:p>
<html:p
id="downloadsPanelTorWarningDescription"
class="downloads-tor-warning-description"
data-l10n-id="downloads-tor-warning-description"
>
<html:a
href="https://tails.net/"
data-l10n-name="tails-link"
class="downloads-tor-warning-tails-link"
></html:a>
</html:p>
<html:div class="panel-footer">
<html:button
class="downloads-tor-warning-dismiss-button"
data-l10n-id="downloads-tor-warning-dismiss-button"
></html:button>
</html:div>
</vbox>
<toolbarseparator />
</vbox>
<vbox class="panel-view-body-unscrollable">
<richlistbox id="downloadsListBox"
data-l10n-id="downloads-panel-items"
......
......@@ -41,3 +41,7 @@ tree[is="places-tree"] > treechildren::-moz-tree-cell {
.places-tooltip-box {
display: block;
}
#placesDownloadsTorWarning:not(.downloads-visible) {
display: none;
}
......@@ -6,6 +6,7 @@
/* import-globals-from editBookmark.js */
/* import-globals-from /toolkit/content/contentAreaUtils.js */
/* import-globals-from /browser/components/downloads/content/allDownloadsView.js */
/* import-globals-from /browser/base/content/utilityOverlay.js */
/* Shared Places Import - change other consumers if you change this: */
var { XPCOMUtils } = ChromeUtils.importESModule(
......@@ -157,11 +158,15 @@ var PlacesOrganizer = {
"&sort=" +
Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
const torWarningMessage = document.getElementById(
"placesDownloadsTorWarning"
);
ContentArea.setContentViewForQueryString(
DOWNLOADS_QUERY,
() =>
new DownloadsPlacesView(
document.getElementById("downloadsListBox"),
torWarningMessage,
false
),
{
......@@ -171,6 +176,23 @@ var PlacesOrganizer = {
}
);
// Intercept clicks on the tor warning 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.
document
.querySelector(".downloads-tor-warning-description")
.addEventListener("click", event => {
const tailsLink = event.target.closest(
".downloads-tor-warning-tails-link"
);
if (!tailsLink) {
return;
}
event.preventDefault();
openWebLinkIn(tailsLink.href, "tab");
});
ContentArea.init();
this._places = document.getElementById("placesList");
......@@ -1418,9 +1440,20 @@ var ContentArea = {
oldView.associatedElement.hidden = true;
aNewView.associatedElement.hidden = false;
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();
}
}
......
......@@ -36,6 +36,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"/>
......@@ -334,6 +335,35 @@
</tree>
<splitter collapse="none" persist="state"></splitter>
<vbox id="contentView">
<html:message-bar
id="placesDownloadsTorWarning"
role="alert"
aria-labelledby="placesDownloadsTorWarningTitle"
aria-describedby="placesDownloadsTorWarningDescription"
>
<html:div class="downloads-tor-warning-grid">
<html:p
id="placesDownloadsTorWarningTitle"
class="downloads-tor-warning-title"
data-l10n-id="downloads-tor-warning-title"
></html:p>
<html:p
id="placesDownloadsTorWarningDescription"
class="downloads-tor-warning-description"
data-l10n-id="downloads-tor-warning-description"
>
<html:a
href="https://tails.net/"
class="downloads-tor-warning-tails-link"
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>
<vbox id="placesViewsBox" flex="1">
<tree id="placeContent"
class="plain placesTree"
......
......@@ -1269,6 +1269,7 @@ panelview .toolbarbutton-1 {
}
/* TODO: Check if we can/should merge with the above rule */
#downloadsPanelTorWarning toolbarseparator,
#securityLevel-panel toolbarseparator {
appearance: none;
min-height: 0;
......
......@@ -25,3 +25,7 @@
text-align: center;
color: var(--text-color-deemphasized);
}
#aboutDownloadsTorWarning {
margin-block-end: 8px;
}
......@@ -261,6 +261,7 @@
}
/*** Toolbarseparator ***/
#downloadsWarning toolbarseparator,
#downloadsFooterButtons > toolbarseparator {
margin-inline: 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment