tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 8bbc3ddd7487ae988ca18e9279bbb877cf2fba1e
parent 1fed30736a26c73b10d63bb7b88ee55d0205350e
Author: DJ <dj@walker.dev>
Date:   Tue,  7 Oct 2025 19:48:51 +0000

Bug 1971240 - improve screen reader support for tab group previews. r=sthompson,fluent-reviewers,tabbrowser-reviewers,bolsson

Differential Revision: https://phabricator.services.mozilla.com/D266366

Diffstat:
Mbrowser/base/content/main-popupset.inc.xhtml | 2++
Mbrowser/components/tabbrowser/content/tab-hover-preview.mjs | 1+
Mbrowser/components/tabbrowser/content/tabgroup.js | 28++++++++++++++++++----------
Mbrowser/components/tabbrowser/test/browser/tabs/browser_tab_groups_a11y.js | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mbrowser/locales/en-US/browser/tabbrowser.ftl | 10++++++++++
5 files changed, 113 insertions(+), 22 deletions(-)

diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml @@ -537,6 +537,8 @@ </panel> <panel id="tabgroup-preview-panel" + role="menu" + data-l10n-id="tab-group-preview-name" type="arrow" class="toolbar-menupopup animatable-menupopup" noautofocus="true" diff --git a/browser/components/tabbrowser/content/tab-hover-preview.mjs b/browser/components/tabbrowser/content/tab-hover-preview.mjs @@ -569,6 +569,7 @@ class TabGroupPanel extends Panel { const fragment = this.win.document.createDocumentFragment(); for (let tab of this.#group.tabs) { let tabbutton = this.win.document.createXULElement("toolbarbutton"); + tabbutton.setAttribute("role", "button"); tabbutton.setAttribute("label", tab.label); tabbutton.setAttribute( "image", diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js @@ -121,7 +121,6 @@ }); this.#updateLabelAriaAttributes(); - this.#updateCollapsedAriaAttributes(); this.overflowContainer = this.querySelector( ".tab-group-overflow-count-container" @@ -332,7 +331,7 @@ } } this.toggleAttribute("collapsed", val); - this.#updateCollapsedAriaAttributes(); + this.#updateLabelAriaAttributes(); this.#updateTooltip(); for (const tab of this.tabs) { this.#updateTabAriaHidden(tab); @@ -367,20 +366,28 @@ async #updateLabelAriaAttributes() { let tabGroupName = this.#label || this.defaultGroupName; + this.#labelElement?.setAttribute("aria-label", tabGroupName); + this.#labelElement?.setAttribute("aria-level", 1); + + let tabGroupDescriptionL10nID; + if (this.collapsed) { + this.#labelElement?.setAttribute("aria-haspopup", "menu"); + this.#labelElement?.setAttribute("aria-expanded", "false"); + tabGroupDescriptionL10nID = this.hasAttribute("previewpanelactive") + ? "tab-group-preview-open-description" + : "tab-group-preview-closed-description"; + } else { + this.#labelElement?.removeAttribute("aria-haspopup"); + this.#labelElement?.setAttribute("aria-expanded", "true"); + tabGroupDescriptionL10nID = "tab-group-description"; + } let tabGroupDescription = await gBrowser.tabLocalization.formatValue( - "tab-group-description", + tabGroupDescriptionL10nID, { tabGroupName, } ); - this.#labelElement?.setAttribute("aria-label", tabGroupName); this.#labelElement?.setAttribute("aria-description", tabGroupDescription); - this.#labelElement?.setAttribute("aria-level", 1); - } - - #updateCollapsedAriaAttributes() { - const ariaExpanded = this.collapsed ? "false" : "true"; - this.#labelElement?.setAttribute("aria-expanded", ariaExpanded); } async #updateTooltip() { @@ -486,6 +493,7 @@ */ set hoverPreviewPanelActive(val) { this.toggleAttribute("previewpanelactive", val); + this.#updateLabelAriaAttributes(); } /** diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_tab_groups_a11y.js b/browser/components/tabbrowser/test/browser/tabs/browser_tab_groups_a11y.js @@ -46,23 +46,25 @@ add_task(async function test_TabGroupA11y() { ); tabGroup.label = "test"; - tabGroup.collapsed = true; await BrowserTestUtils.waitForCondition( () => tabGroup.labelElement.getAttribute("aria-label") == "test", "Tab group label was updated" ); + await flushL10n(); + Assert.equal( tabGroup.labelElement.getAttribute("aria-label"), "test", "tab group label aria-label should equal the name of the tab group" ); - Assert.equal( tabGroup.labelElement.getAttribute("aria-description"), "test — Tab Group", "tab group label aria-description should provide the name of the tab group plus more context" ); + await TabGroupTestUtils.toggleCollapsed(tabGroup, true); + Assert.equal( tabGroup.labelElement.getAttribute("aria-expanded"), "false", @@ -165,12 +167,7 @@ add_task(async function test_collapsedTabGroupTooltips() { info( "Collapse the group to confirm tooltip state when tab group hover preview is enabled" ); - let collapseFinished = BrowserTestUtils.waitForEvent( - group, - "TabGroupCollapse" - ); - group.collapsed = true; - await collapseFinished; + await TabGroupTestUtils.toggleCollapsed(group, true); await flushL10n(); Assert.equal( @@ -192,10 +189,8 @@ add_task(async function test_collapsedTabGroupTooltips() { info( "Un-collapse and re-collapse group to ensure group picks up new pref setting" ); - group.collapsed = false; - collapseFinished = BrowserTestUtils.waitForEvent(group, "TabGroupCollapse"); - group.collapsed = true; - await collapseFinished; + await TabGroupTestUtils.toggleCollapsed(group, false); + await TabGroupTestUtils.toggleCollapsed(group, true); await flushL10n(); Assert.equal( @@ -227,3 +222,78 @@ add_task(async function test_collapsedTabGroupTooltips() { await removeTabGroup(group); }); + +add_task(async function test_tabGroupPreviewA11y() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.tabs.groups.hoverPreview.enabled", true]], + }); + + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + const group = gBrowser.addTabGroup([tab]); + + Assert.ok( + !group.labelElement.getAttribute("aria-haspopup"), + "Group label does not have aria-haspopup attribute" + ); + + await TabGroupTestUtils.toggleCollapsed(group, true); + + Assert.equal( + group.labelElement.getAttribute("aria-haspopup"), + "menu", + 'Group label has aria-haspopup="menu"' + ); + + info("opening collapsed group preview"); + let groupPreview = document.getElementById("tabgroup-preview-panel"); + let groupPreviewShown = BrowserTestUtils.waitForPopupEvent( + groupPreview, + "shown" + ); + EventUtils.synthesizeMouseAtCenter( + group.labelElement, + { + type: "mouseover", + }, + window + ); + await groupPreviewShown; + await flushL10n(); + + Assert.equal( + groupPreview.getAttribute("aria-label"), + "Tabs in a collapsed group", + "Group preview panel has correct label" + ); + Assert.equal( + group.labelElement.getAttribute("aria-description"), + "Tabs list open", + "Group label has a description indicating the preview is open" + ); + Assert.equal( + groupPreview.querySelector("toolbarbutton").getAttribute("role"), + "button", + "Group menu items have correct role" + ); + + info("dismissing group preview"); + let groupPreviewHidden = BrowserTestUtils.waitForPopupEvent( + groupPreview, + "hidden" + ); + EventUtils.synthesizeMouseAtCenter( + document.body, + { type: "mouseover" }, + window + ); + await groupPreviewHidden; + + Assert.equal( + group.labelElement.getAttribute("aria-description"), + "Tabs list closed", + "Group label has a description indicating the preview is closed" + ); + + await TabGroupTestUtils.removeTabGroup(group); + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/locales/en-US/browser/tabbrowser.ftl b/browser/locales/en-US/browser/tabbrowser.ftl @@ -260,6 +260,16 @@ tab-group-editor-color-selector2-red = Red tab-group-description = { $tabGroupName } — Tab Group tab-group-label-tooltip-collapsed = { $tabGroupName } — Collapsed tab-group-label-tooltip-expanded = { $tabGroupName } — Expanded +tab-group-preview-name = + .aria-label = Tabs in a collapsed group + +## When collapsed, the tab group label's aria-description will indicate +## whether the hover menu is open or closed. + +tab-group-preview-open-description = Tabs list open +tab-group-preview-closed-description = Tabs list closed + +## tab-context-unnamed-group = .label = Unnamed group