Commit 31fac267 authored by David Tseng's avatar David Tseng Committed by Commit Bot

Do not speak value changes unconditionally

R=akihiroota@chromium.org

AX-Relnotes: fixes an issue where ChromeVox would speak changes in a popup button when its value changes even if it didn't have focus.
Bug: none
Change-Id: Id609af0fb7ee5e99bb9f0c4e0e822ab986db11e4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2468573Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Commit-Queue: David Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#816901}
parent 8c55b4a4
......@@ -3160,3 +3160,60 @@ TEST_F('ChromeVoxBackgroundTest', 'ContainerButtons', function() {
.replay();
});
});
TEST_F('ChromeVoxBackgroundTest', 'FocusOnWebAreaIgnoresEvents', function() {
const site = `
<div role="application" tabindex=0 aria-label="container">
<select>
<option>apple</option>
<option>grape</option>
<option>pear</option>
</select>
</div>
<p>go</p>
<script>
let counter = 0;
document.body.getElementsByTagName('p')[0].addEventListener('click',
e => {
document.body.getElementsByTagName('select')[0].selectedIndex =
++counter % 3;
});
</script>
`;
this.runWithLoadedTree(site, async function(root) {
const application = root.find({role: RoleType.APPLICATION});
const popUpButton = root.find({role: RoleType.POP_UP_BUTTON});
const p = root.find({role: RoleType.PARAGRAPH});
// Move focus to the select, which honors value changes through
// FocusAutomationHandler.
popUpButton.focus();
await TestUtils.waitForSpeech('apple');
// Clicking the paragraph programmatically changes the select value.
p.doDefault();
await TestUtils.waitForSpeech('grape');
assertEquals(
RoleType.POP_UP_BUTTON,
ChromeVoxState.instance.currentRange.start.node.role);
// Now, move focus to the application which is a parent of the select.
application.focus();
await TestUtils.waitForSpeech('container');
// Hook into the speak call, to see what comes next.
let nextSpeech;
ChromeVox.tts.speak = textString => {
nextSpeech = textString;
};
// Trigger another value update for the select.
p.doDefault();
// This comes when the select's value changes.
await TestUtils.waitForEvent(application, EventType.VALUE_CHANGED);
// Nothing should have been spoken.
assertEquals(undefined, nextSpeech);
});
});
......@@ -36,6 +36,13 @@ FocusAutomationHandler = class extends BaseAutomationHandler {
*/
onFocus(evt) {
this.removeAllListeners();
// Events on roots and web views can be very noisy due to bubling. Ignore
// these.
if (evt.target.root == evt.target || evt.target.role == RoleType.WEB_VIEW) {
return;
}
this.previousActiveDescendant_ = evt.target.activeDescendant;
this.node_ = evt.target;
this.addListener_(
......@@ -98,6 +105,11 @@ FocusAutomationHandler = class extends BaseAutomationHandler {
return;
}
// Focus might be on a container above the popup button.
if (this.node_ != evt.target) {
return;
}
// If it has children, that means a menu is showing.
if (evt.target.firstChild) {
return;
......
......@@ -66,8 +66,39 @@ class TestUtils {
keyEvent.stopPropagation = _ => {};
return keyEvent;
}
}
/**
* Returns a promise which gets resolved when ChromeVox speaks the given
* string.
* @param {string} textStringToWaitFor
* @return {!Promise}
*/
static waitForSpeech(textStringToWaitFor) {
return new Promise(resolve => {
ChromeVox.tts.speak = (textString) => {
if (textString == textStringToWaitFor) {
resolve();
}
};
});
}
/**
* Waits for the specified event on the given node.
* @param {!chrome.automation.AutomationNode} node
* @param {chrome.automation.EventType} eventType
* @return {!Promise}
*/
static waitForEvent(node, eventType) {
return new Promise(resolve => {
const listener = () => {
node.removeEventListener(eventType, listener);
resolve();
};
node.addEventListener(eventType, listener);
});
}
}
/**
* Similar to |TEST_F|. Generates a test for the given |testFixture|,
......
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