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 @@
</iron-iconset-svg>
<tab-search-search-field id="searchField" autofocus clear-label="$i18n{clearSearch}"
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>
<div hidden="[[!filteredOpenTabs_.length]]">
<infinite-list id="tabsList" items="[[filteredOpenTabs_]]" >
......
......@@ -89,6 +89,9 @@ export class TabSearchAppElement extends PolymerElement {
type: Boolean,
value: () => loadTimeData.getBoolean('moveActiveTabToBottom'),
},
/** @private */
searchResultText_: {type: String, value: ''}
};
}
......@@ -228,6 +231,14 @@ export class TabSearchAppElement extends PolymerElement {
/** @type {!InfiniteList} */ (this.$.tabsList).selected =
this.filteredOpenTabs_.length > 0 ? 0 : NO_SELECTION;
this.$.searchField.announce(this.getA11ySearchResultText_());
}
/**
* @return {string}
* @private
*/
getA11ySearchResultText_() {
const length = this.filteredOpenTabs_.length;
let text;
if (this.searchText_.length > 0) {
......@@ -238,7 +249,7 @@ export class TabSearchAppElement extends PolymerElement {
text = loadTimeData.getStringF(
length == 1 ? 'a11yFoundTab' : 'a11yFoundTabs', length);
}
this.announceA11y_(text);
return text;
}
/** @private */
......@@ -376,10 +387,9 @@ export class TabSearchAppElement extends PolymerElement {
e.stopPropagation();
e.preventDefault();
// For some reasons setting combobox/aria-activedescendant on
// tab-search-search-field has no effect, so manually announce a11y
// message here.
this.announceA11y_(
// TODO(tluk): Fix this to use aria-activedescendant when it's updated to
// work with ShadowDOM elements.
this.$.searchField.announce(
this.ariaLabel_(this.filteredOpenTabs_[this.getSelectedIndex()]));
} else if (e.key === 'Enter') {
this.apiProxy_.switchToTab(
......@@ -437,6 +447,7 @@ export class TabSearchAppElement extends PolymerElement {
});
this.filteredOpenTabs_ =
fuzzySearch(this.searchText_, result, this.fuzzySearchOptions_);
this.searchResultText_ = this.getA11ySearchResultText_();
}
/** return {!Tab} */
......
......@@ -60,6 +60,13 @@
color: var(--google-blue-refresh-300);
}
}
#inputAnnounce,
#searchResultText {
clip: rect(0,0,0,0);
display: inline-block;
position: fixed;
}
</style>
<iron-icon id="searchIcon" icon="mwb16:search"></iron-icon>
......@@ -67,9 +74,14 @@
<label id="searchLabel" for="searchInput" aria-hidden="true">
<span>[[label]]</span>
<span>[[shortcut_]]</span>
<span id="searchResultText">[[searchResultText]]</span>
</label>
<input id="searchInput" aria-labelledby="searchLabel"
autofocus="[[autofocus]]" autocomplete="off"
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>
......@@ -40,11 +40,25 @@ export class TabSearchSearchField extends TabSearchSearchFieldBase {
value: false,
},
/**
* Text that describes the resulting tabs currently present in the list.
*/
searchResultText: {
type: String,
value: '',
},
/** @private {string} */
shortcut_: {
type: String,
value: () => loadTimeData.getString('shortcutText'),
},
/** @private {string} */
announceText_: {
type: String,
value: '',
},
};
}
......@@ -52,6 +66,35 @@ export class TabSearchSearchField extends TabSearchSearchFieldBase {
getSearchInput() {
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);
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