Commit 55b083a6 authored by Mike Wasserman's avatar Mike Wasserman Committed by Commit Bot

mash: Move SpokenFeedbackEventRewriter to ash; add mojo delegate

Move the rewriter files to ash, keep the delegate impl in chrome.
Add a mojo interface for the delegate to receive and return events.
Ash's controller creates the rewriter, Chrome sets the delegate.

Move, expand, and refine the existing unit tests.

Add TODOs to refine the rewriter/delegate/ChromeVox pattern.
(the rewriter shouldn't pass events that will need to be reposted)

This should allow Mash to support this rewriter in the future.
(for now, Ash doesn't get Window Service events sent to Chrome)

Bug: 647781
Test: No regressions with ChromeVox spoken feedback on Chrome OS.
Change-Id: I4f7805298156ab9463ca3110a1e957f922c6d82b
Reviewed-on: https://chromium-review.googlesource.com/1038819
Commit-Queue: Michael Wasserman <msw@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555867}
parent b12b2d3b
...@@ -203,6 +203,8 @@ component("ash") { ...@@ -203,6 +203,8 @@ component("ash") {
"events/event_rewriter_controller.h", "events/event_rewriter_controller.h",
"events/keyboard_driven_event_rewriter.cc", "events/keyboard_driven_event_rewriter.cc",
"events/keyboard_driven_event_rewriter.h", "events/keyboard_driven_event_rewriter.h",
"events/spoken_feedback_event_rewriter.cc",
"events/spoken_feedback_event_rewriter.h",
"first_run/desktop_cleaner.cc", "first_run/desktop_cleaner.cc",
"first_run/desktop_cleaner.h", "first_run/desktop_cleaner.h",
"first_run/first_run_helper.cc", "first_run/first_run_helper.cc",
...@@ -1557,6 +1559,7 @@ test("ash_unittests") { ...@@ -1557,6 +1559,7 @@ test("ash_unittests") {
"drag_drop/drag_drop_tracker_unittest.cc", "drag_drop/drag_drop_tracker_unittest.cc",
"drag_drop/drag_image_view_unittest.cc", "drag_drop/drag_image_view_unittest.cc",
"events/keyboard_driven_event_rewriter_unittest.cc", "events/keyboard_driven_event_rewriter_unittest.cc",
"events/spoken_feedback_event_rewriter_unittest.cc",
"extended_desktop_unittest.cc", "extended_desktop_unittest.cc",
"first_run/first_run_helper_unittest.cc", "first_run/first_run_helper_unittest.cc",
"focus_cycler_unittest.cc", "focus_cycler_unittest.cc",
......
per-file spoken_feedback_event_rewriter*=file://ui/accessibility/OWNERS
...@@ -9,10 +9,12 @@ ...@@ -9,10 +9,12 @@
#include "ash/display/mirror_window_controller.h" #include "ash/display/mirror_window_controller.h"
#include "ash/display/window_tree_host_manager.h" #include "ash/display/window_tree_host_manager.h"
#include "ash/events/keyboard_driven_event_rewriter.h" #include "ash/events/keyboard_driven_event_rewriter.h"
#include "ash/events/spoken_feedback_event_rewriter.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ui/aura/env.h" #include "ui/aura/env.h"
#include "ui/aura/window_tree_host.h" #include "ui/aura/window_tree_host.h"
#include "ui/events/event_rewriter.h" #include "ui/events/event_rewriter.h"
#include "ui/events/event_sink.h"
#include "ui/events/event_source.h" #include "ui/events/event_source.h"
namespace ash { namespace ash {
...@@ -25,6 +27,11 @@ EventRewriterController::EventRewriterController() { ...@@ -25,6 +27,11 @@ EventRewriterController::EventRewriterController() {
std::make_unique<KeyboardDrivenEventRewriter>(); std::make_unique<KeyboardDrivenEventRewriter>();
keyboard_driven_event_rewriter_ = keyboard_driven_event_rewriter.get(); keyboard_driven_event_rewriter_ = keyboard_driven_event_rewriter.get();
AddEventRewriter(std::move(keyboard_driven_event_rewriter)); AddEventRewriter(std::move(keyboard_driven_event_rewriter));
std::unique_ptr<SpokenFeedbackEventRewriter> spoken_feedback_event_rewriter =
std::make_unique<SpokenFeedbackEventRewriter>();
spoken_feedback_event_rewriter_ = spoken_feedback_event_rewriter.get();
AddEventRewriter(std::move(spoken_feedback_event_rewriter));
} }
EventRewriterController::~EventRewriterController() { EventRewriterController::~EventRewriterController() {
...@@ -67,6 +74,17 @@ void EventRewriterController::SetArrowToTabRewritingEnabled(bool enabled) { ...@@ -67,6 +74,17 @@ void EventRewriterController::SetArrowToTabRewritingEnabled(bool enabled) {
keyboard_driven_event_rewriter_->set_arrow_to_tab_rewriting_enabled(enabled); keyboard_driven_event_rewriter_->set_arrow_to_tab_rewriting_enabled(enabled);
} }
void EventRewriterController::SetSpokenFeedbackEventRewriterDelegate(
mojom::SpokenFeedbackEventRewriterDelegatePtr delegate) {
spoken_feedback_event_rewriter_->SetDelegate(std::move(delegate));
}
void EventRewriterController::OnUnhandledSpokenFeedbackEvent(
std::unique_ptr<ui::Event> event) {
spoken_feedback_event_rewriter_->OnUnhandledSpokenFeedbackEvent(
std::move(event));
}
void EventRewriterController::OnHostInitialized(aura::WindowTreeHost* host) { void EventRewriterController::OnHostInitialized(aura::WindowTreeHost* host) {
for (const auto& rewriter : rewriters_) for (const auto& rewriter : rewriters_)
host->GetEventSource()->AddEventRewriter(rewriter.get()); host->GetEventSource()->AddEventRewriter(rewriter.get());
......
...@@ -22,6 +22,7 @@ class EventSource; ...@@ -22,6 +22,7 @@ class EventSource;
namespace ash { namespace ash {
class KeyboardDrivenEventRewriter; class KeyboardDrivenEventRewriter;
class SpokenFeedbackEventRewriter;
// Owns ui::EventRewriters and ensures that they are added to each root window // Owns ui::EventRewriters and ensures that they are added to each root window
// EventSource, current and future, in the order that they are added to this. // EventSource, current and future, in the order that they are added to this.
...@@ -42,6 +43,10 @@ class ASH_EXPORT EventRewriterController ...@@ -42,6 +43,10 @@ class ASH_EXPORT EventRewriterController
// mojom::EventRewriterController: // mojom::EventRewriterController:
void SetKeyboardDrivenEventRewriterEnabled(bool enabled) override; void SetKeyboardDrivenEventRewriterEnabled(bool enabled) override;
void SetArrowToTabRewritingEnabled(bool enabled) override; void SetArrowToTabRewritingEnabled(bool enabled) override;
void SetSpokenFeedbackEventRewriterDelegate(
mojom::SpokenFeedbackEventRewriterDelegatePtr delegate) override;
void OnUnhandledSpokenFeedbackEvent(
std::unique_ptr<ui::Event> event) override;
// aura::EnvObserver: // aura::EnvObserver:
void OnWindowInitialized(aura::Window* window) override {} void OnWindowInitialized(aura::Window* window) override {}
...@@ -51,9 +56,12 @@ class ASH_EXPORT EventRewriterController ...@@ -51,9 +56,12 @@ class ASH_EXPORT EventRewriterController
// The |EventRewriter|s managed by this controller. // The |EventRewriter|s managed by this controller.
std::vector<std::unique_ptr<ui::EventRewriter>> rewriters_; std::vector<std::unique_ptr<ui::EventRewriter>> rewriters_;
// A weak pointer the KeyboardDrivenEventRewriter owned in |rewriters_|. // A weak pointer to the KeyboardDrivenEventRewriter owned in |rewriters_|.
KeyboardDrivenEventRewriter* keyboard_driven_event_rewriter_; KeyboardDrivenEventRewriter* keyboard_driven_event_rewriter_;
// A weak pointer to the SpokenFeedbackEventRewriter owned in |rewriters_|.
SpokenFeedbackEventRewriter* spoken_feedback_event_rewriter_;
// Bindings for the EventRewriterController mojo interface. // Bindings for the EventRewriterController mojo interface.
mojo::BindingSet<mojom::EventRewriterController> bindings_; mojo::BindingSet<mojom::EventRewriterController> bindings_;
......
// Copyright 2015 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 "ash/events/spoken_feedback_event_rewriter.h"
#include <string>
#include <utility>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/shell.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_sink.h"
namespace ash {
SpokenFeedbackEventRewriter::SpokenFeedbackEventRewriter() = default;
SpokenFeedbackEventRewriter::~SpokenFeedbackEventRewriter() = default;
void SpokenFeedbackEventRewriter::SetDelegate(
mojom::SpokenFeedbackEventRewriterDelegatePtr delegate) {
delegate_ = std::move(delegate);
}
void SpokenFeedbackEventRewriter::OnUnhandledSpokenFeedbackEvent(
std::unique_ptr<ui::Event> event) const {
DCHECK(event->IsKeyEvent()) << "Unexpected unhandled event type";
// For now, these events are sent directly to the primary display's EventSink.
// TODO: Pass the event to the original EventSource's other event rewriters.
ui::EventSink* sink = Shell::GetPrimaryRootWindow()->GetHost()->event_sink();
if (sink->OnEventFromSource(event.get()).dispatcher_destroyed) {
VLOG(0) << "Undispatched key " << event->AsKeyEvent()->key_code()
<< " due to destroyed dispatcher.";
}
}
ui::EventRewriteStatus SpokenFeedbackEventRewriter::RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) {
if (!delegate_.is_bound() || !event.IsKeyEvent())
return ui::EVENT_REWRITE_CONTINUE;
if (!Shell::Get()->accessibility_controller()->IsSpokenFeedbackEnabled())
return ui::EVENT_REWRITE_CONTINUE;
// TODO: Avoid passing events that will be reposted for system-wide dispatch.
delegate_->DispatchKeyEventToChromeVox(ui::Event::Clone(event));
return ui::EVENT_REWRITE_DISCARD;
}
ui::EventRewriteStatus SpokenFeedbackEventRewriter::NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) {
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
} // namespace ash
// Copyright 2015 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.
#ifndef ASH_EVENTS_SPOKEN_FEEDBACK_EVENT_REWRITER_H_
#define ASH_EVENTS_SPOKEN_FEEDBACK_EVENT_REWRITER_H_
#include "ash/ash_export.h"
#include "ash/public/interfaces/event_rewriter_controller.mojom.h"
#include "base/macros.h"
#include "ui/events/event_rewriter.h"
namespace ash {
// SpokenFeedbackEventRewriter sends key events to ChromeVox (via the delegate)
// when spoken feedback is enabled. Continues dispatch of unhandled key events.
// TODO(http://crbug.com/839541): Avoid reposting unhandled events.
class ASH_EXPORT SpokenFeedbackEventRewriter : public ui::EventRewriter {
public:
SpokenFeedbackEventRewriter();
~SpokenFeedbackEventRewriter() override;
// Set the delegate used to send key events to the ChromeVox extension.
void SetDelegate(mojom::SpokenFeedbackEventRewriterDelegatePtr delegate);
mojom::SpokenFeedbackEventRewriterDelegatePtr* get_delegate_for_testing() {
return &delegate_;
}
// Continue dispatch of events that were unhandled by the ChromeVox extension.
// NOTE: These events may be delivered out-of-order from non-ChromeVox events.
void OnUnhandledSpokenFeedbackEvent(std::unique_ptr<ui::Event> event) const;
private:
// ui::EventRewriter:
ui::EventRewriteStatus RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) override;
ui::EventRewriteStatus NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) override;
// The delegate used to send key events to the ChromeVox extension.
mojom::SpokenFeedbackEventRewriterDelegatePtr delegate_;
DISALLOW_COPY_AND_ASSIGN(SpokenFeedbackEventRewriter);
};
} // namespace ash
#endif // ASH_EVENTS_SPOKEN_FEEDBACK_EVENT_REWRITER_H_
// Copyright 2015 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 "ash/events/spoken_feedback_event_rewriter.h"
#include <memory>
#include <vector>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/macros.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_rewriter.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
namespace ash {
namespace {
// An event rewriter that simply records all events that it receives.
class EventRecorder : public ui::EventRewriter {
public:
EventRecorder() = default;
~EventRecorder() override = default;
// ui::EventRewriter:
ui::EventRewriteStatus RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) override {
recorded_event_count_++;
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) override {
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
// Count of events sent to the rewriter.
size_t recorded_event_count_ = 0;
private:
DISALLOW_COPY_AND_ASSIGN(EventRecorder);
};
// A test implementation of the spoken feedback delegate interface.
class TestDelegate : public mojom::SpokenFeedbackEventRewriterDelegate {
public:
TestDelegate() : binding_(this) {}
~TestDelegate() override = default;
mojom::SpokenFeedbackEventRewriterDelegatePtr BindInterface() {
mojom::SpokenFeedbackEventRewriterDelegatePtr ptr;
binding_.Bind(MakeRequest(&ptr));
return ptr;
}
// Count of events sent to the delegate.
size_t recorded_event_count_ = 0;
private:
// SpokenFeedbackEventRewriterDelegate:
void DispatchKeyEventToChromeVox(std::unique_ptr<ui::Event> event) override {
recorded_event_count_++;
}
// The binding that backs the interface pointer held by the event rewriter.
mojo::Binding<ash::mojom::SpokenFeedbackEventRewriterDelegate> binding_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
class SpokenFeedbackEventRewriterTest : public ash::AshTestBase {
public:
SpokenFeedbackEventRewriterTest() {
spoken_feedback_event_rewriter_.SetDelegate(delegate_.BindInterface());
}
void SetUp() override {
ash::AshTestBase::SetUp();
generator_ = &AshTestBase::GetEventGenerator();
CurrentContext()->GetHost()->GetEventSource()->AddEventRewriter(
&spoken_feedback_event_rewriter_);
CurrentContext()->GetHost()->GetEventSource()->AddEventRewriter(
&event_recorder_);
}
void TearDown() override {
CurrentContext()->GetHost()->GetEventSource()->RemoveEventRewriter(
&event_recorder_);
CurrentContext()->GetHost()->GetEventSource()->RemoveEventRewriter(
&spoken_feedback_event_rewriter_);
generator_ = nullptr;
ash::AshTestBase::TearDown();
}
// Flush any messages to the test delegate and return events it has recorded.
size_t GetDelegateRecordedEventCount() {
spoken_feedback_event_rewriter_.get_delegate_for_testing()
->FlushForTesting();
return delegate_.recorded_event_count_;
}
protected:
// A test spoken feedback delegate; simulates ChromeVox.
TestDelegate delegate_;
// Generates ui::Events from simulated user input.
ui::test::EventGenerator* generator_ = nullptr;
// Records events delivered to the next event rewriter after spoken feedback.
EventRecorder event_recorder_;
private:
SpokenFeedbackEventRewriter spoken_feedback_event_rewriter_;
DISALLOW_COPY_AND_ASSIGN(SpokenFeedbackEventRewriterTest);
};
// The delegate should not intercept events when spoken feedback is disabled.
TEST_F(SpokenFeedbackEventRewriterTest, EventsNotConsumedWhenDisabled) {
AccessibilityController* controller =
Shell::Get()->accessibility_controller();
EXPECT_FALSE(controller->IsSpokenFeedbackEnabled());
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1U, event_recorder_.recorded_event_count_);
EXPECT_EQ(0U, GetDelegateRecordedEventCount());
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2U, event_recorder_.recorded_event_count_);
EXPECT_EQ(0U, GetDelegateRecordedEventCount());
generator_->ClickLeftButton();
EXPECT_EQ(4U, event_recorder_.recorded_event_count_);
EXPECT_EQ(0U, GetDelegateRecordedEventCount());
generator_->GestureTapAt(gfx::Point());
EXPECT_EQ(6U, event_recorder_.recorded_event_count_);
EXPECT_EQ(0U, GetDelegateRecordedEventCount());
}
// The delegate should intercept key events when spoken feedback is enabled.
TEST_F(SpokenFeedbackEventRewriterTest, KeyEventsConsumedWhenEnabled) {
AccessibilityController* controller =
Shell::Get()->accessibility_controller();
controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
EXPECT_TRUE(controller->IsSpokenFeedbackEnabled());
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(0U, event_recorder_.recorded_event_count_);
EXPECT_EQ(1U, GetDelegateRecordedEventCount());
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(0U, event_recorder_.recorded_event_count_);
EXPECT_EQ(2U, GetDelegateRecordedEventCount());
generator_->ClickLeftButton();
EXPECT_EQ(2U, event_recorder_.recorded_event_count_);
EXPECT_EQ(2U, GetDelegateRecordedEventCount());
generator_->GestureTapAt(gfx::Point());
EXPECT_EQ(4U, event_recorder_.recorded_event_count_);
EXPECT_EQ(2U, GetDelegateRecordedEventCount());
}
TEST_F(SpokenFeedbackEventRewriterTest, KeysNotEatenWithChromeVoxDisabled) {
AccessibilityController* controller =
Shell::Get()->accessibility_controller();
EXPECT_FALSE(controller->IsSpokenFeedbackEnabled());
// Send Search+Shift+Right.
generator_->PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
EXPECT_EQ(1U, event_recorder_.recorded_event_count_);
generator_->PressKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_EQ(2U, event_recorder_.recorded_event_count_);
// Mock successful commands lookup and dispatch; shouldn't matter either way.
generator_->PressKey(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_EQ(3U, event_recorder_.recorded_event_count_);
// Released keys shouldn't get eaten.
generator_->ReleaseKey(ui::VKEY_RIGHT,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
generator_->ReleaseKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN);
generator_->ReleaseKey(ui::VKEY_LWIN, 0);
EXPECT_EQ(6U, event_recorder_.recorded_event_count_);
// Try releasing more keys.
generator_->ReleaseKey(ui::VKEY_RIGHT, 0);
generator_->ReleaseKey(ui::VKEY_SHIFT, 0);
generator_->ReleaseKey(ui::VKEY_LWIN, 0);
EXPECT_EQ(9U, event_recorder_.recorded_event_count_);
EXPECT_EQ(0U, GetDelegateRecordedEventCount());
}
} // namespace
} // namespace ash
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
module ash.mojom; module ash.mojom;
import "ui/events/mojo/event.mojom";
// Allows a client to implement spoken feedback features; used for ChromeVox.
interface SpokenFeedbackEventRewriterDelegate {
// Used to send key events to the ChromeVox extension.
DispatchKeyEventToChromeVox(ui.mojom.Event event);
};
// Allows clients to toggle some event rewriting behavior. // Allows clients to toggle some event rewriting behavior.
interface EventRewriterController { interface EventRewriterController {
// Enables the KeyboardDrivenEventRewriter, which is disabled by default. // Enables the KeyboardDrivenEventRewriter, which is disabled by default.
...@@ -13,4 +21,12 @@ interface EventRewriterController { ...@@ -13,4 +21,12 @@ interface EventRewriterController {
// If true, Shift + Arrow keys are rewritten to Tab/Shift-Tab keys. // If true, Shift + Arrow keys are rewritten to Tab/Shift-Tab keys.
// This only applies when the KeyboardDrivenEventRewriter is active. // This only applies when the KeyboardDrivenEventRewriter is active.
SetArrowToTabRewritingEnabled(bool enabled); SetArrowToTabRewritingEnabled(bool enabled);
// Set the delegate used by the spoken feedback event rewriter.
SetSpokenFeedbackEventRewriterDelegate(
SpokenFeedbackEventRewriterDelegate delegate);
// Continue dispatch of key events that were unhandled by ChromeVox.
// TODO: Remove this; ChromeVox should not be able to repost unhandled events.
OnUnhandledSpokenFeedbackEvent(ui.mojom.Event event);
}; };
...@@ -240,8 +240,8 @@ source_set("chromeos") { ...@@ -240,8 +240,8 @@ source_set("chromeos") {
"accessibility/magnification_manager.h", "accessibility/magnification_manager.h",
"accessibility/select_to_speak_event_handler.cc", "accessibility/select_to_speak_event_handler.cc",
"accessibility/select_to_speak_event_handler.h", "accessibility/select_to_speak_event_handler.h",
"accessibility/spoken_feedback_event_rewriter.cc", "accessibility/spoken_feedback_event_rewriter_delegate.cc",
"accessibility/spoken_feedback_event_rewriter.h", "accessibility/spoken_feedback_event_rewriter_delegate.h",
"accessibility/switch_access_event_handler.cc", "accessibility/switch_access_event_handler.cc",
"accessibility/switch_access_event_handler.h", "accessibility/switch_access_event_handler.h",
"app_mode/app_launch_utils.cc", "app_mode/app_launch_utils.cc",
...@@ -1838,7 +1838,6 @@ source_set("unit_tests") { ...@@ -1838,7 +1838,6 @@ source_set("unit_tests") {
"../policy/default_geolocation_policy_handler_unittest.cc", "../policy/default_geolocation_policy_handler_unittest.cc",
"../ui/browser_finder_chromeos_unittest.cc", "../ui/browser_finder_chromeos_unittest.cc",
"accessibility/select_to_speak_event_handler_unittest.cc", "accessibility/select_to_speak_event_handler_unittest.cc",
"accessibility/spoken_feedback_event_rewriter_unittest.cc",
"app_mode/startup_app_launcher_unittest.cc", "app_mode/startup_app_launcher_unittest.cc",
"apps/intent_helper/apps_navigation_throttle_unittest.cc", "apps/intent_helper/apps_navigation_throttle_unittest.cc",
"arc/accessibility/arc_accessibility_helper_bridge_unittest.cc", "arc/accessibility/arc_accessibility_helper_bridge_unittest.cc",
......
...@@ -2,110 +2,97 @@ ...@@ -2,110 +2,97 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter.h" #include "chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.h"
#include <string> #include "ash/public/interfaces/constants.mojom.h"
#include <utility>
#include "ash/shell.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h" #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/accessibility/event_handler_common.h" #include "chrome/browser/chromeos/accessibility/event_handler_common.h"
#include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h" #include "content/public/common/service_manager_connection.h"
#include "extensions/browser/extension_host.h" #include "extensions/browser/extension_host.h"
#include "ui/aura/window_tree_host.h" #include "services/service_manager/public/cpp/connector.h"
#include "ui/content_accelerators/accelerator_util.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/events/event_sink.h"
SpokenFeedbackEventRewriterDelegate::SpokenFeedbackEventRewriterDelegate() {}
bool SpokenFeedbackEventRewriterDelegate::IsSpokenFeedbackEnabled() const { SpokenFeedbackEventRewriterDelegate::SpokenFeedbackEventRewriterDelegate()
return chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled(); : binding_(this) {
content::ServiceManagerConnection* connection =
content::ServiceManagerConnection::GetForProcess();
connection->GetConnector()->BindInterface(ash::mojom::kServiceName,
&event_rewriter_controller_ptr_);
// Set this object as the SpokenFeedbackEventRewriterDelegate.
ash::mojom::SpokenFeedbackEventRewriterDelegatePtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
event_rewriter_controller_ptr_->SetSpokenFeedbackEventRewriterDelegate(
std::move(ptr));
} }
bool SpokenFeedbackEventRewriterDelegate::DispatchKeyEventToChromeVox( SpokenFeedbackEventRewriterDelegate::~SpokenFeedbackEventRewriterDelegate() {}
const ui::KeyEvent& key_event,
bool capture) { void SpokenFeedbackEventRewriterDelegate::DispatchKeyEventToChromeVox(
std::unique_ptr<ui::Event> event) {
if (!ShouldDispatchKeyEventToChromeVox(event.get())) {
OnUnhandledSpokenFeedbackEvent(std::move(event));
return;
}
bool capture =
chromeos::AccessibilityManager::Get()->keyboard_listener_capture();
const ui::KeyEvent* key_event = event->AsKeyEvent();
// Always capture the Search key. // Always capture the Search key.
capture |= key_event.IsCommandDown(); capture |= key_event->IsCommandDown();
// Don't capture tab as it gets consumed by Blink so never comes back // Don't capture tab as it gets consumed by Blink so never comes back
// unhandled. In third_party/WebKit/Source/core/input/EventHandler.cpp, a // unhandled. In third_party/WebKit/Source/core/input/EventHandler.cpp, a
// default tab handler consumes tab even when no focusable nodes are found; it // default tab handler consumes tab even when no focusable nodes are found; it
// sets focus to Chrome and eats the event. // sets focus to Chrome and eats the event.
if (key_event.GetDomKey() == ui::DomKey::TAB) if (key_event->GetDomKey() == ui::DomKey::TAB)
capture = false; capture = false;
extensions::ExtensionHost* host = chromeos::GetAccessibilityExtensionHost( extensions::ExtensionHost* host = chromeos::GetAccessibilityExtensionHost(
extension_misc::kChromeVoxExtensionId); extension_misc::kChromeVoxExtensionId);
if (!host)
return false;
// Listen for any unhandled keyboard events from ChromeVox's background page // Listen for any unhandled keyboard events from ChromeVox's background page
// when capturing keys to reinject. // when capturing keys to reinject.
if (capture) host->host_contents()->SetDelegate(capture ? this : nullptr);
host->host_contents()->SetDelegate(this);
else
host->host_contents()->SetDelegate(nullptr);
// Forward all key events to ChromeVox's background page. // Forward the event to ChromeVox's background page.
chromeos::ForwardKeyToExtension(key_event, host); chromeos::ForwardKeyToExtension(*key_event, host);
return capture; if (!capture)
OnUnhandledSpokenFeedbackEvent(std::move(event));
} }
void SpokenFeedbackEventRewriterDelegate::HandleKeyboardEvent( bool SpokenFeedbackEventRewriterDelegate::ShouldDispatchKeyEventToChromeVox(
content::WebContents* source, const ui::Event* event) const {
const content::NativeWebKeyboardEvent& event) { chromeos::AccessibilityManager* accessibility_manager =
ui::KeyEvent key_event(*static_cast<ui::KeyEvent*>(event.os_event)); chromeos::AccessibilityManager::Get();
if (!accessibility_manager->IsSpokenFeedbackEnabled() ||
ui::EventSink* sink = accessibility_manager->keyboard_listener_extension_id().empty() ||
ash::Shell::GetPrimaryRootWindow()->GetHost()->event_sink(); !chromeos::GetAccessibilityExtensionHost(
extension_misc::kChromeVoxExtensionId)) {
if (sink->OnEventFromSource(&key_event).dispatcher_destroyed) { VLOG(1) << "Event sent to Spoken Feedback when disabled or unavailable";
VLOG(0) << "Undispatched key " << key_event.key_code() return false;
<< " due to destroyed dispatcher.";
} }
}
SpokenFeedbackEventRewriter::SpokenFeedbackEventRewriter() {
delegate_.reset(new SpokenFeedbackEventRewriterDelegate());
}
SpokenFeedbackEventRewriter::~SpokenFeedbackEventRewriter() {} if (!event || !event->IsKeyEvent()) {
NOTREACHED() << "Unexpected event sent to Spoken Feedback";
return false;
}
void SpokenFeedbackEventRewriter::SetDelegateForTest( return true;
std::unique_ptr<SpokenFeedbackEventRewriterDelegate> delegate) {
delegate_ = std::move(delegate);
} }
ui::EventRewriteStatus SpokenFeedbackEventRewriter::RewriteEvent( void SpokenFeedbackEventRewriterDelegate::OnUnhandledSpokenFeedbackEvent(
const ui::Event& event, std::unique_ptr<ui::Event> event) const {
std::unique_ptr<ui::Event>* new_event) { event_rewriter_controller_ptr_->OnUnhandledSpokenFeedbackEvent(
if (!delegate_->IsSpokenFeedbackEnabled()) std::move(event));
return ui::EVENT_REWRITE_CONTINUE;
if ((event.type() != ui::ET_KEY_PRESSED &&
event.type() != ui::ET_KEY_RELEASED))
return ui::EVENT_REWRITE_CONTINUE;
std::string extension_id =
chromeos::AccessibilityManager::Get()->keyboard_listener_extension_id();
if (extension_id.empty())
return ui::EVENT_REWRITE_CONTINUE;
bool capture =
chromeos::AccessibilityManager::Get()->keyboard_listener_capture();
const ui::KeyEvent key_event = static_cast<const ui::KeyEvent&>(event);
if (delegate_->DispatchKeyEventToChromeVox(key_event, capture))
return ui::EVENT_REWRITE_DISCARD;
return ui::EVENT_REWRITE_CONTINUE;
} }
ui::EventRewriteStatus SpokenFeedbackEventRewriter::NextDispatchEvent( void SpokenFeedbackEventRewriterDelegate::HandleKeyboardEvent(
const ui::Event& last_event, content::WebContents* source,
std::unique_ptr<ui::Event>* new_event) { const content::NativeWebKeyboardEvent& event) {
return ui::EVENT_REWRITE_CONTINUE; OnUnhandledSpokenFeedbackEvent(
ui::Event::Clone(*static_cast<ui::Event*>(event.os_event)));
} }
...@@ -2,67 +2,46 @@ ...@@ -2,67 +2,46 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_H_ #ifndef CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_DELEGATE_H_
#define CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_H_ #define CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_DELEGATE_H_
#include <vector> #include <memory>
#include "ash/public/interfaces/event_rewriter_controller.mojom.h"
#include "base/macros.h" #include "base/macros.h"
#include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_delegate.h"
#include "ui/events/event_rewriter.h" #include "mojo/public/cpp/bindings/binding.h"
namespace ui { // Passes key events from Ash's EventRewriter to the ChromeVox extension code.
class KeyEvent; // Reports ChromeVox's unhandled key events back to Ash for continued dispatch.
} // TODO(http://crbug.com/839541): Avoid reposting unhandled events.
// Receives requests for spoken feedback enabled state and command dispatch.
class SpokenFeedbackEventRewriterDelegate class SpokenFeedbackEventRewriterDelegate
: public content::WebContentsDelegate { : public ash::mojom::SpokenFeedbackEventRewriterDelegate,
public content::WebContentsDelegate {
public: public:
SpokenFeedbackEventRewriterDelegate(); SpokenFeedbackEventRewriterDelegate();
~SpokenFeedbackEventRewriterDelegate() override {} ~SpokenFeedbackEventRewriterDelegate() override;
// ui::mojom::SpokenFeedbackEventRewriterDelegate:
void DispatchKeyEventToChromeVox(std::unique_ptr<ui::Event> event) override;
// Returns true when ChromeVox is enabled. private:
virtual bool IsSpokenFeedbackEnabled() const; // Returns whether the event should be dispatched to the ChromeVox extension.
bool ShouldDispatchKeyEventToChromeVox(const ui::Event* event) const;
// Returns true when |key_event| is dispatched to ChromeVox. // Reports unhandled key events to the EventRewriterController for dispatch.
virtual bool DispatchKeyEventToChromeVox(const ui::KeyEvent& key_event, void OnUnhandledSpokenFeedbackEvent(std::unique_ptr<ui::Event> event) const;
bool capture);
// WebContentsDelegate: // WebContentsDelegate:
void HandleKeyboardEvent( void HandleKeyboardEvent(
content::WebContents* source, content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override; const content::NativeWebKeyboardEvent& event) override;
private: ash::mojom::EventRewriterControllerPtr event_rewriter_controller_ptr_;
DISALLOW_COPY_AND_ASSIGN(SpokenFeedbackEventRewriterDelegate);
};
// SpokenFeedbackEventRewriter discards all keyboard events mapped by the spoken
// feedback manifest commands block. It dispatches the associated command name
// directly to spoken feedback. This only occurs whenever spoken feedback is
// enabled.
class SpokenFeedbackEventRewriter : public ui::EventRewriter {
public:
SpokenFeedbackEventRewriter();
~SpokenFeedbackEventRewriter() override;
void SetDelegateForTest(
std::unique_ptr<SpokenFeedbackEventRewriterDelegate> delegate);
private: mojo::Binding<ash::mojom::SpokenFeedbackEventRewriterDelegate> binding_;
// EventRewriter:
ui::EventRewriteStatus RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) override;
ui::EventRewriteStatus NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) override;
// Active delegate (used for testing).
std::unique_ptr<SpokenFeedbackEventRewriterDelegate> delegate_;
DISALLOW_COPY_AND_ASSIGN(SpokenFeedbackEventRewriter); DISALLOW_COPY_AND_ASSIGN(SpokenFeedbackEventRewriterDelegate);
}; };
#endif // CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_H_ #endif // CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_DELEGATE_H_
// Copyright 2015 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/chromeos/accessibility/spoken_feedback_event_rewriter.h"
#include <memory>
#include <vector>
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/macros.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
// Records all key events.
class EventCapturer : public ui::EventHandler {
public:
EventCapturer() {}
~EventCapturer() override {}
void Reset() { events_.clear(); }
void OnEvent(ui::Event* event) override {
if (event->IsKeyEvent())
events_.push_back(std::make_unique<ui::KeyEvent>(*event->AsKeyEvent()));
}
const std::vector<std::unique_ptr<ui::KeyEvent>>& captured_events() const {
return events_;
}
private:
std::vector<std::unique_ptr<ui::KeyEvent>> events_;
DISALLOW_COPY_AND_ASSIGN(EventCapturer);
};
class TestDelegate : public SpokenFeedbackEventRewriterDelegate {
public:
TestDelegate()
: is_spoken_feedback_enabled_(false), dispatch_result_(false) {}
~TestDelegate() override {}
void set_is_spoken_feedback_enabled(bool enabled) {
is_spoken_feedback_enabled_ = enabled;
}
void set_dispatch_result(bool result) { dispatch_result_ = result; }
private:
// SpokenFeedbackEventRewriterDelegate:
bool IsSpokenFeedbackEnabled() const override {
return is_spoken_feedback_enabled_;
}
bool DispatchKeyEventToChromeVox(const ui::KeyEvent& key_event,
bool capture) override {
return dispatch_result_;
}
bool is_spoken_feedback_enabled_;
bool dispatch_result_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
class SpokenFeedbackEventRewriterTest : public ash::AshTestBase {
public:
SpokenFeedbackEventRewriterTest()
: generator_(nullptr),
spoken_feedback_event_rewriter_(new SpokenFeedbackEventRewriter()) {
delegate_ = new TestDelegate();
spoken_feedback_event_rewriter_->SetDelegateForTest(
std::unique_ptr<TestDelegate>(delegate_));
}
void SetUp() override {
ash::AshTestBase::SetUp();
generator_ = &AshTestBase::GetEventGenerator();
CurrentContext()->AddPreTargetHandler(&event_capturer_);
CurrentContext()->GetHost()->GetEventSource()->AddEventRewriter(
spoken_feedback_event_rewriter_.get());
}
void TearDown() override {
CurrentContext()->GetHost()->GetEventSource()->RemoveEventRewriter(
spoken_feedback_event_rewriter_.get());
CurrentContext()->RemovePreTargetHandler(&event_capturer_);
generator_ = nullptr;
ash::AshTestBase::TearDown();
}
protected:
TestDelegate* delegate_;
ui::test::EventGenerator* generator_;
EventCapturer event_capturer_;
private:
std::unique_ptr<SpokenFeedbackEventRewriter> spoken_feedback_event_rewriter_;
DISALLOW_COPY_AND_ASSIGN(SpokenFeedbackEventRewriterTest);
};
TEST_F(SpokenFeedbackEventRewriterTest, KeysNotEatenWithChromeVoxDisabled) {
// Send Search+Shift+Right.
generator_->PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ASSERT_EQ(1U, event_capturer_.captured_events().size());
generator_->PressKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
ASSERT_EQ(2U, event_capturer_.captured_events().size());
// Mock successful commands lookup and dispatch; shouldn't matter either way.
delegate_->set_dispatch_result(true);
generator_->PressKey(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
ASSERT_EQ(3U, event_capturer_.captured_events().size());
// Released keys shouldn't get eaten.
delegate_->set_dispatch_result(false);
generator_->ReleaseKey(ui::VKEY_RIGHT,
ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
generator_->ReleaseKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN);
generator_->ReleaseKey(ui::VKEY_LWIN, 0);
ASSERT_EQ(6U, event_capturer_.captured_events().size());
// Try releasing more keys.
generator_->ReleaseKey(ui::VKEY_RIGHT, 0);
generator_->ReleaseKey(ui::VKEY_SHIFT, 0);
generator_->ReleaseKey(ui::VKEY_LWIN, 0);
ASSERT_EQ(9U, event_capturer_.captured_events().size());
}
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h" #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/accessibility/magnification_manager.h" #include "chrome/browser/chromeos/accessibility/magnification_manager.h"
#include "chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter.h" #include "chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.h"
#include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h" #include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_launch_error.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_launch_error.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
...@@ -1061,12 +1061,14 @@ void ChromeBrowserMainPartsChromeos::PostBrowserStart() { ...@@ -1061,12 +1061,14 @@ void ChromeBrowserMainPartsChromeos::PostBrowserStart() {
event_rewriter_controller_ptr->SetKeyboardDrivenEventRewriterEnabled(true); event_rewriter_controller_ptr->SetKeyboardDrivenEventRewriterEnabled(true);
} }
// Construct a delegate to connect ChromeVox and SpokenFeedbackEventRewriter.
spoken_feedback_event_rewriter_delegate_ =
std::make_unique<SpokenFeedbackEventRewriterDelegate>();
if (chromeos::GetAshConfig() != ash::Config::MASH) { if (chromeos::GetAshConfig() != ash::Config::MASH) {
// TODO(mash): Support EventRewriterController; see crbug.com/647781 // TODO(mash): Support EventRewriterController; see crbug.com/647781
ash::EventRewriterController* event_rewriter_controller = ash::EventRewriterController* event_rewriter_controller =
ash::Shell::Get()->event_rewriter_controller(); ash::Shell::Get()->event_rewriter_controller();
event_rewriter_controller->AddEventRewriter(
std::unique_ptr<ui::EventRewriter>(new SpokenFeedbackEventRewriter()));
event_rewriter_delegate_ = std::make_unique<EventRewriterDelegateImpl>(); event_rewriter_delegate_ = std::make_unique<EventRewriterDelegateImpl>();
event_rewriter_controller->AddEventRewriter( event_rewriter_controller->AddEventRewriter(
std::make_unique<ui::EventRewriterChromeOS>( std::make_unique<ui::EventRewriterChromeOS>(
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "chromeos/system/version_loader.h" #include "chromeos/system/version_loader.h"
class NotificationPlatformBridge; class NotificationPlatformBridge;
class SpokenFeedbackEventRewriterDelegate;
namespace lock_screen_apps { namespace lock_screen_apps {
class StateController; class StateController;
...@@ -109,6 +110,10 @@ class ChromeBrowserMainPartsChromeos : public ChromeBrowserMainPartsLinux { ...@@ -109,6 +110,10 @@ class ChromeBrowserMainPartsChromeos : public ChromeBrowserMainPartsLinux {
std::unique_ptr<EventRewriterDelegateImpl> event_rewriter_delegate_; std::unique_ptr<EventRewriterDelegateImpl> event_rewriter_delegate_;
// Handles event dispatch to the spoken feedback extension (ChromeVox).
std::unique_ptr<SpokenFeedbackEventRewriterDelegate>
spoken_feedback_event_rewriter_delegate_;
scoped_refptr<chromeos::ExternalMetrics> external_metrics_; scoped_refptr<chromeos::ExternalMetrics> external_metrics_;
std::unique_ptr<arc::ArcServiceLauncher> arc_service_launcher_; std::unique_ptr<arc::ArcServiceLauncher> arc_service_launcher_;
......
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