Commit 5a8ec6b7 authored by Yuheng Huang's avatar Yuheng Huang Committed by Josip Sokcevic

Support a11y screen reader

Bug: 1099917
Change-Id: I9038a2ac5911ad01cfa3b0683c4e062146b05dd3
Reviewed-on: https://chrome-internal-review.googlesource.com/c/chrome/browser/resources/tab_search/+/3272139Reviewed-by: default avatarJohn Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#819606}
parent 5014818d
...@@ -25,6 +25,7 @@ js_library("app") { ...@@ -25,6 +25,7 @@ js_library("app") {
":tab_search_api_proxy", ":tab_search_api_proxy",
":tab_search_item", ":tab_search_item",
":tab_search_search_field", ":tab_search_search_field",
"//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer",
"//third_party/polymer/v3_0/components-chromium/iron-selector:iron-selector", "//third_party/polymer/v3_0/components-chromium/iron-selector:iron-selector",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:cr.m", "//ui/webui/resources/js:cr.m",
......
...@@ -60,12 +60,13 @@ ...@@ -60,12 +60,13 @@
</tab-search-search-field> </tab-search-search-field>
<div id="tabs"> <div id="tabs">
<iron-selector id="selector" on-keydown="onItemKeyDown_" <iron-selector id="selector" on-keydown="onItemKeyDown_"
selected="{{selectedIndex_}}" selected-class="selected"> selected="{{selectedIndex_}}" selected-class="selected" role="listbox">
<template id="tabsList" is="dom-repeat" items="[[filteredOpenTabs_]]" <template id="tabsList" is="dom-repeat" items="[[filteredOpenTabs_]]"
initial-count="[[chunkingItemCount_]]"> initial-count="[[chunkingItemCount_]]">
<tab-search-item id="[[item.tabId]]" class="mwb-list-item" data="[[item]]" <tab-search-item id="[[item.tabId]]" aria-label="[[ariaLabel_(item)]]"
class="mwb-list-item" data="[[item]]"
on-click="onItemClick_" on-close="onItemClose_" on-click="onItemClick_" on-close="onItemClose_"
on-focus="onItemFocus_" tabindex="0" > on-focus="onItemFocus_" tabindex="0" role="option">
</tab-search-item> </tab-search-item>
</template> </template>
</iron-selector> </iron-selector>
......
...@@ -11,10 +11,13 @@ import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js'; ...@@ -11,10 +11,13 @@ import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js'; import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
import './tab_search_item.js'; import './tab_search_item.js';
import './tab_search_search_field.js' import './tab_search_search_field.js'
import './strings.js';
import {assert} from 'chrome://resources/js/assert.m.js'; import {assert} from 'chrome://resources/js/assert.m.js';
import {isMac} from 'chrome://resources/js/cr.m.js'; import {isMac} from 'chrome://resources/js/cr.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {listenOnce} from 'chrome://resources/js/util.m.js'; import {listenOnce} from 'chrome://resources/js/util.m.js';
import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {fuzzySearch} from './fuzzy_search.js'; import {fuzzySearch} from './fuzzy_search.js';
...@@ -163,6 +166,18 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -163,6 +166,18 @@ export class TabSearchAppElement extends PolymerElement {
this.$.selector.selected = this.$.selector.selected =
this.filteredOpenTabs_.length > 0 ? 0 : undefined; this.filteredOpenTabs_.length > 0 ? 0 : undefined;
this.$.tabs.scrollTop = 0; this.$.tabs.scrollTop = 0;
const length = this.filteredOpenTabs_.length;
let text;
if (this.searchText_.length > 0) {
text = loadTimeData.getStringF(
length == 1 ? 'a11yFoundTabFor' : 'a11yFoundTabsFor', length,
this.searchText_);
} else {
text = loadTimeData.getStringF(
length == 1 ? 'a11yFoundTab' : 'a11yFoundTabs', length);
}
this.announceA11y_(text);
} }
/** @private */ /** @private */
...@@ -191,6 +206,7 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -191,6 +206,7 @@ export class TabSearchAppElement extends PolymerElement {
onItemClose_(e) { onItemClose_(e) {
const tabId = Number.parseInt(e.currentTarget.id, 10); const tabId = Number.parseInt(e.currentTarget.id, 10);
this.apiProxy_.closeTab(tabId); this.apiProxy_.closeTab(tabId);
this.announceA11y_(loadTimeData.getString('a11yTabClosed'));
} }
/** /**
...@@ -301,6 +317,11 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -301,6 +317,11 @@ export class TabSearchAppElement extends PolymerElement {
this.selectorNavigate_(e.key); this.selectorNavigate_(e.key);
this.updateScrollView_(); this.updateScrollView_();
e.preventDefault(); e.preventDefault();
// For some reasons setting combobox/aria-activedescendant on tab-search-search-field
// has no effect, so manually announce a11y message here.
const selectedItem = this.filteredOpenTabs_[this.getSelectedIndex()];
this.announceA11y_(this.ariaLabel_(selectedItem));
} else if (e.key === 'Enter') { } else if (e.key === 'Enter') {
const selectedItem = this.filteredOpenTabs_[this.getSelectedIndex()]; const selectedItem = this.filteredOpenTabs_[this.getSelectedIndex()];
this.apiProxy_.switchToTab( this.apiProxy_.switchToTab(
...@@ -308,6 +329,21 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -308,6 +329,21 @@ export class TabSearchAppElement extends PolymerElement {
} }
} }
/** @param {string} text */
announceA11y_(text) {
IronA11yAnnouncer.requestAvailability();
this.dispatchEvent(new CustomEvent(
'iron-announce', {bubbles: true, composed: true, detail: {text}}));
}
/**
* @return {string}
* @private
*/
ariaLabel_(item) {
return `${item.title} ${item.hostname}`;
}
/** /**
* @return {string} * @return {string}
* @private * @private
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
<div class="secondary-text">[[urlHostname_(data.url)]]</div> <div class="secondary-text">[[urlHostname_(data.url)]]</div>
</div> </div>
<div class="button-container"> <div class="button-container">
<cr-icon-button id="closeButton" aria-labelledby="label" <cr-icon-button id="closeButton" aria-label="[[ariaLabel_(data.title)]]"
iron-icon="cr:close" on-click="onItemClose_" title="$i18n{closeTab}"> iron-icon="cr:close" on-click="onItemClose_" title="$i18n{closeTab}">
</cr-icon-button> </cr-icon-button>
</div> </div>
...@@ -9,8 +9,11 @@ import 'chrome://resources/cr_elements/mwb_shared_vars.js'; ...@@ -9,8 +9,11 @@ import 'chrome://resources/cr_elements/mwb_shared_vars.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import {getFaviconForPageURL} from 'chrome://resources/js/icon.m.js'; import {getFaviconForPageURL} from 'chrome://resources/js/icon.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import './strings.js';
export class TabSearchItem extends PolymerElement { export class TabSearchItem extends PolymerElement {
static get is() { static get is() {
return 'tab-search-item'; return 'tab-search-item';
...@@ -54,6 +57,10 @@ export class TabSearchItem extends PolymerElement { ...@@ -54,6 +57,10 @@ export class TabSearchItem extends PolymerElement {
faviconUrl_(isDefaultFavicon, url) { faviconUrl_(isDefaultFavicon, url) {
return getFaviconForPageURL(isDefaultFavicon ? undefined : url, false); return getFaviconForPageURL(isDefaultFavicon ? undefined : url, false);
} }
ariaLabel_(title) {
return `${loadTimeData.getString('closeTab')} ${title}`;
}
} }
customElements.define(TabSearchItem.is, TabSearchItem); customElements.define(TabSearchItem.is, TabSearchItem);
...@@ -13,8 +13,8 @@ js_type_check("closure_compile") { ...@@ -13,8 +13,8 @@ js_type_check("closure_compile") {
] ]
deps = [ deps = [
":fuzzy_search_test", ":fuzzy_search_test",
":tab_search_app_test",
":tab_search_app_focus_test", ":tab_search_app_focus_test",
":tab_search_app_test",
] ]
} }
......
...@@ -4,10 +4,10 @@ ...@@ -4,10 +4,10 @@
import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js'; import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
import {TabSearchAppElement} from 'chrome://tab-search/app.js'; import {TabSearchAppElement} from 'chrome://tab-search/app.js';
import {TabSearchSearchField} from 'chrome://tab-search/tab_search_search_field.js';
import {TabSearchApiProxy, TabSearchApiProxyImpl} from 'chrome://tab-search/tab_search_api_proxy.js' import {TabSearchApiProxy, TabSearchApiProxyImpl} from 'chrome://tab-search/tab_search_api_proxy.js'
import {TabSearchSearchField} from 'chrome://tab-search/tab_search_search_field.js';
import {assertEquals, assertNotEquals, assertFalse, assertTrue} from '../../chai_assert.js'; import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js';
import {flushTasks, waitAfterNextRender} from '../../test_util.m.js'; import {flushTasks, waitAfterNextRender} from '../../test_util.m.js';
import {sampleData} from './tab_search_test_data.js'; import {sampleData} from './tab_search_test_data.js';
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment