Commit cfa6bc7d authored by Moe Ahmadi's avatar Moe Ahmadi Committed by Commit Bot

[WebUI][NTP] realbox a11y improvements

- Makes <ntp-realbox-button> and <ntp-realbox-match> custom elements
  focusable with proper aria roles and labels. Removes or replaces
  focusable child elements of these custom elements. Removing the <a>
  element in <ntp-realbox-match> has the added benefit of eliminating
  the possilbity of navigation to a suggeestion without going through
  the browser.
- Adds tooltips and aria labels to the suggestion header toggle button:
 - Tooltip: "Hide suggestions" / "Show suggestions"
 - Voiceover: "Hide the 'X' section" / "Show the 'X' section"
- Allows the suggestion header text to truncate if needed.

Bug: 1084718
Change-Id: I063821d9bfcef191deb070007c028c44903feaae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2210938
Commit-Queue: Moe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarEsmael Elmoslimany <aee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#770899}
parent dd642873
......@@ -6,6 +6,7 @@
flex-shrink: 0;
height: 24px;
justify-content: center;
outline: none;
width: 24px;
}
......@@ -21,37 +22,33 @@
background-color: var(--search-box-icon-bg-focused, rgba(var(--google-grey-900-rgb), .32));
}
button {
#icon {
-webkit-mask-image: url(chrome://resources/images/icon_clear.svg);
-webkit-mask-position: center;
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 16px;
background: transparent;
border: 0;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
:host-context(.header) button {
:host-context(.header) #icon {
-webkit-mask-image: url(icons/chevron.svg);
-webkit-transform: rotate(180deg);
background-color: var(--search-box-icon, var(--google-grey-900));
}
:host-context(.header[group-is-hidden]) button {
:host-context(.header[group-is-hidden]) #icon {
-webkit-transform: none;
}
:host-context(ntp-realbox-match:hover) button {
:host-context(ntp-realbox-match:hover) #icon {
background-color: var(--search-box-icon, var(--google-grey-900));
}
:host-context(ntp-realbox-match:-webkit-any(:focus-within, .selected)) button,
:host-context(.header:focus-within) button {
:host-context(ntp-realbox-match:-webkit-any(:focus-within, .selected)) #icon,
:host-context(.header:focus-within) #icon {
background-color: var(--search-box-icon-selected, var(--google-grey-900));
}
</style>
<button></button>
<div id="icon"></div>
......@@ -22,21 +22,27 @@
.header {
align-items: center;
color: rgb(var(--google-grey-refresh-700-rgb));
display: flex;
font-size: 13px;
font-weight: 500;
justify-content: space-between;
line-height: 16px;
margin-top: 8px;
outline: none;
overflow: hidden;
padding-bottom: 4px;
padding-inline-end: 16px;
padding-inline-start: 12px;
padding-top: 4px;
}
.header .text {
color: var(--google-grey-refresh-700);
cursor: default;
font-size: 13px;
font-weight: 500;
line-height: 16px;
overflow: hidden;
padding-inline-end: 8px;
text-overflow: ellipsis;
text-transform: uppercase;
user-select: none;
white-space: nowrap;
}
......@@ -59,18 +65,21 @@
<!-- Header cannot be tabbed into but gets focus when clicked. This stops
the dropdown from losing focus and closing as a result. -->
<div class="header" tabindex="-1" on-focusin="onHeaderFocusin_"
group-is-hidden$="[[groupIsHidden_(hiddenGroupIds_.*, groupId)]]">
[[headerForGroup_(groupId)]]
<ntp-realbox-button data-id$="[[groupId]]"
aria-hidden="true"
group-is-hidden$="[[groupIsHidden_(groupId, hiddenGroupIds_.*)]]">
<span class="text">[[headerForGroup_(groupId)]]</span>
<ntp-realbox-button data-id$="[[groupId]]" tabindex="0" role="button"
title="[[toggleButtonTitleForGroup_(groupId, hiddenGroupIds_.*)]]"
aria-label$="[[toggleButtonA11yLabelForGroup_(groupId, hiddenGroupIds_.*)]]"
on-click="onToggleButtonClick_" on-keydown="onToggleButtonKeydown_">
</ntp-realbox-button>
</div>
</template>
<template is="dom-if" if="[[!groupIsHidden_(hiddenGroupIds_.*, groupId)]]"
<template is="dom-if" if="[[!groupIsHidden_(groupId, hiddenGroupIds_.*)]]"
restamp>
<template is="dom-repeat" items="[[result.matches]]"
filter="[[computeMatchBelongsToGroup_(groupId)]]">
<ntp-realbox-match role="option" match="[[item]]"
<ntp-realbox-match tabindex="0" role="option" match="[[item]]"
match-index="[[matchIndex_(item)]]">
</ntp-realbox-match>
</template>
......
......@@ -340,13 +340,12 @@ class RealboxDropdownElement extends PolymerElement {
}
/**
* @param {!Object} hiddenGroupIdsChange
* @param {string} groupId
* @returns {boolean} Whether matches with the given suggestion group ID
* should be hidden.
* @private
*/
groupIsHidden_(hiddenGroupIdsChange, groupId) {
groupIsHidden_(groupId) {
return this.hiddenGroupIds_.indexOf(groupId) !== -1;
}
......@@ -357,10 +356,42 @@ class RealboxDropdownElement extends PolymerElement {
* @suppress {checkTypes}
*/
headerForGroup_(groupId) {
return this.result && this.groupHasHeader_(groupId) ?
if (!this.groupHasHeader_(groupId)) {
return '';
}
return (this.result && this.result.suggestionGroupsMap &&
this.result.suggestionGroupsMap[groupId]) ?
decodeString16(this.result.suggestionGroupsMap[groupId].header) :
'';
}
/**
* @param {string} groupId
* @returns {string} Tooltip for suggestion group show/hide toggle button.
* @private
*/
toggleButtonTitleForGroup_(groupId) {
if (!this.groupHasHeader_(groupId)) {
return '';
}
return loadTimeData.getString(
this.groupIsHidden_(groupId) ? 'showSuggestions' : 'hideSuggestions');
}
/**
* @param {string} groupId
* @returns {string} A11y label for suggestion group show/hide toggle button.
* @private
*/
toggleButtonA11yLabelForGroup_(groupId) {
if (!this.groupHasHeader_(groupId)) {
return '';
}
return loadTimeData.substituteString(
loadTimeData.getString(
this.groupIsHidden_(groupId) ? 'showSection' : 'hideSection'),
this.headerForGroup_(groupId));
}
}
customElements.define(RealboxDropdownElement.is, RealboxDropdownElement);
<style>
:host {
display: block;
}
#link {
align-items: center;
color: inherit;
cursor: default;
display: flex;
font-size: 16px;
line-height: 1;
outline: none;
overflow: hidden;
padding-bottom: 6px;
padding-inline-end: 16px;
padding-inline-start: 12px;
padding-top: 6px;
position: relative;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
}
#container {
......@@ -28,6 +19,7 @@
overflow: hidden;
padding-inline-end: 8px;
padding-inline-start: 8px;
white-space: nowrap;
}
#contents,
......@@ -55,6 +47,7 @@
:host([has-image]) #description {
font-size: 14px;
line-height: 16px;
margin-top: 2px;
width: 100%;
}
......@@ -82,17 +75,15 @@
}
</style>
<a id="link" href$="[[match.destinationUrl.url]]">
<ntp-realbox-icon id="icon" match="[[match]]"
favicon-data-url="[[faviconDataUrl]]" image-data-url="[[imageDataUrl]]">
</ntp-realbox-icon>
<div id="container">
<span id="contents" inner-h-t-m-l="[[contentsHtml_]]"></span>
<span id="separator">[[separatorText_]]</span>
<span id="description" inner-h-t-m-l="[[descriptionHtml_]]"></span>
</div>
<ntp-realbox-button id="remove" on-click="onRemoveButtonClick_"
on-keydown="onRemoveButtonKeydown_"
title$="[[removeButtonTitle_]]" hidden$="[[!removeButtonIsVisible_]]">
</ntp-realbox-button>
</a>
<ntp-realbox-icon id="icon" match="[[match]]"
favicon-data-url="[[faviconDataUrl]]" image-data-url="[[imageDataUrl]]">
</ntp-realbox-icon>
<div id="container">
<span id="contents" inner-h-t-m-l="[[contentsHtml_]]"></span>
<span id="separator">[[separatorText_]]</span>
<span id="description" inner-h-t-m-l="[[descriptionHtml_]]"></span>
</div>
<ntp-realbox-button id="remove" tabindex="0" role="button"
on-click="onRemoveButtonClick_" on-keydown="onRemoveButtonKeydown_"
title$="[[removeButtonTitle_]]" hidden$="[[!removeButtonIsVisible_]]">
</ntp-realbox-button>
......@@ -184,18 +184,6 @@ class RealboxMatchElement extends PolymerElement {
this.addEventListener('focusin', this.onMatchFocusin_.bind(this));
}
//============================================================================
// Public methods
//============================================================================
/**
* Focuses the <a> child element.
* @override
*/
focus() {
this.$.link.focus();
}
//============================================================================
// Callbacks
//============================================================================
......
......@@ -151,6 +151,10 @@ content::WebUIDataSource* CreateNewTabPageUiHtmlSource(Profile* profile) {
{"searchBoxHint", IDS_GOOGLE_SEARCH_BOX_EMPTY_HINT_MD},
{"realboxSeparator", IDS_AUTOCOMPLETE_MATCH_DESCRIPTION_SEPARATOR},
{"removeSuggestion", IDS_OMNIBOX_REMOVE_SUGGESTION},
{"hideSuggestions", IDS_TOOLTIP_HEADER_HIDE_SUGGESTIONS_BUTTON},
{"showSuggestions", IDS_TOOLTIP_HEADER_SHOW_SUGGESTIONS_BUTTON},
{"hideSection", IDS_ACC_HEADER_HIDE_SUGGESTIONS_BUTTON},
{"showSection", IDS_ACC_HEADER_SHOW_SUGGESTIONS_BUTTON},
// Logo/doodle.
{"copyLink", IDS_NTP_DOODLE_SHARE_DIALOG_COPY_LABEL},
......
......@@ -157,7 +157,6 @@ function createSearchMatch(modifiers = {}) {
*/
function verifyMatch(match, matchEl) {
assertEquals('option', matchEl.getAttribute('role'));
assertEquals(match.destinationUrl.url, matchEl.$.link.href);
const matchContents = decodeString16(match.contents);
const matchDescription = decodeString16(match.description);
assertEquals(
......
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