This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-102.5.0esr-12.5-1 in repository tor-browser.
commit 1add8d34c1bb91f299d0419aab2cac4edbe142aa Author: Henry Wilkes henry@torproject.org AuthorDate: Mon Nov 21 16:35:34 2022 +0000
fixup! Bug 40925: Implemented the Security Level component
Make the security level panel and button more accessible:
+ We use semantic elements if possible and otherwise set aria attributes. In particular, we give the panel a dialog role. + We set the "Restore Defaults" button as default if it is shown since it is the recommended action. + We focus the default button when showing the popup. + We handle the case where the security level button is in the overflow menu by closing the overflow menu first. Before this key navigation in the sub-panel could disrupt the position in the parent panel.
We also simplify the code in general and use HTML for the main content of the panel. --- .../securitylevel/content/securityLevel.js | 293 +++++++++------------ .../content/securityLevelButton.inc.xhtml | 6 +- .../securitylevel/content/securityLevelPanel.css | 70 +++-- .../content/securityLevelPanel.inc.xhtml | 58 ++-- 4 files changed, 182 insertions(+), 245 deletions(-)
diff --git a/browser/components/securitylevel/content/securityLevel.js b/browser/components/securitylevel/content/securityLevel.js index 4df65fe5452b..e823ac993812 100644 --- a/browser/components/securitylevel/content/securityLevel.js +++ b/browser/components/securitylevel/content/securityLevel.js @@ -2,10 +2,6 @@
/* global AppConstants, Services, openPreferences, XPCOMUtils */
-XPCOMUtils.defineLazyModuleGetters(this, { - PanelMultiView: "resource:///modules/PanelMultiView.jsm", -}); - XPCOMUtils.defineLazyGetter(this, "SecurityLevelStrings", () => { let strings = { // Generic terms @@ -111,58 +107,92 @@ var SecurityLevelPrefs = {
var SecurityLevelButton = { _securityPrefsBranch: null, + /** + * Whether we have added popup listeners to the panel. + * + * @type {boolean} + */ + _panelPopupListenersSetup: false, + /** + * The toolbar button element. + * + * @type {Element} + */ + _button: null, + /** + * The button that the panel should point to. Either the toolbar button or the + * overflow button. + * + * @type {Element} + */ + _anchorButton: null,
_configUIFromPrefs() { - const securityLevelButton = this.button; - if (securityLevelButton != null) { - const level = SecurityLevelPrefs.securityLevel; - if (!level) { - return; - } - const customStr = SecurityLevelPrefs.securityCustom ? "_custom" : ""; - securityLevelButton.setAttribute("level", `${level}${customStr}`); - securityLevelButton.setAttribute( - "tooltiptext", - SecurityLevelStrings[`security_level_tooltip_${level}`] - ); + const level = SecurityLevelPrefs.securityLevel; + if (!level) { + return; } + const customStr = SecurityLevelPrefs.securityCustom ? "_custom" : ""; + this._button.setAttribute("level", `${level}${customStr}`); + this._button.setAttribute( + "tooltiptext", + SecurityLevelStrings[`security_level_tooltip_${level}`] + ); },
/** - * The node for this button. - * - * Note, the returned element may be part of the DOM or may live in the - * toolbox palette, where it may be added later to the DOM through - * customization. - * - * @type {MozToolbarbutton} + * Open the panel popup for the button. */ - get button() { - // We first search in the DOM for the security level button. If it does not - // exist it may be in the toolbox palette. We still want to return the - // button in the latter case to allow it to be initialized or adjusted in - // case it is added back through customization. - return ( - document.getElementById("security-level-button") || - window.gNavToolbox.palette.querySelector("#security-level-button") - ); - }, + openPopup() { + let anchorNode; + const overflowPanel = document.getElementById("widget-overflow"); + if (overflowPanel.contains(this._button)) { + // We are in the overflow panel. + // We first close the overflow panel, otherwise focus will not return to + // the nav-bar-overflow-button if the security level panel is closed with + // "Escape" (the navigation toolbar does not track focus when a panel is + // opened whilst another is already open). + // NOTE: In principle, using PanelMultiView would allow us to open panels + // from within another panel. However, when using panelmultiview for the + // security level panel, tab navigation was broken within the security + // level panel. PanelMultiView may be set up to work with a menu-like + // panel rather than our dialog-like panel. + overflowPanel.hidePopup(); + this._anchorButton = document.getElementById("nav-bar-overflow-button"); + anchorNode = this._anchorButton.icon; + } else { + this._anchorButton = this._button; + anchorNode = this._button.badgeStack; + }
- get anchor() { - let button = this.button; - let anchor = button?.icon; - if (!anchor) { - return null; + const panel = SecurityLevelPanel.panel; + if (!this._panelPopupListenersSetup) { + this._panelPopupListenersSetup = true; + // NOTE: We expect the _anchorButton to not change whilst the popup is + // open. + panel.addEventListener("popupshown", () => { + this._anchorButton.setAttribute("open", "true"); + }); + panel.addEventListener("popuphidden", () => { + this._anchorButton.removeAttribute("open"); + }); }
- anchor.setAttribute("consumeanchor", button.id); - return anchor; + panel.openPopup(anchorNode, "bottomcenter topright", 0, 0, false); },
init() { + // We first search in the DOM for the security level button. If it does not + // exist it may be in the toolbox palette. We still want to return the + // button in the latter case to allow it to be initialized or adjusted in + // case it is added back through customization. + this._button = + document.getElementById("security-level-button") || + window.gNavToolbox.palette.querySelector("#security-level-button"); // Set a label to be be used as the accessible name, and to be shown in the // overflow menu and during customization. - this.button?.setAttribute("label", SecurityLevelStrings.security_level); + this._button.setAttribute("label", SecurityLevelStrings.security_level); + this._button.addEventListener("command", () => this.openPopup()); // set the initial class based off of the current pref this._configUIFromPrefs();
@@ -190,32 +220,6 @@ var SecurityLevelButton = { break; } }, - - // for when the toolbar button needs to be activated and displays the Security Level panel - // - // In the toolbarbutton xul you'll notice we register this callback for both onkeypress and - // onmousedown. We do this to match the behavior of other panel spawning buttons such as Downloads, - // Library, and the Hamburger menus. Using oncommand alone would result in only getting fired - // after onclick, which is mousedown followed by mouseup. - onCommand(aEvent) { - // snippet borrowed from /browser/components/downloads/content/indicator.js DownloadsIndicatorView.onCommand(evt) - if ( - // On Mac, ctrl-click will send a context menu event from the widget, so - // we don't want to bring up the panel when ctrl key is pressed. - (aEvent.type == "mousedown" && - (aEvent.button != 0 || - (AppConstants.platform == "macosx" && aEvent.ctrlKey))) || - (aEvent.type == "keypress" && aEvent.key != " " && aEvent.key != "Enter") - ) { - return; - } - - // we need to set this attribute for the button to be shaded correctly to look like it is pressed - // while the security level panel is open - this.button.setAttribute("open", "true"); - SecurityLevelPanel.show(); - aEvent.stopPropagation(); - }, }; /* SecurityLevelButton */
/* @@ -226,66 +230,49 @@ var SecurityLevelButton = {
var SecurityLevelPanel = { _securityPrefsBranch: null, - _panel: null, - _anchor: null, _populated: false,
- _selectors: Object.freeze({ - panel: "panel#securityLevel-panel", - icon: "vbox#securityLevel-vbox>vbox", - labelLevel: "label#securityLevel-level", - labelCustom: "label#securityLevel-custom", - summary: "description#securityLevel-summary", - restoreDefaults: "button#securityLevel-restoreDefaults", - advancedSecuritySettings: "button#securityLevel-advancedSecuritySettings", - // Selectors used only for l10n - remove them when switching to Fluent - header: "#securityLevel-header", - learnMore: "#securityLevel-panel .learnMore", - }), - _populateXUL() { - let selectors = this._selectors; - this._elements = { - panel: document.querySelector(selectors.panel), - icon: document.querySelector(selectors.icon), - labelLevel: document.querySelector(selectors.labelLevel), - labelCustom: document.querySelector(selectors.labelCustom), - summaryDescription: document.querySelector(selectors.summary), - restoreDefaultsButton: document.querySelector(selectors.restoreDefaults), - advancedSecuritySettings: document.querySelector( - selectors.advancedSecuritySettings + panel: document.getElementById("securityLevel-panel"), + background: document.getElementById("securityLevel-background"), + levelName: document.getElementById("securityLevel-level"), + customName: document.getElementById("securityLevel-custom"), + summary: document.getElementById("securityLevel-summary"), + restoreDefaultsButton: document.getElementById( + "securityLevel-restoreDefaults" ), - header: document.querySelector(selectors.header), - learnMore: document.querySelector(selectors.learnMore), + settingsButton: document.getElementById("securityLevel-settings"), };
- this._elements.header.textContent = SecurityLevelStrings.security_level; - this._elements.labelCustom.setAttribute( - "value", - SecurityLevelStrings.security_level_custom - ); - this._elements.learnMore.setAttribute( - "value", - SecurityLevelStrings.security_level_learn_more - ); + document.getElementById("securityLevel-header").textContent = + SecurityLevelStrings.security_level; + this._elements.customName.textContent = + SecurityLevelStrings.security_level_custom; + const learnMoreEl = document.getElementById("securityLevel-learnMore"); + learnMoreEl.textContent = SecurityLevelStrings.security_level_learn_more; + learnMoreEl.addEventListener("click", event => { + window.openTrustedLinkIn(learnMoreEl.href, "tab"); + this.hide(); + event.preventDefault(); + }); this._elements.restoreDefaultsButton.textContent = SecurityLevelStrings.security_level_restore; - this._elements.advancedSecuritySettings.textContent = + this._elements.settingsButton.textContent = SecurityLevelStrings.security_level_change;
- this._elements.panel.addEventListener("onpopupshown", e => { - this.onPopupShown(e); - }); - this._elements.panel.addEventListener("onpopuphidden", e => { - this.onPopupHidden(e); - }); this._elements.restoreDefaultsButton.addEventListener("command", () => { this.restoreDefaults(); }); - this._elements.advancedSecuritySettings.addEventListener("command", () => { - this.openAdvancedSecuritySettings(); + this._elements.settingsButton.addEventListener("command", () => { + this.openSecuritySettings(); + }); + + this._elements.panel.addEventListener("popupshown", () => { + // Bring focus into the panel by focusing the default button. + this._elements.panel.querySelector('button[default="true"]').focus(); }); + this._populated = true; this._configUIFromPrefs(); }, @@ -301,26 +288,35 @@ var SecurityLevelPanel = { const custom = SecurityLevelPrefs.securityCustom;
// only visible when user is using custom settings - let labelCustomWarning = this._elements.labelCustom; - labelCustomWarning.hidden = !custom; - let buttonRestoreDefaults = this._elements.restoreDefaultsButton; - buttonRestoreDefaults.hidden = !custom; + this._elements.customName.hidden = !custom; + this._elements.restoreDefaultsButton.hidden = !custom; + if (custom) { + this._elements.settingsButton.removeAttribute("default"); + this._elements.restoreDefaultsButton.setAttribute("default", "true"); + } else { + this._elements.settingsButton.setAttribute("default", "true"); + this._elements.restoreDefaultsButton.removeAttribute("default"); + }
- const summary = this._elements.summaryDescription; // Descriptions change based on security level - if (level) { - this._elements.icon.setAttribute("level", level); - this._elements.labelLevel.setAttribute( - "value", - SecurityLevelStrings[`security_level_${level}`] - ); - summary.textContent = - SecurityLevelStrings[`security_level_${level}_summary`]; - } - // override the summary text with custom warning - if (custom) { - summary.textContent = SecurityLevelStrings.security_level_custom_summary; + this._elements.background.setAttribute("level", level); + this._elements.levelName.textContent = + SecurityLevelStrings[`security_level_${level}`]; + this._elements.summary.textContent = custom + ? SecurityLevelStrings.security_level_custom_summary + : SecurityLevelStrings[`security_level_${level}_summary`]; + }, + + /** + * The popup element. + * + * @type {MozPanel} + */ + get panel() { + if (!this._populated) { + this._populateXUL(); } + return this._elements.panel; },
init() { @@ -335,37 +331,18 @@ var SecurityLevelPanel = { this._securityPrefsBranch = null; },
- show() { - // we have to defer this until after the browser has finished init'ing - // before we can populate the panel - if (!this._populated) { - this._populateXUL(); - } - - this._elements.panel.hidden = false; - PanelMultiView.openPopup( - this._elements.panel, - SecurityLevelButton.anchor, - "bottomcenter topright", - 0, - 0, - false, - null - ).catch(Cu.reportError); - }, - hide() { - PanelMultiView.hidePopup(this._elements.panel); + this._elements.panel.hidePopup(); },
restoreDefaults() { SecurityLevelPrefs.securityCustom = false; - // hide and reshow so that layout re-renders properly - this.hide(); - this.show(this._anchor); + // Move focus to the settings button since restore defaults button will + // become hidden. + this._elements.settingsButton.focus(); },
- openAdvancedSecuritySettings() { + openSecuritySettings() { openPreferences("privacy-securitylevel"); this.hide(); }, @@ -380,16 +357,6 @@ var SecurityLevelPanel = { break; } }, - - // callback when the panel is displayed - onPopupShown(event) { - SecurityLevelButton.button.setAttribute("open", "true"); - }, - - // callback when the panel is hidden - onPopupHidden(event) { - SecurityLevelButton.button.removeAttribute("open"); - }, }; /* SecurityLevelPanel */
/* diff --git a/browser/components/securitylevel/content/securityLevelButton.inc.xhtml b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml index 96ee1ec0ca49..1737d27914c3 100644 --- a/browser/components/securitylevel/content/securityLevelButton.inc.xhtml +++ b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml @@ -1,7 +1,5 @@ -<toolbarbutton id="security-level-button" class="toolbarbutton-1 chromeclass-toolbar-additional" +<toolbarbutton id="security-level-button" + class="toolbarbutton-1 chromeclass-toolbar-additional" badged="true" removable="true" - onmousedown="SecurityLevelButton.onCommand(event);" - onkeypress="SecurityLevelButton.onCommand(event);" - closemenu="none" cui-areatype="toolbar"/> diff --git a/browser/components/securitylevel/content/securityLevelPanel.css b/browser/components/securitylevel/content/securityLevelPanel.css index c50acf0ae76c..5a13852be015 100644 --- a/browser/components/securitylevel/content/securityLevelPanel.css +++ b/browser/components/securitylevel/content/securityLevelPanel.css @@ -1,71 +1,61 @@ /* Security Level CSS */
-panelview#securityLevel-panelview { - width: 25em; -} - -vbox#securityLevel-vbox > vbox { +#securityLevel-background { + /* xul:vbox with display: block will be correctly measured in size by the + * parent xul:panel, with line-wrapping taken into account, and allocated the + * required space. */ + display: block; + min-height: 10em; + padding-inline: 16px; background-repeat: no-repeat; - /* icon center-line should be in-line with right margin */ - /* -margin + panelWidth - imageWidth/2 */ - background-position: calc(-16px + 25em - 4.5em) 0.4em; + background-position-y: top 0.4em; + /* Icon center should be in-line with end padding. + * We set the right-to-left position here, and the left-to-right position + * below. */ + --background-inline-offset: calc(16px - 4.5em); + background-position-x: left var(--background-inline-offset); background-size: 9em 9em; -moz-context-properties: fill, fill-opacity; fill-opacity: 1; fill: var(--button-bgcolor); - min-height: 10em; }
-vbox#securityLevel-vbox > vbox[level="standard"] { +/* NOTE: Use ":dir" instead of ":-moz-locale-dir" when panel switches to HTML. */ +#securityLevel-background:-moz-locale-dir(ltr) { + background-position-x: right var(--background-inline-offset); +} + +#securityLevel-background[level="standard"] { background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard"); } -vbox#securityLevel-vbox > vbox[level="safer"] { + +#securityLevel-background[level="safer"] { background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer"); } -vbox#securityLevel-vbox > vbox[level="safest"] { + +#securityLevel-background[level="safest"] { background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest"); }
-vbox#securityLevel-vbox > toolbarseparator { +/* Override margin in panelUI-shared.css */ +#securityLevel-panel toolbarseparator#securityLevel-separator { margin-inline: 16px; }
-vbox#securityLevel-vbox > vbox { - margin-inline: 0; - padding-inline: 16px; -} - -vbox#securityLevel-vbox > vbox * { - margin-inline: 0; -} - -label#securityLevel-level { +#securityLevel-level { font-size: 1.25em; font-weight: 600; - padding-top: 0.15em; }
-label#securityLevel-custom { +#securityLevel-custom { border-radius: 4px; background-color: var(--yellow-50); color: black; - font-size: 1em; - height: 1.6em; - line-height: 1.0em; padding: 0.4em 0.5em; - margin-inline-start: 1em !important; + margin-inline-start: 1em; }
-description#securityLevel-summary { - margin-top: 1em; +#securityLevel-summary { padding-inline-end: 5em; -} - -vbox#securityLevel-vbox > hbox.panel-footer { - display: flex; -} - - -button#securityLevel-advancedSecuritySettings { - margin-block: 0; + max-width: 20em; } diff --git a/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml index c485f819aba9..d330042f7311 100644 --- a/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml +++ b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml @@ -1,44 +1,26 @@ <panel id="securityLevel-panel" - role="group" + role="dialog" + aria-labelledby="securityLevel-header" + aria-describedby="securityLevel-subheading securityLevel-summary" type="arrow" orient="vertical" level="top" - hidden="true" class="panel-no-padding"> - <panelmultiview mainViewId="securityLevel-panelview"> - <panelview id="securityLevel-panelview" descriptionheightworkaround="true"> - <vbox id="securityLevel-vbox"> - <box class="panel-header"> - <html:h1 id="securityLevel-header"/> - </box> - <toolbarseparator></toolbarseparator> - <vbox> - <hbox> - <label id="securityLevel-level"/> - <vbox> - <spacer flex="1"/> - <label id="securityLevel-custom"/> - <spacer flex="1"/> - </vbox> - <spacer flex="1"/> - </hbox> - <description id="securityLevel-summary"/> - <hbox> - <label - class="learnMore text-link" - href="about:manual#security-settings" - useoriginprincipal="true" - onclick="SecurityLevelPanel.hide();" - is="text-link"/> - <spacer/> - </hbox> - </vbox> - <hbox class="panel-footer"> - <button id="securityLevel-restoreDefaults"/> - <button id="securityLevel-advancedSecuritySettings" - default="true"/> - </hbox> - </vbox> - </panelview> - </panelmultiview> + <box class="panel-header"> + <html:h1 id="securityLevel-header"></html:h1> + </box> + <toolbarseparator id="securityLevel-separator"></toolbarseparator> + <vbox id="securityLevel-background" class="panel-subview-body"> + <html:p id="securityLevel-subheading"> + <html:span id="securityLevel-level"></html:span> + <html:span id="securityLevel-custom"></html:span> + </html:p> + <html:p id="securityLevel-summary"></html:p> + <html:a id="securityLevel-learnMore" href="about:manual#security-settings"> + </html:a> + </vbox> + <hbox class="panel-footer"> + <button id="securityLevel-settings"/> + <button id="securityLevel-restoreDefaults"/> + </hbox> </panel>