Loading browser/components/downloads/DownloadsTorWarning.sys.mjs 0 → 100644 +152 −0 Original line number Diff line number Diff line /* 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 ); } } browser/components/downloads/content/contentAreaDownloadsView.js +37 −3 Original line number Diff line number Diff line Loading @@ -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 |= Loading browser/components/downloads/content/contentAreaDownloadsView.xhtml +3 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,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"/> Loading @@ -49,6 +50,8 @@ </keyset> #endif <html:moz-message-bar id="aboutDownloadsTorWarning"></html:moz-message-bar> <richlistbox flex="1" seltype="multiple" id="downloadsListBox" Loading browser/components/downloads/content/downloads.css +11 −0 Original line number Diff line number Diff line Loading @@ -95,6 +95,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; Loading browser/components/downloads/content/downloads.js +50 −11 Original line number Diff line number Diff line Loading @@ -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( Loading Loading @@ -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. Loading @@ -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; Loading Loading @@ -161,6 +184,8 @@ var DownloadsPanel = { DownloadIntegration.downloadSpamProtection.unregister(window); } this._torWarning?.deactivate(); this._initialized = false; DownloadsSummary.active = false; Loading Loading @@ -570,8 +595,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; Loading @@ -584,10 +612,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; Loading Loading
browser/components/downloads/DownloadsTorWarning.sys.mjs 0 → 100644 +152 −0 Original line number Diff line number Diff line /* 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 ); } }
browser/components/downloads/content/contentAreaDownloadsView.js +37 −3 Original line number Diff line number Diff line Loading @@ -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 |= Loading
browser/components/downloads/content/contentAreaDownloadsView.xhtml +3 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,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"/> Loading @@ -49,6 +50,8 @@ </keyset> #endif <html:moz-message-bar id="aboutDownloadsTorWarning"></html:moz-message-bar> <richlistbox flex="1" seltype="multiple" id="downloadsListBox" Loading
browser/components/downloads/content/downloads.css +11 −0 Original line number Diff line number Diff line Loading @@ -95,6 +95,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; Loading
browser/components/downloads/content/downloads.js +50 −11 Original line number Diff line number Diff line Loading @@ -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( Loading Loading @@ -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. Loading @@ -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; Loading Loading @@ -161,6 +184,8 @@ var DownloadsPanel = { DownloadIntegration.downloadSpamProtection.unregister(window); } this._torWarning?.deactivate(); this._initialized = false; DownloadsSummary.active = false; Loading Loading @@ -570,8 +595,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; Loading @@ -584,10 +612,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; Loading