Commit 28ac2d11 authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Finish implementation of automation API hit testing for the desktop.

This change finishes support for AX_ACTION_HIT_TEST in AXHostDelegate
so that you can send a hit test request on the root of the whole automation
tree and get a hit test on either views or web content.

Switches Select-to-speak to use this new API.  A follow-up change will
change the way select-to-speak works so that it sends the mouse event
to the extension and the hit test comes from there, but this is a
self-contained change that can land first.

BUG=699617

Review-Url: https://codereview.chromium.org/2813083003
Cr-Commit-Position: refs/heads/master@{#465716}
parent 88d101dc
...@@ -8,11 +8,12 @@ ...@@ -8,11 +8,12 @@
#include "base/logging.h" #include "base/logging.h"
#include "chrome/browser/speech/tts_controller.h" #include "chrome/browser/speech/tts_controller.h"
#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h" #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
#include "chrome/common/extensions/api/automation_api_constants.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "ui/accessibility/ax_tree_id_registry.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/display/display.h" #include "ui/display/display.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/views/focus/view_storage.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
...@@ -121,42 +122,15 @@ void SelectToSpeakEventHandler::OnMouseEvent(ui::MouseEvent* event) { ...@@ -121,42 +122,15 @@ void SelectToSpeakEventHandler::OnMouseEvent(ui::MouseEvent* event) {
CancelEvent(event); CancelEvent(event);
// Find the View to post the accessibility event on. ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
aura::Window* event_target = static_cast<aura::Window*>(event->target()); ui::AXHostDelegate* delegate =
aura::Window* hit_window = event_target; registry->GetHostDelegate(extensions::api::automation::kDesktopTreeID);
if (!hit_window) if (delegate) {
return; ui::AXActionData action;
action.action = ui::AX_ACTION_HIT_TEST;
views::Widget* hit_widget = views::Widget::GetWidgetForNativeView(hit_window); action.target_point = event->root_location();
while (!hit_widget) { action.hit_test_event_to_fire = ax_event;
hit_window = hit_window->parent(); delegate->PerformAction(action);
if (!hit_window)
break;
hit_widget = views::Widget::GetWidgetForNativeView(hit_window);
}
if (!hit_window || !hit_widget)
return;
gfx::Point window_location = event->location();
aura::Window::ConvertPointToTarget(event_target, hit_window,
&window_location);
views::View* root_view = hit_widget->GetRootView();
views::View* hit_view = root_view->GetEventHandlerForPoint(window_location);
if (hit_view) {
// Send the accessibility event, then save the view so we can post the
// cancel event on the same view if possible.
hit_view->NotifyAccessibilityEvent(ax_event, true);
if (!last_view_storage_id_) {
last_view_storage_id_ =
views::ViewStorage::GetInstance()->CreateStorageID();
}
views::ViewStorage::GetInstance()->RemoveView(last_view_storage_id_);
views::ViewStorage::GetInstance()->StoreView(last_view_storage_id_,
hit_view);
} }
} }
...@@ -169,20 +143,8 @@ void SelectToSpeakEventHandler::CancelEvent(ui::Event* event) { ...@@ -169,20 +143,8 @@ void SelectToSpeakEventHandler::CancelEvent(ui::Event* event) {
} }
void SelectToSpeakEventHandler::SendCancelAXEvent() { void SelectToSpeakEventHandler::SendCancelAXEvent() {
// If the user releases Search while the button is still down, cancel AutomationManagerAura::GetInstance()->HandleEvent(
// the Select-to-speak gesture. Try to post it on the same View that nullptr, nullptr, ui::AX_EVENT_MOUSE_CANCELED);
// was last targeted, but if that's impossible, post it on the desktop.
views::View* last_view =
last_view_storage_id_
? views::ViewStorage::GetInstance()->RetrieveView(
last_view_storage_id_)
: nullptr;
if (last_view) {
last_view->NotifyAccessibilityEvent(ui::AX_EVENT_MOUSE_CANCELED, true);
} else {
AutomationManagerAura::GetInstance()->HandleEvent(
nullptr, nullptr, ui::AX_EVENT_MOUSE_CANCELED);
}
} }
} // namespace chromeos } // namespace chromeos
...@@ -62,8 +62,6 @@ class SelectToSpeakEventHandler : public ui::EventHandler { ...@@ -62,8 +62,6 @@ class SelectToSpeakEventHandler : public ui::EventHandler {
// is pressed, and only cleared when all keys are released. // is pressed, and only cleared when all keys are released.
std::set<ui::KeyboardCode> keys_pressed_together_; std::set<ui::KeyboardCode> keys_pressed_together_;
int last_view_storage_id_ = 0;
DISALLOW_COPY_AND_ASSIGN(SelectToSpeakEventHandler); DISALLOW_COPY_AND_ASSIGN(SelectToSpeakEventHandler);
}; };
......
...@@ -219,30 +219,6 @@ TEST_F(SelectToSpeakEventHandlerTest, SearchPlusClickTwice) { ...@@ -219,30 +219,6 @@ TEST_F(SelectToSpeakEventHandlerTest, SearchPlusClickTwice) {
EXPECT_FALSE(event_capturer_.last_key_event()); EXPECT_FALSE(event_capturer_.last_key_event());
} }
TEST_F(SelectToSpeakEventHandlerTest, SearchPlusMouseThenCancel) {
// If the user holds the Search key and then presses the mouse button,
// but then releases the Search key first while the mouse is still down,
// a cancel AX event is sent and the subsequent mouse up is canceled too.
generator_->PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ASSERT_TRUE(event_capturer_.last_key_event());
EXPECT_FALSE(event_capturer_.last_key_event()->handled());
generator_->set_current_location(gfx::Point(100, 12));
generator_->PressLeftButton();
EXPECT_FALSE(event_capturer_.last_mouse_event());
EXPECT_TRUE(event_delegate_->CapturedAXEvent(ui::AX_EVENT_MOUSE_PRESSED));
event_capturer_.Reset();
generator_->ReleaseKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
EXPECT_FALSE(event_capturer_.last_key_event());
EXPECT_TRUE(event_delegate_->CapturedAXEvent(ui::AX_EVENT_MOUSE_CANCELED));
generator_->ReleaseLeftButton();
EXPECT_FALSE(event_capturer_.last_mouse_event());
}
TEST_F(SelectToSpeakEventHandlerTest, SearchPlusKeyIgnoresClicks) { TEST_F(SelectToSpeakEventHandlerTest, SearchPlusKeyIgnoresClicks) {
// If the user presses the Search key and then some other key, // If the user presses the Search key and then some other key,
// we should assume the user does not want select-to-speak, and // we should assume the user does not want select-to-speak, and
......
...@@ -233,6 +233,11 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) { ...@@ -233,6 +233,11 @@ IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) {
<< message_; << message_;
} }
IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopHitTest) {
ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "hit_test.html"))
<< message_;
}
// Flaky, see http://crbug.com/435449 // Flaky, see http://crbug.com/435449
IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_DesktopLoadTabs) { IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_DesktopLoadTabs) {
ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html")) ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html"))
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#if defined(USE_AURA) #if defined(USE_AURA)
#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h" #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
#include "ui/aura/env.h"
#endif #endif
namespace extensions { namespace extensions {
...@@ -177,6 +178,9 @@ class AutomationWebContentsObserver ...@@ -177,6 +178,9 @@ class AutomationWebContentsObserver
params.event_type = event.event_type; params.event_type = event.event_type;
params.update = event.update; params.update = event.update;
params.event_from = event.event_from; params.event_from = event.event_from;
#if defined(USE_AURA)
params.mouse_location = aura::Env::GetInstance()->last_mouse_location();
#endif
AutomationEventRouter* router = AutomationEventRouter::GetInstance(); AutomationEventRouter* router = AutomationEventRouter::GetInstance();
router->DispatchAccessibilityEvent(params); router->DispatchAccessibilityEvent(params);
......
...@@ -15,9 +15,11 @@ ...@@ -15,9 +15,11 @@
#include "chrome/common/extensions/chrome_extension_messages.h" #include "chrome/common/extensions/chrome_extension_messages.h"
#include "content/public/browser/ax_event_notification_details.h" #include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/browser_context.h" #include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.h" #include "ui/accessibility/ax_enums.h"
#include "ui/accessibility/ax_tree_id_registry.h" #include "ui/accessibility/ax_tree_id_registry.h"
#include "ui/accessibility/platform/aura_window_properties.h"
#include "ui/aura/env.h" #include "ui/aura/env.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h"
...@@ -25,6 +27,7 @@ ...@@ -25,6 +27,7 @@
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
#include "ash/shell.h" // nogncheck
#include "ash/wm/window_util.h" // nogncheck #include "ash/wm/window_util.h" // nogncheck
#endif #endif
...@@ -81,10 +84,16 @@ void AutomationManagerAura::HandleAlert(content::BrowserContext* context, ...@@ -81,10 +84,16 @@ void AutomationManagerAura::HandleAlert(content::BrowserContext* context,
SendEvent(context, obj, ui::AX_EVENT_ALERT); SendEvent(context, obj, ui::AX_EVENT_ALERT);
} }
void AutomationManagerAura::PerformAction( void AutomationManagerAura::PerformAction(const ui::AXActionData& data) {
const ui::AXActionData& data) {
CHECK(enabled_); CHECK(enabled_);
// Unlike all of the other actions, a hit test requires determining the
// node to perform the action on first.
if (data.action == ui::AX_ACTION_HIT_TEST) {
PerformHitTest(data);
return;
}
current_tree_->HandleAccessibleAction(data); current_tree_->HandleAccessibleAction(data);
} }
...@@ -161,3 +170,60 @@ void AutomationManagerAura::SendEvent(BrowserContext* context, ...@@ -161,3 +170,60 @@ void AutomationManagerAura::SendEvent(BrowserContext* context,
pending_events_copy[i].second); pending_events_copy[i].second);
} }
} }
void AutomationManagerAura::PerformHitTest(
const ui::AXActionData& original_action) {
#if defined(OS_CHROMEOS)
ui::AXActionData action = original_action;
aura::Window* root_window = ash::Shell::Get()->GetPrimaryRootWindow();
if (!root_window)
return;
// Determine which aura Window is associated with the target point.
aura::Window* window =
root_window->GetEventHandlerForPoint(action.target_point);
if (!window)
return;
// Convert point to local coordinates of the hit window.
aura::Window::ConvertPointToTarget(root_window, window, &action.target_point);
// If the window has a child AX tree ID, forward the action to the
// associated AXHostDelegate or RenderFrameHost.
ui::AXTreeIDRegistry::AXTreeID child_ax_tree_id =
window->GetProperty(ui::kChildAXTreeID);
if (child_ax_tree_id != ui::AXTreeIDRegistry::kNoAXTreeID) {
ui::AXTreeIDRegistry* registry = ui::AXTreeIDRegistry::GetInstance();
ui::AXHostDelegate* delegate = registry->GetHostDelegate(child_ax_tree_id);
if (delegate) {
delegate->PerformAction(action);
return;
}
content::RenderFrameHost* rfh =
content::RenderFrameHost::FromAXTreeID(child_ax_tree_id);
if (rfh)
rfh->AccessibilityPerformAction(action);
return;
}
// If the window doesn't have a child tree ID, try to fire the event
// on a View.
views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
if (widget) {
views::View* root_view = widget->GetRootView();
views::View* hit_view =
root_view->GetEventHandlerForPoint(action.target_point);
if (hit_view) {
hit_view->NotifyAccessibilityEvent(action.hit_test_event_to_fire, true);
return;
}
}
// Otherwise, fire the event directly on the Window.
views::AXAuraObjWrapper* window_wrapper =
views::AXAuraObjCache::GetInstance()->GetOrCreate(window);
if (window_wrapper)
SendEvent(nullptr, window_wrapper, action.hit_test_event_to_fire);
#endif
}
...@@ -78,6 +78,8 @@ class AutomationManagerAura : public ui::AXHostDelegate, ...@@ -78,6 +78,8 @@ class AutomationManagerAura : public ui::AXHostDelegate,
views::AXAuraObjWrapper* aura_obj, views::AXAuraObjWrapper* aura_obj,
ui::AXEvent event_type); ui::AXEvent event_type);
void PerformHitTest(const ui::AXActionData& data);
// Whether automation support for views is enabled. // Whether automation support for views is enabled.
bool enabled_; bool enabled_;
......
<!--
* 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.
-->
<script src="common.js"></script>
<script src="hit_test.js"></script>
// 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.
var allTests = [
function testHitTestInDesktop() {
var url = 'data:text/html,<!doctype html>' +
encodeURI('<button>Click Me</button>');
chrome.automation.getDesktop(function(desktop) {
chrome.tabs.create({url: url});
desktop.addEventListener('loadComplete', function(event) {
if (event.target.url.indexOf('data:') >= 0) {
var button = desktop.find({ attributes: { name: 'Click Me' } });
if (button) {
button.addEventListener(EventType.ALERT, function() {
chrome.test.succeed();
}, true);
var cx = Math.floor(
button.location.left + button.location.width / 2);
var cy = Math.floor(
button.location.top + button.location.height / 2);
desktop.hitTest(cx, cy, EventType.ALERT);
}
}
}, false);
});
},
];
chrome.test.runTests(allTests);
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