Commit a04842f6 authored by Thomas Lukaszewicz's avatar Thomas Lukaszewicz Committed by Chromium LUCI CQ

Tab Search: Fix arrow key navigation when using NVDA screen reader

This CL updates tab search to correctly announce list items when
navigating with arrow keys from the input field.

Bug: 1152579
Change-Id: I6a13b688c01d7d7df8ceaca47001de2ad539d62c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2570452Reviewed-by: default avatarYuheng Huang <yuhengh@chromium.org>
Commit-Queue: Thomas Lukaszewicz <tluk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833885}
parent c4afc5c8
...@@ -53,7 +53,8 @@ ...@@ -53,7 +53,8 @@
</iron-iconset-svg> </iron-iconset-svg>
<tab-search-search-field id="searchField" autofocus clear-label="$i18n{clearSearch}" <tab-search-search-field id="searchField" autofocus clear-label="$i18n{clearSearch}"
label="$i18n{searchTabs}" on-focus="onSearchFocus_" label="$i18n{searchTabs}" on-focus="onSearchFocus_"
on-keydown="onSearchKeyDown_" on-search-changed="onSearchChanged_"> on-keydown="onSearchKeyDown_" on-search-changed="onSearchChanged_"
search-result-text="[[searchResultText_]]">
</tab-search-search-field> </tab-search-search-field>
<div hidden="[[!filteredOpenTabs_.length]]"> <div hidden="[[!filteredOpenTabs_.length]]">
<infinite-list id="tabsList" items="[[filteredOpenTabs_]]" > <infinite-list id="tabsList" items="[[filteredOpenTabs_]]" >
......
...@@ -89,6 +89,9 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -89,6 +89,9 @@ export class TabSearchAppElement extends PolymerElement {
type: Boolean, type: Boolean,
value: () => loadTimeData.getBoolean('moveActiveTabToBottom'), value: () => loadTimeData.getBoolean('moveActiveTabToBottom'),
}, },
/** @private */
searchResultText_: {type: String, value: ''}
}; };
} }
...@@ -228,6 +231,14 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -228,6 +231,14 @@ export class TabSearchAppElement extends PolymerElement {
/** @type {!InfiniteList} */ (this.$.tabsList).selected = /** @type {!InfiniteList} */ (this.$.tabsList).selected =
this.filteredOpenTabs_.length > 0 ? 0 : NO_SELECTION; this.filteredOpenTabs_.length > 0 ? 0 : NO_SELECTION;
this.$.searchField.announce(this.getA11ySearchResultText_());
}
/**
* @return {string}
* @private
*/
getA11ySearchResultText_() {
const length = this.filteredOpenTabs_.length; const length = this.filteredOpenTabs_.length;
let text; let text;
if (this.searchText_.length > 0) { if (this.searchText_.length > 0) {
...@@ -238,7 +249,7 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -238,7 +249,7 @@ export class TabSearchAppElement extends PolymerElement {
text = loadTimeData.getStringF( text = loadTimeData.getStringF(
length == 1 ? 'a11yFoundTab' : 'a11yFoundTabs', length); length == 1 ? 'a11yFoundTab' : 'a11yFoundTabs', length);
} }
this.announceA11y_(text); return text;
} }
/** @private */ /** @private */
...@@ -376,10 +387,9 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -376,10 +387,9 @@ export class TabSearchAppElement extends PolymerElement {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
// For some reasons setting combobox/aria-activedescendant on // TODO(tluk): Fix this to use aria-activedescendant when it's updated to
// tab-search-search-field has no effect, so manually announce a11y // work with ShadowDOM elements.
// message here. this.$.searchField.announce(
this.announceA11y_(
this.ariaLabel_(this.filteredOpenTabs_[this.getSelectedIndex()])); this.ariaLabel_(this.filteredOpenTabs_[this.getSelectedIndex()]));
} else if (e.key === 'Enter') { } else if (e.key === 'Enter') {
this.apiProxy_.switchToTab( this.apiProxy_.switchToTab(
...@@ -437,6 +447,7 @@ export class TabSearchAppElement extends PolymerElement { ...@@ -437,6 +447,7 @@ export class TabSearchAppElement extends PolymerElement {
}); });
this.filteredOpenTabs_ = this.filteredOpenTabs_ =
fuzzySearch(this.searchText_, result, this.fuzzySearchOptions_); fuzzySearch(this.searchText_, result, this.fuzzySearchOptions_);
this.searchResultText_ = this.getA11ySearchResultText_();
} }
/** return {!Tab} */ /** return {!Tab} */
......
...@@ -60,6 +60,13 @@ ...@@ -60,6 +60,13 @@
color: var(--google-blue-refresh-300); color: var(--google-blue-refresh-300);
} }
} }
#inputAnnounce,
#searchResultText {
clip: rect(0,0,0,0);
display: inline-block;
position: fixed;
}
</style> </style>
<iron-icon id="searchIcon" icon="mwb16:search"></iron-icon> <iron-icon id="searchIcon" icon="mwb16:search"></iron-icon>
...@@ -67,9 +74,14 @@ ...@@ -67,9 +74,14 @@
<label id="searchLabel" for="searchInput" aria-hidden="true"> <label id="searchLabel" for="searchInput" aria-hidden="true">
<span>[[label]]</span> <span>[[label]]</span>
<span>[[shortcut_]]</span> <span>[[shortcut_]]</span>
<span id="searchResultText">[[searchResultText]]</span>
</label> </label>
<input id="searchInput" aria-labelledby="searchLabel" <input id="searchInput" aria-labelledby="searchLabel"
autofocus="[[autofocus]]" autocomplete="off" autofocus="[[autofocus]]" autocomplete="off"
on-search="onSearchTermSearch" on-input="onSearchTermInput" type="search" on-search="onSearchTermSearch" on-input="onSearchTermInput" type="search"
spellcheck="false"> spellcheck="false" aria-describedby="inputAnnounce"
on-blur="onInputBlur_">
<span id="inputAnnounce" aria-live="assertive">
[[announceText_]]
</span>
</div> </div>
...@@ -40,11 +40,25 @@ export class TabSearchSearchField extends TabSearchSearchFieldBase { ...@@ -40,11 +40,25 @@ export class TabSearchSearchField extends TabSearchSearchFieldBase {
value: false, value: false,
}, },
/**
* Text that describes the resulting tabs currently present in the list.
*/
searchResultText: {
type: String,
value: '',
},
/** @private {string} */ /** @private {string} */
shortcut_: { shortcut_: {
type: String, type: String,
value: () => loadTimeData.getString('shortcutText'), value: () => loadTimeData.getString('shortcutText'),
}, },
/** @private {string} */
announceText_: {
type: String,
value: '',
},
}; };
} }
...@@ -52,6 +66,35 @@ export class TabSearchSearchField extends TabSearchSearchFieldBase { ...@@ -52,6 +66,35 @@ export class TabSearchSearchField extends TabSearchSearchFieldBase {
getSearchInput() { getSearchInput() {
return /** @type {!HTMLInputElement} */ (this.$.searchInput); return /** @type {!HTMLInputElement} */ (this.$.searchInput);
} }
/**
* Cause a text string to be announced by screen readers. Used for announcing
* when the input field has focus for better compatibility with screen
* readers.
* @param {string} text The text that should be announced.
*/
announce(text) {
this.$.searchWrapper.append(
this.$.searchWrapper.removeChild(this.$.inputAnnounce));
if (this.announceText_ === text) {
// A timeout is required when announcing duplicate text for certain
// screen readers.
this.announceText_ = '';
setTimeout(() => {
this.announceText_ = text;
}, 100);
} else {
this.announceText_ = text;
}
}
/**
* Clear |announceText_| when focus leaves the input field to ensure the text
* is not re-announced when focus returns to the input field.
*/
onInputBlur_(text) {
this.announceText_ = '';
}
} }
customElements.define(TabSearchSearchField.is, TabSearchSearchField); customElements.define(TabSearchSearchField.is, TabSearchSearchField);
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