Commit 4cdf84ed authored by Luciano Pacheco's avatar Luciano Pacheco Committed by Commit Bot

Add spoken feedback for opening a file

Add two types of messages to announce to screen reader/Chromevox when
a file or multiple files are open.

Add a new 0px-wide DIV to not be shown in the UI but still be accepted
by screen reader, if we set display:none screen reader ignores it. This
DIV has aria-live="polite" which makes screen reader read it whenever
its content is updated, the "polite" part means that it will wait for
any current content being read before announcing this text.

Add a new method on FileManagerUI |speakA11yMesage| to change the
content of the DIV above so the text is announced via screen reader.
When IN_TEST, this also stores the text so it can be checked via in the
tests. Add a test helper function to return all messages announced via
this new method.

Add a check for the A11y message on Open Image test.

Test: browser_test --gtest_filter="OpenImageFiles/FilesAppBrowserTest.Test/imageOpenGalleryOpenDownloads"
Bug: 428530
Tbr: aboxhall@chromium.org
Change-Id: I713300a828912722c4fbe795f0505ed7db371fe0
Reviewed-on: https://chromium-review.googlesource.com/c/1347956
Commit-Queue: Luciano Pacheco <lucmult@chromium.org>
Reviewed-by: default avatarNoel Gordon <noel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610522}
parent a7686bfe
...@@ -1262,6 +1262,12 @@ ...@@ -1262,6 +1262,12 @@
<message name="IDS_FILE_BROWSER_SEE_MENU_FOR_ACTIONS" desc="Text to be used by screen reader to indicate users that there are more options on the action bar."> <message name="IDS_FILE_BROWSER_SEE_MENU_FOR_ACTIONS" desc="Text to be used by screen reader to indicate users that there are more options on the action bar.">
More options available on the action bar. Press Alt + A to focus the action bar. More options available on the action bar. Press Alt + A to focus the action bar.
</message> </message>
<message name="IDS_FILE_BROWSER_OPEN_A11Y" desc="Text to be used by screen reader to indicate users that the opening one single file is being executed.">
Opening file <ph name="FILE_NAME"> $1<ex>file_name.txt</ex>.</ph>
</message>
<message name="IDS_FILE_BROWSER_OPEN_A11Y_PLURAL" desc="Text to be used by screen reader to indicate users that the opening multiple files is being executed.">
Opening <ph name="NUMBER_FILES"> $1<ex>3</ex> files.</ph>
</message>
<!-- Common for Audio player and Media player --> <!-- Common for Audio player and Media player -->
<message name="IDS_MEDIA_PLAYER_PLAY_BUTTON_LABEL" desc="Label for the Play button of media players (audio player / video player)."> <message name="IDS_MEDIA_PLAYER_PLAY_BUTTON_LABEL" desc="Label for the Play button of media players (audio player / video player).">
......
bcb5583ecbfa833378aeec26d2d2f64bebceab88
\ No newline at end of file
b718276b1086c00020d41a8dbd8be6ce1a8b5f7b
\ No newline at end of file
...@@ -828,6 +828,8 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() { ...@@ -828,6 +828,8 @@ ExtensionFunction::ResponseAction FileManagerPrivateGetStringsFunction::Run() {
IDS_FILE_BROWSER_ZIP_TARGET_EXISTS_ERROR); IDS_FILE_BROWSER_ZIP_TARGET_EXISTS_ERROR);
SET_STRING("ZIP_UNEXPECTED_ERROR", IDS_FILE_BROWSER_ZIP_UNEXPECTED_ERROR); SET_STRING("ZIP_UNEXPECTED_ERROR", IDS_FILE_BROWSER_ZIP_UNEXPECTED_ERROR);
SET_STRING("SEE_MENU_FOR_ACTIONS", IDS_FILE_BROWSER_SEE_MENU_FOR_ACTIONS); SET_STRING("SEE_MENU_FOR_ACTIONS", IDS_FILE_BROWSER_SEE_MENU_FOR_ACTIONS);
SET_STRING("OPEN_A11Y", IDS_FILE_BROWSER_OPEN_A11Y);
SET_STRING("OPEN_A11Y_PLURAL", IDS_FILE_BROWSER_OPEN_A11Y_PLURAL);
SET_STRING("FILEMANAGER_APP_NAME", IDS_FILEMANAGER_APP_NAME); SET_STRING("FILEMANAGER_APP_NAME", IDS_FILEMANAGER_APP_NAME);
#undef SET_STRING #undef SET_STRING
......
...@@ -679,6 +679,19 @@ test.util.sync.isFileManagerLoaded = function(contentWindow) { ...@@ -679,6 +679,19 @@ test.util.sync.isFileManagerLoaded = function(contentWindow) {
return false; return false;
}; };
/**
* Returns all a11y messages announced by |FileManagerUI.speakA11yMessage|.
*
* @return {Array<string>}
*/
test.util.sync.getA11yAnnounces = function(contentWindow) {
if (contentWindow && contentWindow.fileManager &&
contentWindow.fileManager.ui)
return contentWindow.fileManager.ui.a11yAnnounces;
return null;
};
/** /**
* Reports to the given |callback| the number of volumes available in * Reports to the given |callback| the number of volumes available in
* VolumeManager in the background page. * VolumeManager in the background page.
......
...@@ -814,6 +814,13 @@ FileTasks.prototype.executeInternal_ = function(task) { ...@@ -814,6 +814,13 @@ FileTasks.prototype.executeInternal_ = function(task) {
this.checkAvailability_(() => { this.checkAvailability_(() => {
this.maybeShareWithCrostiniOrShowDialog_(task, () => { this.maybeShareWithCrostiniOrShowDialog_(task, () => {
this.taskHistory_.recordTaskExecuted(task.taskId); this.taskHistory_.recordTaskExecuted(task.taskId);
let msg;
if (this.entries.length === 1) {
msg = strf('OPEN_A11Y', this.entries_[0].name);
} else {
msg = strf('OPEN_A11Y_PLURAL', this.entries_.length);
}
this.ui_.speakA11yMessage(msg);
if (FileTasks.isInternalTask_(task.taskId)) { if (FileTasks.isInternalTask_(task.taskId)) {
this.executeInternalTask_(task.taskId); this.executeInternalTask_(task.taskId);
} else { } else {
......
...@@ -14,23 +14,12 @@ var mockTaskHistory = { ...@@ -14,23 +14,12 @@ var mockTaskHistory = {
recordTaskExecuted: function(id) {} recordTaskExecuted: function(id) {}
}; };
loadTimeData.data = { function setUp() {
window.loadTimeData.data = {
DRIVE_FS_ENABLED: false, DRIVE_FS_ENABLED: false,
MORE_ACTIONS_BUTTON_LABEL: 'MORE_ACTIONS_BUTTON_LABEL', };
NO_TASK_FOR_EXECUTABLE: 'NO_TASK_FOR_EXECUTABLE', window.loadTimeData.getString = id => id;
NO_TASK_FOR_FILE_URL: 'NO_TASK_FOR_FILE_URL',
NO_TASK_FOR_FILE: 'NO_TASK_FOR_FILE',
NO_TASK_FOR_DMG: 'NO_TASK_FOR_DMG',
NO_TASK_FOR_CRX: 'NO_TASK_FOR_CRX',
NO_TASK_FOR_CRX_TITLE: 'NO_TASK_FOR_CRX_TITLE',
OPEN_WITH_BUTTON_LABEL: 'OPEN_WITH_BUTTON_LABEL',
TASK_INSTALL_LINUX_PACKAGE: 'TASK_INSTALL_LINUX_PACKAGE',
TASK_OPEN: 'TASK_OPEN',
UNABLE_TO_OPEN_CROSTINI_TITLE: 'UNABLE_TO_OPEN_CROSTINI_TITLE',
UNABLE_TO_OPEN_CROSTINI: 'UNABLE_TO_OPEN_CROSTINI',
};
function setUp() {
window.chrome = { window.chrome = {
commandLinePrivate: { commandLinePrivate: {
hasSwitch: function(name, callback) { hasSwitch: function(name, callback) {
...@@ -78,7 +67,8 @@ function getMockFileManager() { ...@@ -78,7 +67,8 @@ function getMockFileManager() {
} }
}, },
ui: { ui: {
alertDialog: {showHtml: function(title, text, onOk, onCancel, onShow) {}} alertDialog: {showHtml: function(title, text, onOk, onCancel, onShow) {}},
speakA11yMessage: (text) => {},
}, },
metadataModel: {}, metadataModel: {},
directoryModel: { directoryModel: {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
<command id="more-actions"> <command id="more-actions">
<script src="../../../../../ui/webui/resources/js/assert.js"></script> <script src="../../../../../ui/webui/resources/js/assert.js"></script>
<script src="../../../../../ui/webui/resources/js/load_time_data.js"></script>
<script src="../../../../../ui/webui/resources/js/cr.js"></script> <script src="../../../../../ui/webui/resources/js/cr.js"></script>
<script src="../../../../../ui/webui/resources/js/cr/event_target.js"></script> <script src="../../../../../ui/webui/resources/js/cr/event_target.js"></script>
<script src="../../../../../ui/webui/resources/js/cr/ui.js"></script> <script src="../../../../../ui/webui/resources/js/cr/ui.js"></script>
......
...@@ -2,17 +2,14 @@ ...@@ -2,17 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
window.loadTimeData = {
getBoolean: function(key) {
return false;
}
};
window.metrics = { window.metrics = {
recordEnum: function() {} recordEnum: function() {}
}; };
function setUp() { function setUp() {
window.loadTimeData.getBoolean = key => false;
window.loadTimeData.getString = id => id;
// Behavior of window.chrome depends on each test case. window.chrome should // Behavior of window.chrome depends on each test case. window.chrome should
// be initialized properly inside each test function. // be initialized properly inside each test function.
window.chrome = { window.chrome = {
...@@ -80,7 +77,8 @@ function testExecuteEntryTask(callback) { ...@@ -80,7 +77,8 @@ function testExecuteEntryTask(callback) {
{ {
taskMenuButton: document.createElement('button'), taskMenuButton: document.createElement('button'),
shareMenuButton: {menu: document.createElement('div')}, shareMenuButton: {menu: document.createElement('div')},
fileContextMenu: {defaultActionMenuItem: document.createElement('div')} fileContextMenu: {defaultActionMenuItem: document.createElement('div')},
speakA11yMessage: text => {},
}, },
new MockMetadataModel({}), { new MockMetadataModel({}), {
getCurrentRootType: function() { getCurrentRootType: function() {
......
...@@ -354,6 +354,21 @@ function FileManagerUI(providersModel, element, launchParam) { ...@@ -354,6 +354,21 @@ function FileManagerUI(providersModel, element, launchParam) {
this.toast = this.toast =
/** @type {!FilesToast} */ (document.querySelector('files-toast')); /** @type {!FilesToast} */ (document.querySelector('files-toast'));
/**
* A hidden div that can be used to announce text to screen reader/ChromeVox.
* @private {!HTMLElement}
*/
this.a11yMessage_ = queryRequiredElement('#a11y-msg', this.element);
if (window.IN_TEST) {
/**
* Stores all a11y announces to be checked in tests.
* @public {Array<string>}
*/
this.a11yAnnounces = [];
}
// Initialize attributes. // Initialize attributes.
this.element.setAttribute('type', this.dialogType_); this.element.setAttribute('type', this.dialogType_);
...@@ -639,3 +654,17 @@ FileManagerUI.prototype.showConfirmationDialog = function(isMove, messages) { ...@@ -639,3 +654,17 @@ FileManagerUI.prototype.showConfirmationDialog = function(isMove, messages) {
}); });
}); });
}; };
/**
* Send a text to screen reader/Chromevox without displaying the text in the UI.
* @param {string} text Text to be announced by screen reader, which should be
* already translated.
*/
FileManagerUI.prototype.speakA11yMessage = function(text) {
// Screen reader only reads if the content changes, so clear the content
// first.
this.a11yMessage_.textContent = '';
this.a11yMessage_.textContent = text;
if (window.IN_TEST)
this.a11yAnnounces.push(text);
};
...@@ -480,6 +480,7 @@ ...@@ -480,6 +480,7 @@
<div class="splitter" id="navigation-list-splitter"></div> <div class="splitter" id="navigation-list-splitter"></div>
<div class="dialog-main"> <div class="dialog-main">
<div class="dialog-body"> <div class="dialog-body">
<div id="a11y-msg" aria-live="polite" style="display: inline-block; position: fixed; clip: rect(0, 0, 0, 0);"></div>
<div class="main-panel"> <div class="main-panel">
<div class="filelist-panel"> <div class="filelist-panel">
<div class="drive-welcome header"></div> <div class="drive-welcome header"></div>
......
...@@ -76,9 +76,25 @@ function imageOpenGalleryOpen(path) { ...@@ -76,9 +76,25 @@ function imageOpenGalleryOpen(path) {
remoteCall.callRemoteTestUtil( remoteCall.callRemoteTestUtil(
'openFile', appId, [ENTRIES.image3.targetPath], this.next); 'openFile', appId, [ENTRIES.image3.targetPath], this.next);
}, },
// Check: the Gallery window should open. // Wait a11y-msg to have some text.
function(result) { function(result) {
chrome.test.assertTrue(result); chrome.test.assertTrue(result);
remoteCall.waitForElement(appId, '#a11y-msg:not(:empty)').then(this.next);
},
// Fetch A11y messages.
function() {
remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, [])
.then(this.next);
},
// Check that opening the file was announced to screen reader.
function(a11yMessages) {
chrome.test.assertTrue(a11yMessages instanceof Array);
chrome.test.assertEq(1, a11yMessages.length);
chrome.test.assertEq('Opening file image3.jpg.', a11yMessages[0]);
this.next();
},
// Check: the Gallery window should open.
function() {
galleryApp.waitForWindow('gallery.html').then(this.next); galleryApp.waitForWindow('gallery.html').then(this.next);
}, },
// Check: the image should appear in the Gallery window. // Check: the image should appear in the Gallery window.
......
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