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

Fix Chrome OS virtual keyboard accessibility

The user-visible change here is that Select-to-speak on Chrome OS now
works on the virtual keyboard. It also makes it visible to ChromeVox
so we can make that work better too.

Three fixes were required:

* We need to always set the window property on the window for a
  RenderWidgetHostViewAura, whether that WebContents already has
  accessibility enabled or not. That's solved by moving the
  trigger for the window property to RenderViewReady and
  WebContentsImpl::NotifySwappedFromRenderManager.

* AXAuraObjCache needs to listen for newly-created windows,
  changes to window bounds, and window property changes.
  Otherwise the virtual keyboard window is created but no
  accessibility events fire allowing automation clients to
  find it.

* The bounds for a AXWindowObjWrapper should be global bounds and not
  local. This wasn't caught before because widget bounds were correct,
  but the virtual keyboard is directly in an aura Window, with no Widget
  and no Views.

BUG=699617
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation;master.tryserver.chromium.linux:linux_site_isolation

Review-Url: https://codereview.chromium.org/2803823002
Cr-Commit-Position: refs/heads/master@{#468182}
parent d33a171b
......@@ -108,6 +108,11 @@ void AutomationManagerAura::OnChildWindowRemoved(
SendEvent(nullptr, parent, ui::AX_EVENT_CHILDREN_CHANGED);
}
void AutomationManagerAura::OnEvent(views::AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type) {
SendEvent(nullptr, aura_obj, event_type);
}
AutomationManagerAura::AutomationManagerAura()
: AXHostDelegate(extensions::api::automation::kDesktopTreeID),
enabled_(false),
......
......@@ -62,6 +62,8 @@ class AutomationManagerAura : public ui::AXHostDelegate,
// views::AXAuraObjCache::Delegate implementation.
void OnChildWindowRemoved(views::AXAuraObjWrapper* parent) override;
void OnEvent(views::AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type) override;
protected:
AutomationManagerAura();
......@@ -70,6 +72,8 @@ class AutomationManagerAura : public ui::AXHostDelegate,
private:
friend struct base::DefaultSingletonTraits<AutomationManagerAura>;
FRIEND_TEST_ALL_PREFIXES(AutomationManagerAuraBrowserTest, WebAppearsOnce);
// Reset state in this manager. If |reset_serializer| is true, reset the
// serializer to save memory.
void Reset(bool reset_serializer);
......
// Copyright 2017 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 "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "ui/accessibility/ax_node_data.h"
namespace {
// Given an AXTreeSourceAura and a node within that tree, recursively search
// for all nodes who have a child tree id of |target_ax_tree_id|, meaning
// that they're a parent of a particular web contents.
void FindAllHostsOfWebContentsWithAXTreeID(
AXTreeSourceAura* tree,
views::AXAuraObjWrapper* node,
int target_ax_tree_id,
std::vector<views::AXAuraObjWrapper*>* web_hosts) {
ui::AXNodeData node_data;
tree->SerializeNode(node, &node_data);
if (node_data.GetIntAttribute(ui::AX_ATTR_CHILD_TREE_ID) ==
target_ax_tree_id) {
web_hosts->push_back(node);
}
std::vector<views::AXAuraObjWrapper*> children;
tree->GetChildren(node, &children);
for (auto* child : children) {
FindAllHostsOfWebContentsWithAXTreeID(tree, child, target_ax_tree_id,
web_hosts);
}
}
} // namespace
typedef InProcessBrowserTest AutomationManagerAuraBrowserTest;
// A WebContents can be "hooked up" to the Chrome OS Desktop accessibility
// tree two different ways: via its aura::Window, and via a views::WebView.
// This test makes sure that we don't hook up both simultaneously, leading
// to the same web page appearing in the overall tree twice.
IN_PROC_BROWSER_TEST_F(AutomationManagerAuraBrowserTest, WebAppearsOnce) {
content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
AutomationManagerAura* manager = AutomationManagerAura::GetInstance();
manager->Enable(browser()->profile());
auto* tree = manager->current_tree_.get();
ui_test_utils::NavigateToURL(
browser(),
GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
WaitForAccessibilityTreeToContainNodeWithName(web_contents, "Click me");
auto* frame_host = web_contents->GetMainFrame();
int ax_tree_id = frame_host->GetAXTreeID();
ASSERT_GT(ax_tree_id, 0);
std::vector<views::AXAuraObjWrapper*> web_hosts;
FindAllHostsOfWebContentsWithAXTreeID(tree, tree->GetRoot(), ax_tree_id,
&web_hosts);
EXPECT_EQ(1U, web_hosts.size());
if (web_hosts.size() == 1) {
ui::AXNodeData node_data;
tree->SerializeNode(web_hosts[0], &node_data);
EXPECT_EQ(ui::AX_ROLE_WEB_VIEW, node_data.role);
} else {
for (size_t i = 0; i < web_hosts.size(); i++) {
ui::AXNodeData node_data;
tree->SerializeNode(web_hosts[i], &node_data);
LOG(ERROR) << i << ": " << node_data.ToString();
}
}
}
......@@ -1945,6 +1945,9 @@ test("browser_tests") {
deps += [ ":test_support_applist_ash" ]
}
}
if (use_aura) {
sources += [ "../browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc" ]
}
if (use_aura || toolkit_views) {
deps += [ "//ui/events:test_support" ]
}
......
......@@ -2167,10 +2167,6 @@ void RenderFrameHostImpl::OnAccessibilityEvents(
accessibility_reset_token_ = 0;
RenderWidgetHostViewBase* view = GetViewForAccessibility();
if (frame_tree_node_->IsMainFrame() && view)
view->SetMainFrameAXTreeID(GetAXTreeID());
AccessibilityMode accessibility_mode = delegate_->GetAccessibilityMode();
if (!accessibility_mode.is_mode_off() && view && is_active()) {
if (accessibility_mode.has_mode(AccessibilityMode::kNativeAPIs))
......
......@@ -4520,6 +4520,11 @@ void WebContentsImpl::RenderViewReady(RenderViewHost* rvh) {
return;
}
RenderWidgetHostViewBase* rwhv =
static_cast<RenderWidgetHostViewBase*>(GetRenderWidgetHostView());
if (rwhv)
rwhv->SetMainFrameAXTreeID(GetMainFrame()->GetAXTreeID());
notify_disconnection_ = true;
// TODO(avi): Remove. http://crbug.com/170921
NotificationService::current()->Notify(
......@@ -5080,6 +5085,11 @@ void WebContentsImpl::NotifySwappedFromRenderManager(RenderFrameHost* old_host,
view_->SetOverscrollControllerEnabled(CanOverscrollContent());
view_->RenderViewSwappedIn(new_host->GetRenderViewHost());
RenderWidgetHostViewBase* rwhv =
static_cast<RenderWidgetHostViewBase*>(GetRenderWidgetHostView());
if (rwhv)
rwhv->SetMainFrameAXTreeID(GetMainFrame()->GetAXTreeID());
}
NotifyFrameSwapped(old_host, new_host);
......
......@@ -34,6 +34,14 @@ AXAuraObjWrapper* AXAuraObjCache::GetOrCreate(Widget* widget) {
AXAuraObjWrapper* AXAuraObjCache::GetOrCreate(aura::Window* window) {
if (!focus_client_) {
// Note: On Chrome OS, there's exactly one root window per screen,
// it's the same as ash::Shell::Get()->GetPrimaryRootWindow() when
// there's only one screen. Observing the root window allows us to
// detect any time a window is added or removed.
//
// TODO(dmazzoni): Explicitly observe each root window on Chrome OS
// and track as root windows are added and removed.
// http://crbug.com/713278
aura::Window* root_window = window->GetRootWindow();
if (root_window) {
focus_client_ = aura::client::GetFocusClient(root_window);
......@@ -124,6 +132,12 @@ void AXAuraObjCache::OnFocusedViewChanged() {
view->NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, true);
}
void AXAuraObjCache::FireEvent(AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type) {
if (delegate_)
delegate_->OnEvent(aura_obj, event_type);
}
AXAuraObjCache::AXAuraObjCache()
: current_id_(1),
focus_client_(nullptr),
......@@ -181,6 +195,15 @@ void AXAuraObjCache::OnWindowDestroying(aura::Window* window) {
focus_client_ = nullptr;
}
void AXAuraObjCache::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
aura::Window* window = params.target;
if (window->parent()) {
delegate_->OnEvent(GetOrCreate(window->parent()),
ui::AX_EVENT_CHILDREN_CHANGED);
}
}
template <typename AuraViewWrapper, typename AuraView>
AXAuraObjWrapper* AXAuraObjCache::CreateInternal(
AuraView* aura_view,
......
......@@ -12,6 +12,7 @@
#include <vector>
#include "base/macros.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/aura/client/focus_change_observer.h"
#include "ui/aura/window_observer.h"
#include "ui/views/views_export.h"
......@@ -43,6 +44,8 @@ class VIEWS_EXPORT AXAuraObjCache
class Delegate {
public:
virtual void OnChildWindowRemoved(AXAuraObjWrapper* parent) = 0;
virtual void OnEvent(AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type) = 0;
};
// Get or create an entry in the cache based on an Aura view.
......@@ -85,6 +88,9 @@ class VIEWS_EXPORT AXAuraObjCache
// Send a notification that the focused view may have changed.
void OnFocusedViewChanged();
// Tell our delegate to fire an event on a given object.
void FireEvent(AXAuraObjWrapper* aura_obj, ui::AXEvent event_type);
// Indicates if this object's currently being destroyed.
bool is_destroying() { return is_destroying_; }
......@@ -104,6 +110,7 @@ class VIEWS_EXPORT AXAuraObjCache
// aura::WindowObserver override.
void OnWindowDestroying(aura::Window* window) override;
void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override;
template <typename AuraViewWrapper, typename AuraView>
AXAuraObjWrapper* CreateInternal(
......
......@@ -54,12 +54,24 @@ void AXWindowObjWrapper::Serialize(ui::AXNodeData* out_node_data) {
out_node_data->AddStringAttribute(ui::AX_ATTR_NAME,
base::UTF16ToUTF8(window_->GetTitle()));
out_node_data->state = 0;
out_node_data->location = gfx::RectF(window_->bounds());
out_node_data->location = gfx::RectF(window_->GetBoundsInScreen());
ui::AXTreeIDRegistry::AXTreeID child_ax_tree_id =
window_->GetProperty(ui::kChildAXTreeID);
if (child_ax_tree_id != ui::AXTreeIDRegistry::kNoAXTreeID)
if (child_ax_tree_id != ui::AXTreeIDRegistry::kNoAXTreeID) {
// Most often, child AX trees are parented to Views. We need to handle
// the case where they're not here, but we don't want the same AX tree
// to be a child of two different parents.
//
// To avoid this double-parenting, only add the child tree ID of this
// window if the top-level window doesn't have an associated Widget.
if (!window_->GetToplevelWindow() ||
Widget::GetWidgetForNativeView(window_->GetToplevelWindow())) {
return;
}
out_node_data->AddIntAttribute(ui::AX_ATTR_CHILD_TREE_ID, child_ax_tree_id);
}
}
int32_t AXWindowObjWrapper::GetID() {
......@@ -85,6 +97,11 @@ void AXWindowObjWrapper::OnWindowHierarchyChanged(
void AXWindowObjWrapper::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
if (window != window_)
return;
AXAuraObjCache::GetInstance()->FireEvent(this, ui::AX_EVENT_LOCATION_CHANGED);
Widget* widget = Widget::GetWidgetForNativeView(window);
if (widget) {
widget->GetRootView()->NotifyAccessibilityEvent(
......@@ -92,4 +109,13 @@ void AXWindowObjWrapper::OnWindowBoundsChanged(aura::Window* window,
}
}
void AXWindowObjWrapper::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
if (window == window_ && key == ui::kChildAXTreeID) {
AXAuraObjCache::GetInstance()->FireEvent(this,
ui::AX_EVENT_CHILDREN_CHANGED);
}
}
} // namespace views
......@@ -43,6 +43,9 @@ class AXWindowObjWrapper : public AXAuraObjWrapper,
void OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) override;
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) override;
private:
aura::Window* window_;
......
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