Commit b54f00f2 authored by Akihiro Ota's avatar Akihiro Ota Committed by Commit Bot

ChromeVox Tutorial: Automatic reading behavior.

This change implements automatic reading behavior for the ChromeVox
tutorial. This change implements the following:

1. When an interactive lesson is shown, read the lesson title. This
is needed because we focus the text content for these lessons.
2. When a non-interactive lesson is shown, read the lesson contents.
3. Refactor requestSpeech() to pass queuemode and properties
for more control over speech behavior.
4. Add @suppresss statements to a few functions. This allows
us to reference the panel window without triggering closure errors.

Automated tests are included confirm bullets 1 and 2.

Fixed: 1129194
Change-Id: I2ce38fc0228bce749033f4effac4b79846dd696f
AX-Relnotes: N/A
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2414776
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812503}
parent 412b99ac
......@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Defines a custom Polymer component for the ChromeVox
* interactive tutorial engine.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
......@@ -570,9 +575,15 @@ Polymer({
// update their visibility.
this.activeLessonNum = this.includedLessons[index].lessonNum;
const lesson = this.includedLessons[this.activeLessonIndex];
const lesson = this.getCurrentLesson();
if (lesson.autoInteractive) {
this.startInteractiveMode(lesson.actions);
// Read the title since initial focus gets placed on the first piece of
// text content.
this.readCurrentLessonTitle();
} else {
// Otherwise, automatically read current lesson content.
setTimeout(this.readCurrentLessonContent.bind(this), 1000);
}
},
......@@ -830,6 +841,8 @@ Polymer({
/**
* @param {NudgeType} type
* @private
* @suppress {undefinedVars|missingProperties} For referencing QueueMode,
* which is defined on the Panel window.
*/
initializeNudges(type) {
const maybeGiveNudge = (msg) => {
......@@ -839,7 +852,7 @@ Polymer({
return;
}
this.requestSpeech(msg);
this.requestSpeech(msg, QueueMode.INTERJECT);
};
this.nudgeArray = [];
......@@ -848,7 +861,8 @@ Polymer({
// strings.
const hints = this.lessonData[this.activeLessonNum].hints;
for (const hint of hints) {
this.nudgeArray.push(this.requestSpeech.bind(this, hint));
this.nudgeArray.push(
this.requestSpeech.bind(this, hint, QueueMode.INTERJECT));
}
} else if (type === NudgeType.GENERAL) {
this.nudgeArray = [
......@@ -860,7 +874,8 @@ Polymer({
maybeGiveNudge.bind(
this, 'Hint: Press Search + Space to activate the current item.'),
this.requestSpeech.bind(
this, 'Hint: Press Escape if you would like to exit this tutorial.')
this, 'Hint: Press Escape if you would like to exit this tutorial.',
QueueMode.INTERJECT)
];
} else {
throw new Error('Invalid NudgeType: ' + type);
......@@ -894,11 +909,16 @@ Polymer({
/**
* @param {string} text
* @param {number} queueMode
* @param {{doNotInterrupt: boolean}=} properties
* @private
* @suppress {undefinedVars|missingProperties} For referencing QueueMode,
* which is defined on the Panel window.
*/
requestSpeech(text) {
this.dispatchEvent(
new CustomEvent('requestspeech', {composed: true, detail: {text}}));
requestSpeech(text, queueMode, properties) {
this.dispatchEvent(new CustomEvent(
'requestspeech',
{composed: true, detail: {text, queueMode, properties}}));
},
/** @private */
......@@ -919,5 +939,34 @@ Polymer({
}
this.restartNudges();
},
/** @return {!TutorialLesson} */
getCurrentLesson() {
return this.includedLessons[this.activeLessonIndex];
},
/**
* @private
* @suppress {undefinedVars|missingProperties} For referencing QueueMode,
* which is defined on the Panel window.
*/
readCurrentLessonTitle() {
const lesson = this.getCurrentLesson();
this.requestSpeech(
lesson.title, QueueMode.INTERJECT, {doNotInterrupt: true});
},
/**
* @private
* @suppress {undefinedVars|missingProperties} For referencing QueueMode,
* which is defined on the Panel window.
*/
readCurrentLessonContent() {
const lesson = this.getCurrentLesson();
for (const text of lesson.content) {
// Queue lesson content so it is read after the lesson title.
this.requestSpeech(text, QueueMode.QUEUE);
}
}
});
......@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Defines a custom Polymer component for a lesson in the
* ChromeVox interactive tutorial.
*/
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
......@@ -232,11 +237,12 @@ export const TutorialLesson = Polymer({
},
/**
* Requests speech from the Panel.
* @param {string} text
* @private
*/
requestSpeech(text) {
// TODO (akihiroota): Migrate this to i_tutorial.js so that the tutorial
// engine controls all speech requests.
this.dispatchEvent(
new CustomEvent('requestspeech', {composed: true, detail: {text}}));
},
......
......@@ -24,6 +24,7 @@ goog.require('Msgs');
goog.require('PanelCommand');
goog.require('PanelMenu');
goog.require('PanelMenuItem');
goog.require('QueueMode');
goog.require('Tutorial');
goog.require('UserAnnotationHandler');
......@@ -1191,11 +1192,24 @@ Panel = class {
Panel.onCloseTutorial();
});
$('i-tutorial').addEventListener('requestspeech', (evt) => {
const text = evt.detail.text;
const background = chrome.extension.getBackgroundPage();
/**
* @type {{
* text: string,
* queueMode: QueueMode,
* properties: ({doNotInterrupt: boolean}|undefined)}}
*/
const detail = evt.detail;
const text = detail.text;
const queueMode = detail.queueMode;
const properties = detail.properties || {};
if (!text || queueMode === undefined) {
throw new Error(
`Must specify text and queueMode when requesting speech from the
tutorial`);
}
const cvox = background['ChromeVox'];
cvox.tts.speak(
text, background.QueueMode.INTERJECT, {'doNotInterrupt': true});
cvox.tts.speak(text, queueMode, properties);
});
$('i-tutorial').addEventListener('startinteractivemode', (evt) => {
const actions = evt.detail.actions;
......
......@@ -395,3 +395,53 @@ TEST_F('ChromeVoxTutorialTest', 'NextPreviousButtons', function() {
.replay();
});
});
// Tests that the title of an interactive lesson is read when shown.
TEST_F('ChromeVoxTutorialTest', 'AutoReadTitle', function() {
const mockFeedback = this.createMockFeedback();
this.runWithLoadedTree(this.simpleDoc, async function(root) {
await this.launchAndWaitForTutorial();
const tutorial = this.getPanel().iTutorial;
mockFeedback.expectSpeech('Choose your tutorial experience')
.call(doCmd('nextObject'))
.expectSpeech('Quick orientation', 'Button')
.call(doCmd('forceClickOnCurrentItem'))
.expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/)
.call(doCmd('nextObject'))
.expectSpeech('Welcome to ChromeVox!', 'Button')
.call(doCmd('forceClickOnCurrentItem'))
.expectSpeech('Welcome to ChromeVox!')
.expectSpeech(
'Welcome to the ChromeVox tutorial. To exit this tutorial at any ' +
'time, press the Escape key on the top left corner of the ' +
'keyboard. To turn off ChromeVox, hold Control and Alt, and ' +
`press Z. When you're ready, use the spacebar to move to the ` +
'next lesson.')
.replay();
});
});
// Tests that the content of a non-interactive lesson is read when shown.
TEST_F('ChromeVoxTutorialTest', 'AutoReadLesson', function() {
const mockFeedback = this.createMockFeedback();
this.runWithLoadedTree(this.simpleDoc, async function(root) {
await this.launchAndWaitForTutorial();
const tutorial = this.getPanel().iTutorial;
mockFeedback.expectSpeech('Choose your tutorial experience')
.call(doCmd('nextObject'))
.expectSpeech('Quick orientation', 'Button')
.call(doCmd('nextObject'))
.expectSpeech('Essential keys', 'Button')
.call(doCmd('forceClickOnCurrentItem'))
.expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/)
.call(() => {
tutorial.showLesson(0);
})
.expectSpeech('On, Off, and Stop', 'Heading 1')
.expectSpeech(
'To temporarily stop ChromeVox from speaking, ' +
'press the Control key.')
.expectSpeech('To turn ChromeVox on or off, use Control+Alt+Z.')
.replay();
});
});
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