WebUI Tab Strip: Add aria-labels and roles to elements
- Add more accessible labels for title and alert states.
- Move the focusable target from TabElement's host to inner #tab
element to allow aria-labelledby to work.
- Add role and tabindex to appropriate elements.
- Does not update focus outline UI.
Screenshot examples of what is read out: https://imgur.com/a/o0Hmu3a
Bug: 1017472
Change-Id: I230e810fadf92749c2ecf824bb92eb4d8c45774d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1877194
Commit-Queue: John Lee <[email protected]>
Reviewed-by: Demetrios Papadopoulos <[email protected]>
Cr-Commit-Position: refs/heads/master@{#709246}
diff --git a/chrome/browser/resources/tab_strip/tab.js b/chrome/browser/resources/tab_strip/tab.js
index a9e4bc0..cafce9b 100644
--- a/chrome/browser/resources/tab_strip/tab.js
+++ b/chrome/browser/resources/tab_strip/tab.js
@@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import './strings.m.js';
+
import {assert} from 'chrome://resources/js/assert.m.js';
import {getFavicon} from 'chrome://resources/js/icon.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {AlertIndicatorsElement} from './alert_indicators.js';
import {CustomElement} from './custom_element.js';
@@ -13,6 +16,24 @@
export const DEFAULT_ANIMATION_DURATION = 125;
+/**
+ * @param {!TabData} tab
+ * @return {string}
+ */
+function getAccessibleTitle(tab) {
+ const tabTitle = tab.title;
+
+ if (tab.crashed) {
+ return loadTimeData.getStringF('tabCrashed', tabTitle);
+ }
+
+ if (tab.networkState === TabNetworkState.ERROR) {
+ return loadTimeData.getStringF('tabNetworkError', tabTitle);
+ }
+
+ return tabTitle;
+}
+
export class TabElement extends CustomElement {
static get template() {
return `{__html_template__}`;
@@ -31,11 +52,12 @@
/** @private {!HTMLElement} */
this.closeButtonEl_ =
/** @type {!HTMLElement} */ (this.shadowRoot.querySelector('#close'));
+ this.closeButtonEl_.setAttribute(
+ 'aria-label', loadTimeData.getString('closeTab'));
/** @private {!HTMLElement} */
- this.dragImage_ =
- /** @type {!HTMLElement} */ (
- this.shadowRoot.querySelector('#dragImage'));
+ this.tabEl_ =
+ /** @type {!HTMLElement} */ (this.shadowRoot.querySelector('#tab'));
/** @private {!HTMLElement} */
this.faviconEl_ =
@@ -63,8 +85,8 @@
this.titleTextEl_ = /** @type {!HTMLElement} */ (
this.shadowRoot.querySelector('#titleText'));
- this.addEventListener('click', this.onClick_.bind(this));
- this.addEventListener('contextmenu', this.onContextMenu_.bind(this));
+ this.tabEl_.addEventListener('click', this.onClick_.bind(this));
+ this.tabEl_.addEventListener('contextmenu', this.onContextMenu_.bind(this));
this.closeButtonEl_.addEventListener('click', this.onClose_.bind(this));
}
@@ -77,6 +99,7 @@
set tab(tab) {
assert(this.tab_ !== tab);
this.toggleAttribute('active', tab.active);
+ this.tabEl_.setAttribute('aria-selected', tab.active.toString());
this.toggleAttribute('hide-icon_', !tab.showIcon);
this.toggleAttribute(
'waiting_',
@@ -94,6 +117,7 @@
if (!this.tab_ || this.tab_.title !== tab.title) {
this.titleTextEl_.textContent = tab.title;
}
+ this.titleTextEl_.setAttribute('aria-label', getAccessibleTitle(tab));
if (tab.networkState === TabNetworkState.WAITING ||
(tab.networkState === TabNetworkState.LOADING &&
@@ -120,11 +144,9 @@
this.tab_ = Object.freeze(tab);
}
- /**
- * @return {!HTMLElement}
- */
+ /** @return {!HTMLElement} */
getDragImage() {
- return this.dragImage_;
+ return this.tabEl_;
}
/**
@@ -147,7 +169,10 @@
}
}
- /** @private */
+ /**
+ * @param {!Event} event
+ * @private
+ */
onContextMenu_(event) {
event.preventDefault();