Commit 9f968ecf authored by W. James MacLean's avatar W. James MacLean Committed by Commit Bot

Make Talkback work for OOPIFs on Android

This CL fixes RenderAccessibilityImpl::OnHitTest for OOPIFs by
having the point under hit testing transform if it's going to be
passed to an OOPIF. While the transformation used in this CL isn't
fully general, it will fix the majority of cases.

Bug: 977381
Change-Id: Ie2f3bfb9334c6f4a3f38680f2b8de887620fe26c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1687562Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Commit-Queue: James MacLean <wjmaclean@chromium.org>
Cr-Commit-Position: refs/heads/master@{#675639}
parent ccd95a49
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "content/public/test/test_utils.h" #include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h" #include "content/shell/browser/shell.h"
#include "content/test/accessibility_browser_test_utils.h" #include "content/test/accessibility_browser_test_utils.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h" #include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -25,7 +26,6 @@ class AccessibilityHitTestingBrowserTest : public ContentBrowserTest { ...@@ -25,7 +26,6 @@ class AccessibilityHitTestingBrowserTest : public ContentBrowserTest {
AccessibilityHitTestingBrowserTest() {} AccessibilityHitTestingBrowserTest() {}
~AccessibilityHitTestingBrowserTest() override {} ~AccessibilityHitTestingBrowserTest() override {}
protected:
BrowserAccessibility* HitTestAndWaitForResultWithEvent( BrowserAccessibility* HitTestAndWaitForResultWithEvent(
const gfx::Point& point, const gfx::Point& point,
ax::mojom::Event event_to_fire) { ax::mojom::Event event_to_fire) {
...@@ -104,6 +104,23 @@ class AccessibilityHitTestingBrowserTest : public ContentBrowserTest { ...@@ -104,6 +104,23 @@ class AccessibilityHitTestingBrowserTest : public ContentBrowserTest {
} }
}; };
class AccessibilityHitTestingCrossProcessBrowserTest
: public AccessibilityHitTestingBrowserTest {
public:
AccessibilityHitTestingCrossProcessBrowserTest() {}
~AccessibilityHitTestingCrossProcessBrowserTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
IsolateAllSitesForTesting(command_line);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
};
IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingBrowserTest, IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingBrowserTest,
HitTestOutsideDocumentBoundsReturnsRoot) { HitTestOutsideDocumentBoundsReturnsRoot) {
NavigateToURL(shell(), GURL(url::kAboutBlankURL)); NavigateToURL(shell(), GURL(url::kAboutBlankURL));
...@@ -200,6 +217,151 @@ IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingBrowserTest, ...@@ -200,6 +217,151 @@ IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingBrowserTest,
ASSERT_EQ(ax::mojom::Role::kGenericContainer, hit_node->GetRole()); ASSERT_EQ(ax::mojom::Role::kGenericContainer, hit_node->GetRole());
} }
IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingCrossProcessBrowserTest,
HitTestingInCrossProcessIframes) {
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/accessibility/hit_testing/hit_testing_a.html"));
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/accessibility/hit_testing/hit_testing_b.html"));
GURL url_c(embedded_test_server()->GetURL(
"c.com", "/accessibility/hit_testing/hit_testing_c.html"));
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
AccessibilityNotificationWaiter waiter(shell()->web_contents(),
ui::kAXModeComplete,
ax::mojom::Event::kLoadComplete);
EXPECT_TRUE(NavigateToURL(shell(), url_a));
waiter.WaitForNotification();
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Button A");
auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
NavigateFrameToURL(child, url_b);
EXPECT_EQ(url_b, child->current_url());
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Button B");
ASSERT_EQ(1U, child->child_count());
FrameTreeNode* grand_child = child->child_at(0);
NavigateFrameToURL(grand_child, url_c);
EXPECT_EQ(url_c, grand_child->current_url());
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Button C");
FrameTreeVisualizer visualizer;
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://a.com/\n"
" B = http://b.com/\n"
" C = http://c.com/",
visualizer.DepictFrameTree(root));
{
// (26, 26) -> "Button A"
BrowserAccessibility* hit_node;
hit_node = HitTestAndWaitForResult(gfx::Point(26, 26));
ASSERT_TRUE(hit_node != nullptr);
ASSERT_EQ(ax::mojom::Role::kButton, hit_node->GetRole());
ASSERT_EQ("Button A",
hit_node->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
{
// (26, 176) -> "Button B"
// 176 = height of div in parent (150), plus button offset (26).
BrowserAccessibility* hit_node;
hit_node = HitTestAndWaitForResult(gfx::Point(26, 176));
ASSERT_TRUE(hit_node != nullptr);
ASSERT_EQ(ax::mojom::Role::kButton, hit_node->GetRole());
ASSERT_EQ("Button B",
hit_node->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
{
// (26, 326) -> "Button C"
// 326 = 2x height of div in ancestors (300), plus button offset (26).
BrowserAccessibility* hit_node;
hit_node = HitTestAndWaitForResult(gfx::Point(26, 326));
ASSERT_TRUE(hit_node != nullptr);
ASSERT_EQ(ax::mojom::Role::kButton, hit_node->GetRole());
ASSERT_EQ("Button C",
hit_node->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
}
IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingCrossProcessBrowserTest,
HitTestingInScrolledCrossProcessIframe) {
GURL url_a(embedded_test_server()->GetURL(
"a.com", "/accessibility/hit_testing/hit_testing_a.html"));
GURL url_b(embedded_test_server()->GetURL(
"b.com", "/accessibility/hit_testing/hit_testing_b_tall.html"));
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
AccessibilityNotificationWaiter waiter(shell()->web_contents(),
ui::kAXModeComplete,
ax::mojom::Event::kLoadComplete);
EXPECT_TRUE(NavigateToURL(shell(), url_a));
waiter.WaitForNotification();
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Button A");
auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child = root->child_at(0);
NavigateFrameToURL(child, url_b);
EXPECT_EQ(url_b, child->current_url());
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Button B");
ASSERT_EQ(1U, child->child_count());
// Before scrolling.
{
// (26, 476) -> "Button B"
// 476 = height of div in parent (150), plus the placeholder div height
// (300), plus button offset (26).
BrowserAccessibility* hit_node;
hit_node = HitTestAndWaitForResult(gfx::Point(26, 476));
ASSERT_TRUE(hit_node != nullptr);
ASSERT_EQ(ax::mojom::Role::kButton, hit_node->GetRole());
ASSERT_EQ("Button B",
hit_node->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
// Scroll div up 100px.
int scroll_delta = 100;
double actual_scroll_delta = 0;
std::string scroll_string = base::StringPrintf(
"window.scrollTo(0, %d); "
"window.domAutomationController.send(window.scrollY);",
scroll_delta);
EXPECT_TRUE(ExecuteScriptAndExtractDouble(
child->current_frame_host(), scroll_string, &actual_scroll_delta));
EXPECT_NEAR(static_cast<double>(scroll_delta), actual_scroll_delta, 1.0);
// After scrolling.
{
// (26, 376) -> "Button B"
// 376 = height of div in parent (150), plus the placeholder div height
// (300), plus button offset (26), less the scroll delta.
BrowserAccessibility* hit_node;
hit_node = HitTestAndWaitForResult(gfx::Point(26, 476 - scroll_delta));
ASSERT_TRUE(hit_node != nullptr);
ASSERT_EQ(ax::mojom::Role::kButton, hit_node->GetRole());
ASSERT_EQ("Button B",
hit_node->GetStringAttribute(ax::mojom::StringAttribute::kName));
}
}
IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingBrowserTest, IN_PROC_BROWSER_TEST_F(AccessibilityHitTestingBrowserTest,
CachingAsyncHitTestingInIframes) { CachingAsyncHitTestingInIframes) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "content/renderer/accessibility/ax_image_annotator.h" #include "content/renderer/accessibility/ax_image_annotator.h"
#include "content/renderer/accessibility/blink_ax_enum_conversion.h" #include "content/renderer/accessibility/blink_ax_enum_conversion.h"
#include "content/renderer/render_frame_impl.h" #include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_frame_proxy.h"
#include "content/renderer/render_view_impl.h" #include "content/renderer/render_view_impl.h"
#include "services/image_annotation/public/mojom/image_annotation.mojom.h" #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
#include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/public/platform/task_type.h"
...@@ -879,8 +880,24 @@ void RenderAccessibilityImpl::OnHitTest(const gfx::Point& point, ...@@ -879,8 +880,24 @@ void RenderAccessibilityImpl::OnHitTest(const gfx::Point& point,
if (data.HasContentIntAttribute(AX_CONTENT_ATTR_CHILD_ROUTING_ID) || if (data.HasContentIntAttribute(AX_CONTENT_ATTR_CHILD_ROUTING_ID) ||
data.HasContentIntAttribute( data.HasContentIntAttribute(
AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID)) { AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID)) {
gfx::Point transformed_point = point;
bool is_remote_frame = RenderFrameProxy::FromRoutingID(
data.GetContentIntAttribute(AX_CONTENT_ATTR_CHILD_ROUTING_ID));
if (is_remote_frame) {
// Remote frames don't have access to the information from the visual
// viewport regarding the visual viewport offset, so we adjust the
// coordinates before sending them to the remote renderer.
WebRect rect = obj.GetBoundsInFrameCoordinates();
// The following transformation of the input point is naive, but works
// fairly well. It will fail with CSS transforms that rotate or shear.
// https://crbug.com/981959.
WebView* web_view = render_frame_->GetRenderView()->GetWebView();
blink::WebFloatPoint viewport_offset = web_view->VisualViewportOffset();
transformed_point += gfx::Vector2d(viewport_offset.x, viewport_offset.y) -
gfx::Rect(rect).OffsetFromOrigin();
}
Send(new AccessibilityHostMsg_ChildFrameHitTestResult( Send(new AccessibilityHostMsg_ChildFrameHitTestResult(
routing_id(), action_request_id, point, routing_id(), action_request_id, transformed_point,
data.GetContentIntAttribute(AX_CONTENT_ATTR_CHILD_ROUTING_ID), data.GetContentIntAttribute(AX_CONTENT_ATTR_CHILD_ROUTING_ID),
data.GetContentIntAttribute( data.GetContentIntAttribute(
AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID), AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID),
......
<!DOCTYPE html>
<html>
<head>
<style>
body {
overflow: hidden;
}
iframe {
border: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
div {
width: 300px;
height: 150px;
}
</style>
</head>
<body style="margin: 0; padding: 0;">
<div>
<button style="margin: 25px; border: 0; width: 250px; height: 50px">
Button A
</button>
</div>
<div style="width: 300px; height: 400px">
<iframe id="frame_a" style="width: 300px; height: 400px;"
scrolling="no"></iframe>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<style>
body {
overflow: hidden;
}
iframe {
border: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
div {
width: 300px;
height: 150px;
}
</style>
</head>
<body style="margin: 0; padding: 0;">
<div>
<button style="margin: 25px; border: 0; width: 250px; height: 50px">
Button B
</button>
</div>
<div>
<iframe id="frame_b" style="width: 300px; height: 300px;"
scrolling="no"></iframe>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<style>
body {
overflow: hidden;
}
iframe {
border: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
div {
width: 300px;
height: 150px;
}
div.placeholder {
height: 300px;
color: Red;
}
</style>
</head>
<body style="margin: 0; padding: 0;" bgcolor=Green>
<div class="placeholder">Placeholder 1</div>
<div>
<button style="margin: 25px; border: 0; width: 250px; height: 50px">
Button B
</button>
</div>
<div class="placeholder">Placeholder 2</div>
<div>
<iframe id="frame_b" style="width: 300px; height: 300px;"
scrolling="no"></iframe>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<style>
body {
overflow: hidden;
}
iframe {
border: 0;
margin: 0;
padding: 0;
overflow: hidden;
}
div {
width: 300px;
height: 150px;
}
</style>
</head>
<body style="margin: 0; padding: 0;">
<div>
<button style="margin: 25px; border: 0; width: 250px; height: 50px">
Button C
</button>
</div>
</body>
</html>
...@@ -181,6 +181,8 @@ class WebAXObject { ...@@ -181,6 +181,8 @@ class WebAXObject {
BLINK_EXPORT int HeadingLevel() const; BLINK_EXPORT int HeadingLevel() const;
BLINK_EXPORT int HierarchicalLevel() const; BLINK_EXPORT int HierarchicalLevel() const;
BLINK_EXPORT WebAXObject HitTest(const WebPoint&) const; BLINK_EXPORT WebAXObject HitTest(const WebPoint&) const;
// Get the WebAXObject's bounds in frame-relative coordinates as a WebRect.
BLINK_EXPORT WebRect GetBoundsInFrameCoordinates() const;
BLINK_EXPORT WebString KeyboardShortcut() const; BLINK_EXPORT WebString KeyboardShortcut() const;
BLINK_EXPORT WebString Language() const; BLINK_EXPORT WebString Language() const;
BLINK_EXPORT WebAXObject InPageLinkTarget() const; BLINK_EXPORT WebAXObject InPageLinkTarget() const;
......
...@@ -699,6 +699,11 @@ WebAXObject WebAXObject::HitTest(const WebPoint& point) const { ...@@ -699,6 +699,11 @@ WebAXObject WebAXObject::HitTest(const WebPoint& point) const {
return WebAXObject(); return WebAXObject();
} }
WebRect WebAXObject::GetBoundsInFrameCoordinates() const {
LayoutRect rect = private_->GetBoundsInFrameCoordinates();
return WebRect(EnclosingIntRect(rect));
}
WebString WebAXObject::KeyboardShortcut() const { WebString WebAXObject::KeyboardShortcut() const {
if (IsDetached()) if (IsDetached())
return WebString(); return WebString();
......
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