mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1884970 - Close current tab button is missing an accessible name and role. r=tabbrowser-reviewers,fluent-reviewers,dao,bolsson,flod
The [tab-close-button](https://searchfox.org/mozilla-central/rev/f9157a03835653cd3ece8d2dc713a782b7e4374e/browser/base/content/tabbrowser-tab.js#40) is not labeled and is missing an interactive role of button, while it is functioning as one. Note: we do not want this control to be keyboard focusable, because keyboard-only user could close the tab via the context menu and we don't want to create an additional tab stop for the navigation as well, but making sure the control is marked up as a button with an accessible name would allow it to be actionable with speech-to-text software, with touch devices, with switch controls in scan mode, and for screen readers via their navigation shortcuts as well. Differential Revision: https://phabricator.services.mozilla.com/D204413
This commit is contained in:
parent
19e11b61ea
commit
39d02ec02f
@ -115,6 +115,11 @@
|
||||
// xul:text, i.e. the tab label text
|
||||
role: ROLE_TEXT_LEAF,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("Close tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -126,6 +131,11 @@
|
||||
// xul:text, i.e. the tab label text
|
||||
role: ROLE_TEXT_LEAF,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
// xul:toolbarbutton ("Close tab")
|
||||
role: ROLE_PUSHBUTTON,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -37,7 +37,7 @@
|
||||
<label class="tab-icon-sound-label tab-icon-sound-tooltip-label" role="presentation"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<image class="tab-close-button close-icon" role="presentation"/>
|
||||
<image class="tab-close-button close-icon" role="button" data-l10n-id="tabbrowser-close-tabs-button" data-l10n-args='{"tabCount": 1}' keyNav="false"/>
|
||||
</hbox>
|
||||
</stack>
|
||||
`;
|
||||
|
@ -5110,6 +5110,9 @@
|
||||
|
||||
this.tabContainer._updateCloseButtons();
|
||||
this.tabContainer._updateHiddenTabsStatus();
|
||||
if (aTab.multiselected) {
|
||||
this._updateMultiselectedTabCloseButtonTooltip();
|
||||
}
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent("TabShow", true, false);
|
||||
@ -5133,6 +5136,9 @@
|
||||
|
||||
this.tabContainer._updateCloseButtons();
|
||||
this.tabContainer._updateHiddenTabsStatus();
|
||||
if (aTab.multiselected) {
|
||||
this._updateMultiselectedTabCloseButtonTooltip();
|
||||
}
|
||||
|
||||
// Splice this tab out of any lines of succession before any events are
|
||||
// dispatched.
|
||||
@ -5499,6 +5505,19 @@
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update accessible names of close buttons in the (multi) selected tabs
|
||||
* collection with how many tabs they will close
|
||||
*/
|
||||
_updateMultiselectedTabCloseButtonTooltip() {
|
||||
const tabCount = gBrowser.selectedTabs.length;
|
||||
gBrowser.selectedTabs.forEach(selectedTab => {
|
||||
document.l10n.setArgs(selectedTab.querySelector(".tab-close-button"), {
|
||||
tabCount,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
addToMultiSelectedTabs(aTab) {
|
||||
if (aTab.multiselected) {
|
||||
return;
|
||||
@ -5513,6 +5532,8 @@
|
||||
} else {
|
||||
this._multiSelectChangeAdditions.add(aTab);
|
||||
}
|
||||
|
||||
this._updateMultiselectedTabCloseButtonTooltip();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -5535,6 +5556,8 @@
|
||||
for (let i = lowerIndex; i <= higherIndex; i++) {
|
||||
this.addToMultiSelectedTabs(tabs[i]);
|
||||
}
|
||||
|
||||
this._updateMultiselectedTabCloseButtonTooltip();
|
||||
},
|
||||
|
||||
removeFromMultiSelectedTabs(aTab) {
|
||||
@ -5550,6 +5573,13 @@
|
||||
} else {
|
||||
this._multiSelectChangeRemovals.add(aTab);
|
||||
}
|
||||
// Update labels for Close buttons of the remaining multiselected tabs:
|
||||
this._updateMultiselectedTabCloseButtonTooltip();
|
||||
// Update the label for the Close button of the tab being removed
|
||||
// from the multiselection:
|
||||
document.l10n.setArgs(aTab.querySelector(".tab-close-button"), {
|
||||
tabCount: 1,
|
||||
});
|
||||
},
|
||||
|
||||
clearMultiSelectedTabs() {
|
||||
@ -6060,12 +6090,7 @@
|
||||
const tabCount = this.selectedTabs.includes(tab)
|
||||
? this.selectedTabs.length
|
||||
: 1;
|
||||
if (tab.mOverCloseButton) {
|
||||
tooltip.label = "";
|
||||
document.l10n.setAttributes(tooltip, "tabbrowser-close-tabs-tooltip", {
|
||||
tabCount,
|
||||
});
|
||||
} else if (tab._overPlayingIcon) {
|
||||
if (tab._overPlayingIcon) {
|
||||
let l10nId;
|
||||
const l10nArgs = { tabCount };
|
||||
if (tab.selected) {
|
||||
|
@ -14,6 +14,25 @@ async function openTabMenuFor(tab) {
|
||||
return tabMenu;
|
||||
}
|
||||
|
||||
function checkTabCloseButtonTooltip(
|
||||
tab,
|
||||
expectedTabCount = 1 /* not multiselected */
|
||||
) {
|
||||
const l10nAttrs = document.l10n.getAttributes(
|
||||
tab.querySelector(".tab-close-button")
|
||||
);
|
||||
Assert.deepEqual(
|
||||
l10nAttrs,
|
||||
{
|
||||
id: "tabbrowser-close-tabs-button",
|
||||
args: {
|
||||
tabCount: expectedTabCount,
|
||||
},
|
||||
},
|
||||
`Close tab button has an expected accessible name with ${expectedTabCount} tabs (multi) selected.`
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function setPref() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[PREF_WARN_ON_CLOSE, false]],
|
||||
@ -27,37 +46,46 @@ add_task(async function usingTabCloseButton() {
|
||||
let tab4 = await addTab();
|
||||
|
||||
is(gBrowser.multiSelectedTabsCount, 0, "Zero multiselected tabs");
|
||||
checkTabCloseButtonTooltip(tab1);
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, tab1);
|
||||
await triggerClickOn(tab2, { ctrlKey: true });
|
||||
|
||||
ok(tab1.multiselected, "Tab1 is multiselected");
|
||||
checkTabCloseButtonTooltip(tab1, 2);
|
||||
ok(tab2.multiselected, "Tab2 is multiselected");
|
||||
checkTabCloseButtonTooltip(tab2, 2);
|
||||
ok(!tab3.multiselected, "Tab3 is not multiselected");
|
||||
checkTabCloseButtonTooltip(tab3);
|
||||
ok(!tab4.multiselected, "Tab4 is not multiselected");
|
||||
checkTabCloseButtonTooltip(tab4);
|
||||
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
|
||||
is(gBrowser.selectedTab, tab1, "Tab1 is active");
|
||||
|
||||
await triggerClickOn(tab3, { ctrlKey: true });
|
||||
is(gBrowser.multiSelectedTabsCount, 3, "Three multiselected tabs");
|
||||
checkTabCloseButtonTooltip(tab1, 3);
|
||||
gBrowser.hideTab(tab3);
|
||||
is(
|
||||
gBrowser.multiSelectedTabsCount,
|
||||
2,
|
||||
"Two multiselected tabs after hiding one tab"
|
||||
);
|
||||
checkTabCloseButtonTooltip(tab1, 2);
|
||||
gBrowser.showTab(tab3);
|
||||
is(
|
||||
gBrowser.multiSelectedTabsCount,
|
||||
3,
|
||||
"Three multiselected tabs after re-showing hidden tab"
|
||||
);
|
||||
checkTabCloseButtonTooltip(tab1, 3);
|
||||
await triggerClickOn(tab3, { ctrlKey: true });
|
||||
is(
|
||||
gBrowser.multiSelectedTabsCount,
|
||||
2,
|
||||
"Two multiselected tabs after ctrl-clicking multiselected tab"
|
||||
);
|
||||
checkTabCloseButtonTooltip(tab1, 2);
|
||||
|
||||
// Closing a tab which is not multiselected
|
||||
let tab4CloseBtn = tab4.closeButton;
|
||||
|
@ -4,10 +4,12 @@ const MOUSE_OFFSET = 7;
|
||||
// Normal tooltips are positioned vertically at least this amount
|
||||
const MIN_VERTICAL_TOOLTIP_OFFSET = 18;
|
||||
|
||||
function openTooltip(node, tooltip) {
|
||||
function openTooltip(node) {
|
||||
let tooltipShownPromise = BrowserTestUtils.waitForEvent(
|
||||
tooltip,
|
||||
"popupshown"
|
||||
document,
|
||||
"popupshown",
|
||||
false,
|
||||
event => event.originalTarget.nodeName == "tooltip"
|
||||
);
|
||||
window.windowUtils.disableNonTestMouseEvents(true);
|
||||
EventUtils.synthesizeMouse(node, 2, 2, { type: "mouseover" });
|
||||
@ -20,10 +22,12 @@ function openTooltip(node, tooltip) {
|
||||
return tooltipShownPromise;
|
||||
}
|
||||
|
||||
function closeTooltip(node, tooltip) {
|
||||
function closeTooltip() {
|
||||
let tooltipHiddenPromise = BrowserTestUtils.waitForEvent(
|
||||
tooltip,
|
||||
"popuphidden"
|
||||
document,
|
||||
"popuphidden",
|
||||
false,
|
||||
event => event.originalTarget.nodeName == "tooltip"
|
||||
);
|
||||
EventUtils.synthesizeMouse(document.documentElement, 2, 2, {
|
||||
type: "mousemove",
|
||||
@ -44,8 +48,8 @@ add_task(async function () {
|
||||
"data:text/html,<html><head><title>A Tab</title></head><body>Hello</body></html>";
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl);
|
||||
|
||||
let tooltip = document.getElementById("tabbrowser-tab-tooltip");
|
||||
await openTooltip(tab, tooltip);
|
||||
let event = await openTooltip(tab);
|
||||
let tooltip = event.originalTarget;
|
||||
|
||||
let tabRect = tab.getBoundingClientRect();
|
||||
let tooltipRect = tooltip.getBoundingClientRect();
|
||||
@ -67,9 +71,10 @@ add_task(async function () {
|
||||
"tooltip position attribute for tab"
|
||||
);
|
||||
|
||||
await closeTooltip(tab, tooltip);
|
||||
await closeTooltip();
|
||||
|
||||
await openTooltip(tab.closeButton, tooltip);
|
||||
event = await openTooltip(tab.closeButton);
|
||||
tooltip = event.originalTarget;
|
||||
|
||||
let closeButtonRect = tab.closeButton.getBoundingClientRect();
|
||||
tooltipRect = tooltip.getBoundingClientRect();
|
||||
@ -90,7 +95,7 @@ add_task(async function () {
|
||||
"tooltip position attribute for close button"
|
||||
);
|
||||
|
||||
await closeTooltip(tab, tooltip);
|
||||
await closeTooltip();
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
@ -101,8 +106,8 @@ add_task(async function () {
|
||||
"data:text/html,<html><head><title>A Tab</title></head><body>Hello</body></html>";
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, tabUrl);
|
||||
|
||||
let tooltip = document.getElementById("tabbrowser-tab-tooltip");
|
||||
await openTooltip(tab, tooltip);
|
||||
let event = await openTooltip(tab);
|
||||
let tooltip = event.originalTarget;
|
||||
|
||||
EventUtils.synthesizeWheel(tab, 4, 4, {
|
||||
deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||
|
@ -16,10 +16,13 @@ tabbrowser-menuitem-close =
|
||||
# $containerName (String): the name of the current container.
|
||||
tabbrowser-container-tab-title = { $title } — { $containerName }
|
||||
|
||||
# This text serves as an on-screen tooltip as well as an accessible name for
|
||||
# the "X" button that is shown on the active tab or, when multiple tabs are
|
||||
# selected, to all their "X" buttons.
|
||||
# Variables:
|
||||
# $tabCount (Number): The number of tabs that will be closed.
|
||||
tabbrowser-close-tabs-tooltip =
|
||||
.label =
|
||||
tabbrowser-close-tabs-button =
|
||||
.tooltiptext =
|
||||
{ $tabCount ->
|
||||
[one] Close tab
|
||||
*[other] Close { $tabCount } tabs
|
||||
|
@ -0,0 +1,23 @@
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
from fluent.migrate.helpers import transforms_from
|
||||
|
||||
|
||||
def migrate(ctx):
|
||||
"""Bug 1884970 - Close current tab button is missing an accessible name and role, part {index}."""
|
||||
|
||||
source = "browser/browser/tabbrowser.ftl"
|
||||
target = "browser/browser/tabbrowser.ftl"
|
||||
|
||||
ctx.add_transforms(
|
||||
target,
|
||||
target,
|
||||
transforms_from(
|
||||
"""
|
||||
tabbrowser-close-tabs-button =
|
||||
.tooltiptext = {COPY_PATTERN(from_path, "tabbrowser-close-tabs-tooltip.label")}
|
||||
""",
|
||||
from_path=source,
|
||||
),
|
||||
)
|
Loading…
Reference in New Issue
Block a user