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") {
sources = [
"accessibility_common_test.js",
"autoclick/autoclick_test.js",
"magnifier/magnifier_test.js",
]
gen_include_files = [
"../common/rect_util.js",
......@@ -88,6 +89,7 @@ js_library("accessibility_common") {
deps = [
":autoclick",
":magnifier",
"../common:event_handler",
"../common:instance_checker",
]
externs_list = [
......
......@@ -71,6 +71,7 @@ class AccessibilityCommon {
if (details.value && !this.magnifier_) {
this.magnifier_ = new Magnifier();
} else if (!details.value && this.magnifier_) {
this.magnifier_.onMagnifierDisabled();
this.magnifier_ = null;
}
}
......
......@@ -6,5 +6,48 @@
* Main class for the Chrome OS 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 = {
/** @private {!Array<!chrome.accessibilityPrivate.ScreenRect>} */
focusRingRects_: [],
handleScrollableBoundsForPointFoundCallback_: null,
moveMagnifierToRectCallback_: null,
// Methods from AccessibilityPrivate API. //
......@@ -28,15 +29,15 @@ var MockAccessibilityPrivate = {
* @param {function<number, number>} listener
*/
addListener: (listener) => {
boundsListener_ = listener;
MockAccessibilityPrivate.boundsListener_ = listener;
},
/**
* Removes the listener.
*/
removeListener: (listener) => {
if (boundsListener_ == listener) {
boundsListener_ = null;
if (MockAccessibilityPrivate.boundsListener_ == listener) {
MockAccessibilityPrivate.boundsListener_ = null;
}
}
},
......@@ -46,8 +47,17 @@ var MockAccessibilityPrivate = {
* @param {!chrome.accessibilityPrivate.ScreenRect} bounds
*/
handleScrollableBoundsForPointFound: (bounds) => {
scrollableBounds_ = bounds;
handleScrollableBoundsForPointFoundCallback_();
MockAccessibilityPrivate.scrollableBounds_ = bounds;
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 = {
* @param {!Array<!FocusRingInfo>} focusRingInfos
*/
setFocusRings: (focusRingInfos) => {
focusRingRects_ = focusRingInfos[0].rects;
MockAccessibilityPrivate.focusRingRects_ = focusRingInfos[0].rects;
},
// Methods for testing. //
......@@ -75,18 +85,29 @@ var MockAccessibilityPrivate = {
*/
callOnScrollableBoundsForPointRequested:
(x, y, handleScrollableBoundsForPointFoundCallback) => {
handleScrollableBoundsForPointFoundCallback_ =
MockAccessibilityPrivate.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
* extension.
* @return {Array<!chrome.AccessibilityPrivate.ScreenRect>}
*/
getScrollableBounds: () => {
return scrollableBounds_;
return MockAccessibilityPrivate.scrollableBounds_;
},
/**
......@@ -95,6 +116,6 @@ var MockAccessibilityPrivate = {
* @return {Array<!chrome.AccessibilityPrivate.ScreenRect>}
*/
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