Commit ea95f1f2 authored by Josiah K's avatar Josiah K Committed by Commit Bot

Add Fullscreen Magnifier support for active-descendant focus-following

Plumb through active-descendant focus-following for full-screen magnifier in Chrome OS.

Bug: 1131153
Change-Id: I8bc79096768449a231221cd8f354f80c966033a9
AX-Relnotes: Adds Magnifier support for active-descendant focus-following
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2440763
Commit-Queue: Josiah Krutz <josiahk@google.com>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#816468}
parent e3001d1f
...@@ -60,6 +60,7 @@ js2gtest("accessibility_common_extjs_tests") { ...@@ -60,6 +60,7 @@ js2gtest("accessibility_common_extjs_tests") {
sources = [ sources = [
"accessibility_common_test.js", "accessibility_common_test.js",
"autoclick/autoclick_test.js", "autoclick/autoclick_test.js",
"magnifier/magnifier_test.js",
] ]
gen_include_files = [ gen_include_files = [
"../common/rect_util.js", "../common/rect_util.js",
...@@ -88,6 +89,7 @@ js_library("accessibility_common") { ...@@ -88,6 +89,7 @@ js_library("accessibility_common") {
deps = [ deps = [
":autoclick", ":autoclick",
":magnifier", ":magnifier",
"../common:event_handler",
"../common:instance_checker", "../common:instance_checker",
] ]
externs_list = [ externs_list = [
......
...@@ -71,6 +71,7 @@ class AccessibilityCommon { ...@@ -71,6 +71,7 @@ class AccessibilityCommon {
if (details.value && !this.magnifier_) { if (details.value && !this.magnifier_) {
this.magnifier_ = new Magnifier(); this.magnifier_ = new Magnifier();
} else if (!details.value && this.magnifier_) { } else if (!details.value && this.magnifier_) {
this.magnifier_.onMagnifierDisabled();
this.magnifier_ = null; this.magnifier_ = null;
} }
} }
......
...@@ -6,5 +6,48 @@ ...@@ -6,5 +6,48 @@
* Main class for the Chrome OS magnifier. * Main class for the Chrome OS magnifier.
*/ */
class Magnifier { class Magnifier {
constructor() {} constructor() {
/** @private {!EventHandler} */
this.activeDescendantHandler_ = new EventHandler(
[], chrome.automation.EventType.ACTIVE_DESCENDANT_CHANGED,
this.onActiveDescendantChanged_.bind(this));
this.init_();
}
/** Destructor to remove listener. */
onMagnifierDisabled() {
this.activeDescendantHandler_.stop();
}
/**
* Initializes Magnifier.
* @private
*/
init_() {
chrome.automation.getDesktop(desktop => {
this.activeDescendantHandler_.setNodes(desktop);
this.activeDescendantHandler_.start();
});
}
/**
* Listener for when active descendant is changed. Moves magnifier to include
* active descendant in viewport.
* @param {!chrome.automation.AutomationEvent} event
* @private
*/
onActiveDescendantChanged_(event) {
const {activeDescendant} = event.target;
if (!activeDescendant) {
return;
}
const {location} = activeDescendant;
if (!location) {
return;
}
chrome.accessibilityPrivate.moveMagnifierToRect(location);
}
} }
// 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.
GEN_INCLUDE(['../../common/testing/e2e_test_base.js']);
GEN_INCLUDE(['../../common/testing/mock_accessibility_private.js']);
GEN_INCLUDE(['../../common/rect_util.js']);
/**
* Magnifier feature using accessibility common extension browser tests.
*/
MagnifierE2ETest = class extends E2ETestBase {
constructor() {
super();
this.mockAccessibilityPrivate = MockAccessibilityPrivate;
chrome.accessibilityPrivate = this.mockAccessibilityPrivate;
window.RoleType = chrome.automation.RoleType;
// Re-initialize AccessibilityCommon with mock AccessibilityPrivate API.
window.accessibilityCommon = new AccessibilityCommon();
}
/** @override */
testGenCppIncludes() {
super.testGenCppIncludes();
GEN(`
#include "chrome/browser/chromeos/accessibility/magnification_manager.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/test/browser_test.h"
`);
}
/** @override */
testGenPreamble() {
super.testGenPreamble();
GEN(`
base::Closure load_cb =
base::Bind(&chromeos::MagnificationManager::SetMagnifierEnabled,
base::Unretained(chromeos::MagnificationManager::Get()),
true);
WaitForExtension(extension_misc::kAccessibilityCommonExtensionId, load_cb);
`);
}
};
TEST_F(
'MagnifierE2ETest', 'MovesScreenMagnifierToActiveDescendant', function() {
const site = `
<div role="group" id="parent" aria-activedescendant="apple">
<div id="apple" role="treeitem">Apple</div>
<div id="banana" role="treeitem">Banana</div>
</div>
<script>
const parent = document.getElementById('parent');
parent.addEventListener('click', function() {
parent.setAttribute('aria-activedescendant', 'banana');
});
</script>
`;
this.runWithLoadedTree(site, async function(root) {
// Click parent to change active descendant from apple to banana.
const parent = root.find({role: RoleType.GROUP});
parent.doDefault();
// Register and wait for rect from magnifier.
const rect = await new Promise(resolve => {
this.mockAccessibilityPrivate.registerMoveMagnifierToRectCallback(
resolve);
});
// Validate rect from magnifier is rect of banana.
const bananaNode =
root.find({role: RoleType.TREE_ITEM, attributes: {name: 'Banana'}});
assertTrue(RectUtil.equal(rect, bananaNode.location));
}, {returnPage: true});
});
...@@ -19,6 +19,7 @@ var MockAccessibilityPrivate = { ...@@ -19,6 +19,7 @@ var MockAccessibilityPrivate = {
/** @private {!Array<!chrome.accessibilityPrivate.ScreenRect>} */ /** @private {!Array<!chrome.accessibilityPrivate.ScreenRect>} */
focusRingRects_: [], focusRingRects_: [],
handleScrollableBoundsForPointFoundCallback_: null, handleScrollableBoundsForPointFoundCallback_: null,
moveMagnifierToRectCallback_: null,
// Methods from AccessibilityPrivate API. // // Methods from AccessibilityPrivate API. //
...@@ -28,15 +29,15 @@ var MockAccessibilityPrivate = { ...@@ -28,15 +29,15 @@ var MockAccessibilityPrivate = {
* @param {function<number, number>} listener * @param {function<number, number>} listener
*/ */
addListener: (listener) => { addListener: (listener) => {
boundsListener_ = listener; MockAccessibilityPrivate.boundsListener_ = listener;
}, },
/** /**
* Removes the listener. * Removes the listener.
*/ */
removeListener: (listener) => { removeListener: (listener) => {
if (boundsListener_ == listener) { if (MockAccessibilityPrivate.boundsListener_ == listener) {
boundsListener_ = null; MockAccessibilityPrivate.boundsListener_ = null;
} }
} }
}, },
...@@ -46,8 +47,17 @@ var MockAccessibilityPrivate = { ...@@ -46,8 +47,17 @@ var MockAccessibilityPrivate = {
* @param {!chrome.accessibilityPrivate.ScreenRect} bounds * @param {!chrome.accessibilityPrivate.ScreenRect} bounds
*/ */
handleScrollableBoundsForPointFound: (bounds) => { handleScrollableBoundsForPointFound: (bounds) => {
scrollableBounds_ = bounds; MockAccessibilityPrivate.scrollableBounds_ = bounds;
handleScrollableBoundsForPointFoundCallback_(); MockAccessibilityPrivate.handleScrollableBoundsForPointFoundCallback_();
},
/**
* Called when AccessibilityCommon wants to move the magnifier viewport to
* include a specific rect.
* @param {!chrome.accessibilityPrivate.ScreenRect} rect
*/
moveMagnifierToRect: (rect) => {
MockAccessibilityPrivate.moveMagnifierToRectCallback_(rect);
}, },
/** /**
...@@ -57,7 +67,7 @@ var MockAccessibilityPrivate = { ...@@ -57,7 +67,7 @@ var MockAccessibilityPrivate = {
* @param {!Array<!FocusRingInfo>} focusRingInfos * @param {!Array<!FocusRingInfo>} focusRingInfos
*/ */
setFocusRings: (focusRingInfos) => { setFocusRings: (focusRingInfos) => {
focusRingRects_ = focusRingInfos[0].rects; MockAccessibilityPrivate.focusRingRects_ = focusRingInfos[0].rects;
}, },
// Methods for testing. // // Methods for testing. //
...@@ -75,18 +85,29 @@ var MockAccessibilityPrivate = { ...@@ -75,18 +85,29 @@ var MockAccessibilityPrivate = {
*/ */
callOnScrollableBoundsForPointRequested: callOnScrollableBoundsForPointRequested:
(x, y, handleScrollableBoundsForPointFoundCallback) => { (x, y, handleScrollableBoundsForPointFoundCallback) => {
handleScrollableBoundsForPointFoundCallback_ = MockAccessibilityPrivate.handleScrollableBoundsForPointFoundCallback_ =
handleScrollableBoundsForPointFoundCallback; handleScrollableBoundsForPointFoundCallback;
boundsListener_(x, y); MockAccessibilityPrivate.boundsListener_(x, y);
}, },
/**
* Called to register a stubbed callback for moveMagnifierToRect.
* When magnifier identifies a desired rect to move the viewport to,
* moveMagnifierToRectCallback will be called with that desired rect.
* @param {!function<>} moveMagnifierToRectCallback
*/
registerMoveMagnifierToRectCallback: (moveMagnifierToRectCallback) => {
MockAccessibilityPrivate.moveMagnifierToRectCallback_ =
moveMagnifierToRectCallback;
},
/** /**
* Gets the scrollable bounds which were found by the AccessibilityCommon * Gets the scrollable bounds which were found by the AccessibilityCommon
* extension. * extension.
* @return {Array<!chrome.AccessibilityPrivate.ScreenRect>} * @return {Array<!chrome.AccessibilityPrivate.ScreenRect>}
*/ */
getScrollableBounds: () => { getScrollableBounds: () => {
return scrollableBounds_; return MockAccessibilityPrivate.scrollableBounds_;
}, },
/** /**
...@@ -95,6 +116,6 @@ var MockAccessibilityPrivate = { ...@@ -95,6 +116,6 @@ var MockAccessibilityPrivate = {
* @return {Array<!chrome.AccessibilityPrivate.ScreenRect>} * @return {Array<!chrome.AccessibilityPrivate.ScreenRect>}
*/ */
getFocusRings: () => { getFocusRings: () => {
return focusRingRects_; return MockAccessibilityPrivate.focusRingRects_;
}, },
}; };
\ No newline at end of file
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