personalization: accessibility in image grids
Add role=listbox to iron-list elements, and role=option to image containers. Add aria-posinset and aria-setsize attributes so screen readers report correct size of image grids. Add aria-label for back button. Add container with role=main and aria-label of current page - "Wallpaper Collections" on main page or localized name of collection. BUG=b/192975897 TEST=use Chromevox to navigate through new wallpaper app Cq-Include-Trybots: luci.chrome.try:linux-chromeos-chrome Change-Id: If7796d4405fb41eb8910da8934b912511bbbff72 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3010318 Commit-Queue: Jeffrey Young <cowmoo@chromium.org> Reviewed-by: Tao Wu <wutao@chromium.org> Cr-Commit-Position: refs/heads/master@{#900177}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
e6ffb0cf42
commit
52af24caec
chromeos
@ -1513,6 +1513,9 @@ Try tapping the mic to ask me anything.
|
|||||||
<message name="IDS_PERSONALIZATION_APP_BACK_BUTTON" desc="Aria label for the back button to return to a prior page">
|
<message name="IDS_PERSONALIZATION_APP_BACK_BUTTON" desc="Aria label for the back button to return to a prior page">
|
||||||
Back to <ph name="PAGE_NAME">$1<ex>Wallpaper</ex></ph>
|
Back to <ph name="PAGE_NAME">$1<ex>Wallpaper</ex></ph>
|
||||||
</message>
|
</message>
|
||||||
|
<message name="IDS_PERSONALIZATION_APP_WALLPAPER_COLLECTIONS" desc="Aria label for the wallpaper collections grid">
|
||||||
|
Wallpaper Collections
|
||||||
|
</message>
|
||||||
<message name="IDS_PERSONALIZATION_APP_CURRENTLY_SET" desc="Label for the currently set user wallpaper section.">
|
<message name="IDS_PERSONALIZATION_APP_CURRENTLY_SET" desc="Label for the currently set user wallpaper section.">
|
||||||
Currently set
|
Currently set
|
||||||
</message>
|
</message>
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
9886a8cbb0984357b1fc02e21d4962e63972b364
|
@ -60,7 +60,8 @@ void AddStrings(content::WebUIDataSource* source) {
|
|||||||
{"title", IDS_PERSONALIZATION_APP_TITLE},
|
{"title", IDS_PERSONALIZATION_APP_TITLE},
|
||||||
{"back", IDS_PERSONALIZATION_APP_BACK_BUTTON},
|
{"back", IDS_PERSONALIZATION_APP_BACK_BUTTON},
|
||||||
{"currentlySet", IDS_PERSONALIZATION_APP_CURRENTLY_SET},
|
{"currentlySet", IDS_PERSONALIZATION_APP_CURRENTLY_SET},
|
||||||
{"myImagesLabel", IDS_PERSONALIZATION_APP_MY_IMAGES}};
|
{"myImagesLabel", IDS_PERSONALIZATION_APP_MY_IMAGES},
|
||||||
|
{"wallpaperCollections", IDS_PERSONALIZATION_APP_WALLPAPER_COLLECTIONS}};
|
||||||
source->AddLocalizedStrings(kLocalizedStrings);
|
source->AddLocalizedStrings(kLocalizedStrings);
|
||||||
source->UseStringsJs();
|
source->UseStringsJs();
|
||||||
}
|
}
|
||||||
|
@ -2,23 +2,34 @@
|
|||||||
:host {
|
:host {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
#main {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<paper-spinner-lite active="[[imagesLoading_]]">
|
<paper-spinner-lite active="[[imagesLoading_]]">
|
||||||
</paper-spinner-lite>
|
</paper-spinner-lite>
|
||||||
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
||||||
<p hidden$="[[!hasError_]]" id="error">error</p>
|
<p hidden$="[[!hasError_]]" id="error">error</p>
|
||||||
<template is="dom-if" if="[[showImages_]]">
|
<template is="dom-if" if="[[showImages_]]">
|
||||||
<iron-list items="[[getImages_(hidden, images_)]]" grid>
|
<div id="main" role="main" aria-label="[[i18n('myImagesLabel')]]">
|
||||||
<template>
|
<iron-list items="[[getImages_(hidden, images_)]]" grid role="listbox"
|
||||||
<div class="photo-container">
|
scroll-target="main"
|
||||||
<div class="padding-fix">
|
aria-setsize$="[[getImageCount_(hidden, images_)]]">
|
||||||
<template is="dom-if"
|
<template>
|
||||||
if="[[shouldShowImage_(item, imageData_, imageDataLoading_)]]">
|
<div class="photo-container" role="option" tabindex$="[[tabIndex]]"
|
||||||
<img on-click="onClickImage_" data-id$="[[getImageKey_(item)]]"
|
aria-posinset$="[[getAriaIndex_(index)]]"
|
||||||
src="[[getImageData_(item, imageData_)]]">
|
aria-selected$="[[getAriaSelected_(item)]]">
|
||||||
</template>
|
<div class="padding-fix">
|
||||||
|
<template is="dom-if"
|
||||||
|
if="[[shouldShowImage_(item, imageData_, imageDataLoading_)]]">
|
||||||
|
<img on-click="onClickImage_" data-id$="[[getImageKey_(item)]]"
|
||||||
|
src="[[getImageData_(item, imageData_)]]" alt="[[item.name]]">
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</iron-list>
|
||||||
</iron-list>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -124,6 +124,25 @@ export class LocalImages extends WithPersonalizationStore {
|
|||||||
return hidden ? [] : images;
|
return hidden ? [] : images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} hidden
|
||||||
|
* @param {!Array<!chromeos.personalizationApp.mojom.LocalImage>} images
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getImageCount_(hidden, images) {
|
||||||
|
return this.getImages_(hidden, images).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO(b/192975897) compare with currently selected image to return correct
|
||||||
|
* aria-selected attribute.
|
||||||
|
* @param {!chromeos.personalizationApp.mojom.LocalImage} image
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
getAriaSelected_(image) {
|
||||||
|
return 'false';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {chromeos.personalizationApp.mojom.LocalImage} image
|
* @param {chromeos.personalizationApp.mojom.LocalImage} image
|
||||||
@ -172,6 +191,16 @@ export class LocalImages extends WithPersonalizationStore {
|
|||||||
/** @type {!chromeos.personalizationApp.mojom.LocalImage} */ (image),
|
/** @type {!chromeos.personalizationApp.mojom.LocalImage} */ (image),
|
||||||
getWallpaperProvider(), this.getStore());
|
getWallpaperProvider(), this.getStore());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {number} i
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getAriaIndex_(i) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(LocalImages.is, LocalImages);
|
customElements.define(LocalImages.is, LocalImages);
|
||||||
|
@ -4,5 +4,6 @@
|
|||||||
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
||||||
<p hidden$="[[!hasError_]]" id="error">error</p>
|
<p hidden$="[[!hasError_]]" id="error">error</p>
|
||||||
<iframe id="collections-iframe" frameBorder="0" hidden$="[[!showCollections_]]"
|
<iframe id="collections-iframe" frameBorder="0" hidden$="[[!showCollections_]]"
|
||||||
src="chrome-untrusted://personalization/untrusted/collections.html">
|
src="chrome-untrusted://personalization/untrusted/collections.html"
|
||||||
|
role="main" aria-label$="[[i18n('wallpaperCollections')]]">
|
||||||
</iframe>
|
</iframe>
|
||||||
|
@ -6,5 +6,6 @@
|
|||||||
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
<!-- TODO(b/181697575) handle error cases and update error string to UI spec -->
|
||||||
<p hidden$="[[!hasError_]]" id="error">error</p>
|
<p hidden$="[[!hasError_]]" id="error">error</p>
|
||||||
<iframe frameBorder="0" id="images-iframe" hidden$="[[!showImages_]]"
|
<iframe frameBorder="0" id="images-iframe" hidden$="[[!showImages_]]"
|
||||||
src="chrome-untrusted://personalization/untrusted/images.html">
|
src="chrome-untrusted://personalization/untrusted/images.html" role="main"
|
||||||
|
aria-label="[[getMainAriaLabel_(collectionId, collections_)]]">
|
||||||
</iframe>
|
</iframe>
|
||||||
|
@ -44,6 +44,13 @@ export class WallpaperImages extends WithPersonalizationStore {
|
|||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {?Array<!chromeos.personalizationApp.mojom.WallpaperCollection>}
|
||||||
|
*/
|
||||||
|
collections_: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {!Object<string,
|
* @type {!Object<string,
|
||||||
* ?Array<!chromeos.personalizationApp.mojom.WallpaperImage>>}
|
* ?Array<!chromeos.personalizationApp.mojom.WallpaperImage>>}
|
||||||
@ -92,6 +99,7 @@ export class WallpaperImages extends WithPersonalizationStore {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.watch('images_', state => state.backdrop.images);
|
this.watch('images_', state => state.backdrop.images);
|
||||||
this.watch('imagesLoading_', state => state.loading.images);
|
this.watch('imagesLoading_', state => state.loading.images);
|
||||||
|
this.watch('collections_', state => state.backdrop.collections);
|
||||||
this.updateFromStore();
|
this.updateFromStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +153,28 @@ export class WallpaperImages extends WithPersonalizationStore {
|
|||||||
sendImagesFunction(iframe.contentWindow, this.images_[collectionId]);
|
sendImagesFunction(iframe.contentWindow, this.images_[collectionId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {string} collectionId
|
||||||
|
* @param {?Array<!chromeos.personalizationApp.mojom.WallpaperCollection>}
|
||||||
|
* collections
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
getMainAriaLabel_(collectionId, collections) {
|
||||||
|
if (!collectionId || !Array.isArray(collections)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const collection =
|
||||||
|
collections.find(collection => collection.id === collectionId);
|
||||||
|
|
||||||
|
if (!collection) {
|
||||||
|
console.warn('Did not find collection matching collectionId');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(WallpaperImages.is, WallpaperImages);
|
customElements.define(WallpaperImages.is, WallpaperImages);
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<iron-list items="[[tiles_]]" grid scroll-target="document">
|
<iron-list items="[[tiles_]]" grid scroll-target="document" role="listbox"
|
||||||
|
aria-setsize$="[[tiles_.length]]">
|
||||||
<template>
|
<template>
|
||||||
<div class="photo-container" data-id$="[[item.id]]"
|
<div class="photo-container" data-id$="[[item.id]]"
|
||||||
on-click="onCollectionClicked_">
|
on-click="onCollectionClicked_" tabindex$="[[tabIndex]]" role="option"
|
||||||
|
aria-posinset$="[[getAriaIndex_(index)]]" aria-selected="false">
|
||||||
<div class="padding-fix">
|
<div class="padding-fix">
|
||||||
<template is="dom-if" if="[[item.preview]]">
|
<template is="dom-if" if="[[item.preview]]">
|
||||||
<img src="[[item.preview.url]]">
|
<img src="[[item.preview.url]]">
|
||||||
|
@ -232,6 +232,15 @@ class CollectionsGrid extends PolymerElement {
|
|||||||
}
|
}
|
||||||
selectCollection(window.parent, id);
|
selectCollection(window.parent, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {number} i
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getAriaIndex_(i) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(CollectionsGrid.is, CollectionsGrid);
|
customElements.define(CollectionsGrid.is, CollectionsGrid);
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<iron-list scroll-target="document" grid items="[[images_]]">
|
<iron-list scroll-target="document" grid items="[[images_]]" role="listbox"
|
||||||
|
aria-setsize$="[[images_.length]]">
|
||||||
<template>
|
<template>
|
||||||
<div class="photo-container">
|
<div class="photo-container" tabindex$="[[tabIndex]]" role="option"
|
||||||
|
aria-posinset$="[[getAriaIndex_(index)]]"
|
||||||
|
aria-selected$="[[getAriaSelected_(item)]]">
|
||||||
<div class="padding-fix">
|
<div class="padding-fix">
|
||||||
<img data-id$="[[item.assetId]]" src="[[item.url.url]]"
|
<img data-id$="[[item.assetId]]" src="[[item.url.url]]"
|
||||||
on-click="onImageClicked_">
|
on-click="onImageClicked_" alt$="[[getImgAlt_(item)]]">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -68,6 +68,16 @@ class ImagesGrid extends PolymerElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO(b/192975897) compare with currently selected image to return correct
|
||||||
|
* aria-selected attribute.
|
||||||
|
* @param {!chromeos.personalizationApp.mojom.LocalImage} image
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
getAriaSelected_(image) {
|
||||||
|
return 'false';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify trusted code that a user clicked on an image.
|
* Notify trusted code that a user clicked on an image.
|
||||||
* @private
|
* @private
|
||||||
@ -77,6 +87,24 @@ class ImagesGrid extends PolymerElement {
|
|||||||
const img = e.currentTarget;
|
const img = e.currentTarget;
|
||||||
selectImage(window.parent, BigInt(img.dataset.id));
|
selectImage(window.parent, BigInt(img.dataset.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {!chromeos.personalizationApp.mojom.WallpaperImage} image
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
getImgAlt_(image) {
|
||||||
|
return image.attribution.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {number} i
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getAriaIndex_(i) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(ImagesGrid.is, ImagesGrid);
|
customElements.define(ImagesGrid.is, ImagesGrid);
|
||||||
|
Reference in New Issue
Block a user