Skip to content
Snippets Groups Projects
Verified Commit 8c46471b 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 8bb74fe0
Branches
Tags
1 merge request!888Bug 42365: Tor Browser 115.7.0esr stable rebase
Showing
with 415 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") {
......@@ -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);
},
};
......
......
......@@ -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"
......
......
......@@ -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,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);
......
......
......@@ -124,6 +124,24 @@
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">
</html:p>
<html:p id="downloadsPanelTorWarningDescription"
class="downloads-tor-warning-description">
</html:p>
<html:div class="panel-footer">
<html:button class="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,33 @@ var PlacesOrganizer = {
}
);
// Initialize tor warning text content.
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();
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);
torWarningMessage.querySelector(
".downloads-tor-warning-dismiss-button"
).textContent = this._getTorString("torbutton.download.warning.dismiss");
ContentArea.init();
this._places = document.getElementById("placesList");
......@@ -242,6 +274,30 @@ var PlacesOrganizer = {
ContentArea.focus();
},
/**
* Get a string from the properties bundle.
*
* @param {string} name - The string name.
*
* @returns {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);
},
QueryInterface: ChromeUtils.generateQI([]),
handleEvent: function PO_handleEvent(aEvent) {
......@@ -1418,9 +1474,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();
}
}
......
......
......@@ -338,6 +338,21 @@
</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">
</html:p>
<html:p id="placesDownloadsTorWarningDescription"
class="downloads-tor-warning-description">
</html:p>
<html:button class="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