Commit fdfa2e07 authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Fix WebView accessibility

This makes the chrome://chrome-signin page accessible too.

BUG=557429,587187,453157
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_site_isolation

Review URL: https://codereview.chromium.org/1815933002

Cr-Commit-Position: refs/heads/master@{#383885}
parent e317ac94
......@@ -738,6 +738,11 @@ INSTANTIATE_TEST_CASE_P(WebViewTests, WebViewTest, testing::Bool());
// These features current would not work with
// --use-cross-process-frames-for-guest and is disabled on
// UseCrossProcessFramesForGuests.
class WebViewAccessibilityTest : public WebViewTest {};
INSTANTIATE_TEST_CASE_P(WebViewTests,
WebViewAccessibilityTest,
testing::Values(false));
class WebViewNewWindowTest : public WebViewTest {};
INSTANTIATE_TEST_CASE_P(WebViewTests,
WebViewNewWindowTest,
......@@ -2664,6 +2669,35 @@ IN_PROC_BROWSER_TEST_P(WebViewTest, LoadWebviewAccessibleResource) {
"web_view/load_webview_accessible_resource", NEEDS_TEST_SERVER);
}
IN_PROC_BROWSER_TEST_P(WebViewAccessibilityTest, FocusAccessibility) {
LoadAppWithGuest("web_view/focus_accessibility");
content::WebContents* web_contents = GetFirstAppWindowWebContents();
content::EnableAccessibilityForWebContents(web_contents);
content::WebContents* guest_web_contents = GetGuestWebContents();
content::EnableAccessibilityForWebContents(guest_web_contents);
// Wait for focus to land on the "root web area" role, representing
// focus on the main document itself.
while (content::GetFocusedAccessibilityNodeInfo(web_contents).role !=
ui::AX_ROLE_ROOT_WEB_AREA) {
content::WaitForAccessibilityFocusChange();
}
// Now keep pressing the Tab key until focus lands on a button.
while (content::GetFocusedAccessibilityNodeInfo(web_contents).role !=
ui::AX_ROLE_BUTTON) {
content::SimulateKeyPress(
web_contents, ui::VKEY_TAB, false, false, false, false);
content::WaitForAccessibilityFocusChange();
}
// Ensure that we hit the button inside the guest frame labeled
// "Guest button".
ui::AXNodeData node_data =
content::GetFocusedAccessibilityNodeInfo(web_contents);
EXPECT_EQ("Guest button", node_data.GetStringAttribute(ui::AX_ATTR_NAME));
}
class WebViewGuestScrollTest
: public WebViewTestBase,
public testing::WithParamInterface<testing::tuple<bool, bool>> {
......
<!--
* Copyright 2016 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.
-->
<html>
<body>
<script src="main.js"></script>
</body>
</html>
// Copyright 2016 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.
function CreateWebViewAndGuest(callback) {
var webview = document.createElement('webview');
var onLoadStop = function(e) {
chrome.test.sendMessage('WebViewTest.LAUNCHED');
webview.removeEventListener('loadstop', onLoadStop);
webview.removeEventListener('loadabort', onLoadAbort);
callback();
};
webview.addEventListener('loadstop', onLoadStop);
var onLoadAbort = function(e) {
chrome.test.sendMessage('WebViewTest.FAILURE');
webview.removeEventListener('loadstop', onLoadStop);
webview.removeEventListener('loadabort', onLoadAbort);
};
webview.src = 'data:text/html,' +
'<html><body><button>Guest button</button></body></html>';
return webview;
}
onload = function() {
var webview = CreateWebViewAndGuest(function() {
webview.addEventListener('newwindow', function(e) {
var newwebview = document.createElement('webview');
newwebview.addEventListener('loadstop', function(e) {
chrome.test.sendMessage('WebViewTest.NEWWINDOW');
});
e.window.attach(newwebview);
document.body.appendChild(newwebview);
});
webview.addEventListener('loadstop', function(e) {
chrome.test.sendMessage('WebViewTest.LOADSTOP');
});
});
document.body.appendChild(webview);
};
{
"name": "<webview> focus accessibility test.",
"version": "1",
"permissions": [
"webview"
],
"app": {
"background": {
"scripts": ["test.js"]
}
}
}
// Copyright 2016 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.
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('main.html', {}, function () {});
});
......@@ -43,6 +43,10 @@ using AXTreeIDMap =
base::hash_map<AXTreeIDRegistry::AXTreeID, BrowserAccessibilityManager*>;
base::LazyInstance<AXTreeIDMap> g_ax_tree_id_map = LAZY_INSTANCE_INITIALIZER;
// A function to call when focus changes, for testing only.
base::LazyInstance<base::Closure> g_focus_change_callback_for_testing =
LAZY_INSTANCE_INITIALIZER;
ui::AXTreeUpdate MakeAXTreeUpdate(
const ui::AXNodeData& node1,
const ui::AXNodeData& node2 /* = ui::AXNodeData() */,
......@@ -179,11 +183,17 @@ BrowserAccessibilityManager::GetEmptyDocument() {
void BrowserAccessibilityManager::FireFocusEventsIfNeeded() {
BrowserAccessibility* focus = GetFocus();
if (delegate_ && !delegate_->AccessibilityViewHasFocus())
focus = nullptr;
if (!CanFireEvents())
focus = nullptr;
// Don't fire focus events if the window itself doesn't have focus.
// Bypass this check if a global focus listener was set up for testing
// so that the test passes whether the window is active or not.
if (!g_focus_change_callback_for_testing.Pointer()) {
if (delegate_ && !delegate_->AccessibilityViewHasFocus())
focus = nullptr;
if (!CanFireEvents())
focus = nullptr;
}
// Don't allow the document to be focused if it has no children and
// hasn't finished loading yet. Wait for at least a tiny bit of content,
......@@ -209,6 +219,9 @@ bool BrowserAccessibilityManager::CanFireEvents() {
void BrowserAccessibilityManager::FireFocusEvent(BrowserAccessibility* node) {
NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, node);
if (!g_focus_change_callback_for_testing.Get().is_null())
g_focus_change_callback_for_testing.Get().Run();
}
BrowserAccessibility* BrowserAccessibilityManager::GetRoot() {
......@@ -456,17 +469,22 @@ BrowserAccessibility* BrowserAccessibilityManager::GetFocus() {
if (!focused_manager)
focused_manager = root_manager;
int32_t focus_id = focused_manager->GetTreeData().focus_id;
BrowserAccessibility* obj = focused_manager->GetFromID(focus_id);
return focused_manager->GetFocusFromThisOrDescendantFrame();
}
BrowserAccessibility*
BrowserAccessibilityManager::GetFocusFromThisOrDescendantFrame() {
int32_t focus_id = GetTreeData().focus_id;
BrowserAccessibility* obj = GetFromID(focus_id);
if (!obj)
return focused_manager->GetRoot();
return GetRoot();
if (obj->HasIntAttribute(ui::AX_ATTR_CHILD_TREE_ID)) {
BrowserAccessibilityManager* child_manager =
BrowserAccessibilityManager::FromID(
obj->GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID));
if (child_manager)
return child_manager->GetFocus();
return child_manager->GetFocusFromThisOrDescendantFrame();
}
return obj;
......@@ -484,6 +502,12 @@ void BrowserAccessibilityManager::SetFocusLocallyForTesting(
tree_->UpdateData(data);
}
// static
void BrowserAccessibilityManager::SetFocusChangeCallbackForTesting(
const base::Closure& callback) {
g_focus_change_callback_for_testing.Get() = callback;
}
void BrowserAccessibilityManager::DoDefaultAction(
const BrowserAccessibility& node) {
if (delegate_)
......
......@@ -9,6 +9,7 @@
#include <vector>
#include "base/callback_forward.h"
#include "base/containers/hash_tables.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
......@@ -196,6 +197,10 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate {
// communicate with the renderer and doesn't fire any events.
void SetFocusLocallyForTesting(BrowserAccessibility* node);
// For testing only, register a function to be called when focus changes
// in any BrowserAccessibilityManager.
static void SetFocusChangeCallbackForTesting(const base::Closure& callback);
// Tell the renderer to do the default action for this node.
void DoDefaultAction(const BrowserAccessibility& node);
......@@ -266,9 +271,13 @@ class CONTENT_EXPORT BrowserAccessibilityManager : public ui::AXTreeDelegate {
ToBrowserAccessibilityManagerAuraLinux();
#endif
// Return the object that has focus.
// Return the object that has focus, starting at the top of the frame tree.
virtual BrowserAccessibility* GetFocus();
// Return the object that has focus, only considering this frame and
// descendants.
BrowserAccessibility* GetFocusFromThisOrDescendantFrame();
// Given a focused node |focus|, returns a descendant of that node if it
// has an active descendant, otherwise returns |focus|.
BrowserAccessibility* GetActiveDescendantFocus(BrowserAccessibility* focus);
......
......@@ -206,6 +206,7 @@ RenderFrameHostImpl::RenderFrameHostImpl(SiteInstance* site_instance,
nav_entry_id_(0),
accessibility_reset_token_(0),
accessibility_reset_count_(0),
browser_plugin_embedder_ax_tree_id_(AXTreeIDRegistry::kNoAXTreeID),
no_create_browser_accessibility_manager_for_testing_(false),
web_ui_type_(WebUI::kNoWebUI),
pending_web_ui_type_(WebUI::kNoWebUI),
......@@ -2668,11 +2669,13 @@ AXTreeIDRegistry::AXTreeID RenderFrameHostImpl::RoutingIDToAXTreeID(
AXTreeIDRegistry::AXTreeID
RenderFrameHostImpl::BrowserPluginInstanceIDToAXTreeID(
int instance_id) {
RenderFrameHost* guest = delegate()->GetGuestByInstanceID(
this, instance_id);
RenderFrameHostImpl* guest = static_cast<RenderFrameHostImpl*>(
delegate()->GetGuestByInstanceID(this, instance_id));
if (!guest)
return AXTreeIDRegistry::kNoAXTreeID;
guest->set_browser_plugin_embedder_ax_tree_id(GetAXTreeID());
return guest->GetAXTreeID();
}
......@@ -2717,6 +2720,9 @@ void RenderFrameHostImpl::AXContentTreeDataToAXTreeData(
if (src.parent_routing_id != -1)
dst->parent_tree_id = RoutingIDToAXTreeID(src.parent_routing_id);
if (browser_plugin_embedder_ax_tree_id_ != AXTreeIDRegistry::kNoAXTreeID)
dst->parent_tree_id = browser_plugin_embedder_ax_tree_id_;
// If this is not the root frame tree node, we're done.
if (frame_tree_node()->parent())
return;
......
......@@ -458,6 +458,12 @@ class CONTENT_EXPORT RenderFrameHostImpl : public RenderFrameHost,
// a renderer.
void UpdateAXTreeData();
// Set the AX tree ID of the embedder RFHI, if this is a browser plugin guest.
void set_browser_plugin_embedder_ax_tree_id(
AXTreeIDRegistry::AXTreeID ax_tree_id) {
browser_plugin_embedder_ax_tree_id_ = ax_tree_id;
}
// Send a message to the render process to change text track style settings.
void SetTextTrackSettings(const FrameMsg_TextTrackSettings_Params& params);
......@@ -894,6 +900,9 @@ class CONTENT_EXPORT RenderFrameHostImpl : public RenderFrameHost,
// The last AXContentTreeData for this frame received from the RenderFrame.
AXContentTreeData ax_content_tree_data_;
// The AX tree ID of the embedder, if this is a browser plugin guest.
AXTreeIDRegistry::AXTreeID browser_plugin_embedder_ax_tree_id_;
// The mapping from callback id to corresponding callback for pending
// accessibility tree snapshot calls created by RequestAXTreeSnapshot.
std::map<int, AXTreeSnapshotCallback> ax_tree_snapshot_callbacks_;
......
......@@ -1330,6 +1330,7 @@
'../net/net.gyp:net_test_support',
'../testing/gtest.gyp:gtest',
'../third_party/WebKit/public/blink.gyp:blink',
'../ui/accessibility/accessibility.gyp:accessibility',
'../ui/base/ime/ui_base_ime.gyp:ui_base_ime',
'../ui/base/ui_base.gyp:ui_base',
'../ui/base/ui_base.gyp:ui_base_test_support',
......
......@@ -21,6 +21,9 @@
#include "base/test/test_timeouts.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/accessibility/accessibility_mode_helper.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
......@@ -875,6 +878,31 @@ bool WaitForRenderFrameReady(RenderFrameHost* rfh) {
return result == "pageLoadComplete";
}
void EnableAccessibilityForWebContents(WebContents* web_contents) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents);
web_contents_impl->SetAccessibilityMode(AccessibilityModeComplete);
}
void WaitForAccessibilityFocusChange() {
scoped_refptr<content::MessageLoopRunner> loop_runner(
new content::MessageLoopRunner);
BrowserAccessibilityManager::SetFocusChangeCallbackForTesting(
loop_runner->QuitClosure());
loop_runner->Run();
}
ui::AXNodeData GetFocusedAccessibilityNodeInfo(WebContents* web_contents) {
WebContentsImpl* web_contents_impl =
static_cast<WebContentsImpl*>(web_contents);
BrowserAccessibilityManager* manager =
web_contents_impl->GetRootBrowserAccessibilityManager();
if (!manager)
return ui::AXNodeData();
BrowserAccessibility* focused_node = manager->GetFocus();
return focused_node->GetData();
}
TitleWatcher::TitleWatcher(WebContents* web_contents,
const base::string16& expected_title)
: WebContentsObserver(web_contents),
......
......@@ -26,6 +26,7 @@
#include "content/public/common/page_type.h"
#include "ipc/message_filter.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "url/gurl.h"
......@@ -279,6 +280,15 @@ void RunTaskAndWaitForInterstitialDetach(content::WebContents* web_contents,
// message is handled properly.
bool WaitForRenderFrameReady(RenderFrameHost* rfh) WARN_UNUSED_RESULT;
// Enable accessibility support for all of the frames in this WebContents
void EnableAccessibilityForWebContents(WebContents* web_contents);
// Wait until the focused accessible node changes in any WebContents.
void WaitForAccessibilityFocusChange();
// Retrieve information about the node that's focused in the accessibility tree.
ui::AXNodeData GetFocusedAccessibilityNodeInfo(WebContents* web_contents);
// Watches title changes on a WebContents, blocking until an expected title is
// set.
class TitleWatcher : public WebContentsObserver {
......
......@@ -264,6 +264,7 @@ source_set("browsertest_base") {
"//content/public/app:both",
"//net:test_support",
"//testing/gtest",
"//ui/accessibility",
"//ui/base",
"//ui/base:test_support",
"//ui/base/ime",
......
......@@ -56,6 +56,7 @@ RoleMap BuildRoleMap() {
{ui::AX_ROLE_DISCLOSURE_TRIANGLE, NSAccessibilityDisclosureTriangleRole},
{ui::AX_ROLE_DIV, NSAccessibilityGroupRole},
{ui::AX_ROLE_DOCUMENT, NSAccessibilityGroupRole},
{ui::AX_ROLE_EMBEDDED_OBJECT, NSAccessibilityGroupRole},
{ui::AX_ROLE_FIGCAPTION, NSAccessibilityGroupRole},
{ui::AX_ROLE_FIGURE, NSAccessibilityGroupRole},
{ui::AX_ROLE_FOOTER, NSAccessibilityGroupRole},
......
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