Commit f390ca89 authored by David Tseng's avatar David Tseng Committed by Commit Bot

Fixes some corner cases in smart sticky mode

An example of unexpected cases include:
- editable address bar. Whenever a user types, ChromeVox shifts focus over to the auto completions list. We want to have sticky mode continue to be off because further typing will actually continue entering text into the text field
- within content editables, ChromeVox may place range on inline text boxes. These nodes are not themselves editable

RELNOTES=n/a

Change-Id: Iaafc60f978d76239a91284ee99aa773177c944e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2138235
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757433}
parent eaa5bd83
......@@ -400,6 +400,7 @@ if (is_chromeos) {
"background/logging/log_store_test.js",
"background/output_test.js",
"background/recovery_strategy_test.js",
"background/smart_sticky_mode_test.js",
"braille/braille_table_test.js",
"braille/braille_translator_manager_test.js",
"braille/liblouis_test.js",
......
......@@ -150,3 +150,13 @@ ChromeVoxState.observers = [];
ChromeVoxState.addObserver = function(observer) {
ChromeVoxState.observers.push(observer);
};
/**
* @param {ChromeVoxStateObserver} observer
*/
ChromeVoxState.removeObserver = function(observer) {
const index = ChromeVoxState.observers.indexOf(observer);
if (index > -1) {
ChromeVoxState.observers.splice(index, 1);
}
};
......@@ -35,16 +35,42 @@ SmartStickyMode = class {
return;
}
const isRangeEditable =
newRange.start.node.state[chrome.automation.StateType.EDITABLE];
// Several cases arise which may lead to a sticky mode toggle:
// The node is either editable itself or a descendant of an editable.
// The node is a relation target of an editable.
const node = newRange.start.node;
let shouldTurnOffStickyMode = false;
if (node.state[chrome.automation.StateType.EDITABLE] ||
(node.parent &&
node.parent.state[chrome.automation.StateType.EDITABLE])) {
// This covers both editable nodes, and inline text boxes (which are not
// editable themselves, but may have an editable parent).
shouldTurnOffStickyMode = true;
} else {
let focus = node;
while (!shouldTurnOffStickyMode && focus) {
if (focus.activeDescendantFor && focus.activeDescendantFor.length) {
shouldTurnOffStickyMode |= focus.activeDescendantFor.some(
(n) => n.state[chrome.automation.StateType.EDITABLE]);
}
// This toggler should not make any changes when the range isn't editable
// and we haven't previously tracked any sticky mode state from the user.
if (!isRangeEditable && !this.didTurnOffStickyMode_) {
if (focus.controlledBy && focus.controlledBy.length) {
shouldTurnOffStickyMode |= focus.controlledBy.some(
(n) => n.state[chrome.automation.StateType.EDITABLE]);
}
focus = focus.parent;
}
}
// This toggler should not make any changes when the range isn't what we're
// lloking for and we haven't previously tracked any sticky mode state from
// the user.
if (!shouldTurnOffStickyMode && !this.didTurnOffStickyMode_) {
return;
}
if (isRangeEditable) {
if (shouldTurnOffStickyMode) {
if (!ChromeVox.isStickyPrefOn) {
// Sticky mode was already off; do not track the current sticky state
// since we may have set it ourselves.
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Include test fixture.
GEN_INCLUDE([
'../testing/chromevox_next_e2e_test_base.js', '../testing/assert_additions.js'
]);
/**
* Test fixture for SmartStickyMode.
*/
ChromeVoxSmartStickyModeTest = class extends ChromeVoxNextE2ETest {};
TEST_F('ChromeVoxSmartStickyModeTest', 'PossibleRangeTypes', function() {
this.runWithLoadedTree(
`
<p>start</p>
<input aria-controls="controls-target" type="text"></input>
<textarea aria-activedescendant="active-descendant-target"></textarea>
<div contenteditable><h3>hello</h3></div>
<ul id="controls-target"><li>end</ul>
<ul id="active-descendant-target"><li>end</ul>
`,
function(root) {
const ssm = new SmartStickyMode();
// Deregister from actual range changes.
ChromeVoxState.removeObserver(ssm);
assertFalse(ssm.didTurnOffStickyMode_);
const [p, input, textarea, contenteditable, ul1, ul2] = root.children;
function assertDidTurnOffForNode(node) {
ssm.onCurrentRangeChanged(cursors.Range.fromNode(node));
assertTrue(ssm.didTurnOffStickyMode_);
}
function assertDidNotTurnOffForNode(node) {
ssm.onCurrentRangeChanged(cursors.Range.fromNode(node));
assertFalse(ssm.didTurnOffStickyMode_);
}
// First, turn on sticky mode and try changing range to various parts of
// the document.
ChromeVoxBackground.setPref(
'sticky', true /* value */, true /* announce */);
assertDidTurnOffForNode(input);
assertDidTurnOffForNode(textarea);
assertDidNotTurnOffForNode(p);
assertDidTurnOffForNode(contenteditable);
assertDidTurnOffForNode(ul1);
assertDidNotTurnOffForNode(p);
assertDidTurnOffForNode(ul2);
assertDidTurnOffForNode(ul1.firstChild);
assertDidNotTurnOffForNode(p);
assertDidTurnOffForNode(ul2.firstChild);
assertDidNotTurnOffForNode(p);
assertDidTurnOffForNode(contenteditable.find({role: 'heading'}));
assertDidTurnOffForNode(contenteditable.find({role: 'inlineTextBox'}));
});
});
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