Commit b6d58e56 authored by Mike Wasserman's avatar Mike Wasserman Committed by Commit Bot

Reland mash: Move SpokenFeedbackEventRewriter to ash; add mojo delegate

The original crrev.com/c/1038819 broke volume keys: crbug.com/839683
PS2 fixes that by reposting events to other rewriters and adds a test.

ORIGINAL CHANGE DESCRIPTION FOLLOWS:

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)

TBR=jamescook@chromium.org

Bug: 647781
Test: No regressions with ChromeVox spoken feedback on Chrome OS.
Change-Id: Id2e778fa3a82aee7f568244a5c2596ab86919a1d
Reviewed-on: https://chromium-review.googlesource.com/1044780
Commit-Queue: Michael Wasserman <msw@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarDavid Tseng <dtseng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#556458}
parent c83f8839
......@@ -209,6 +209,8 @@ component("ash") {
"events/event_rewriter_controller.h",
"events/keyboard_driven_event_rewriter.cc",
"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.h",
"first_run/first_run_helper.cc",
......@@ -1581,6 +1583,7 @@ test("ash_unittests") {
"drag_drop/drag_drop_tracker_unittest.cc",
"drag_drop/drag_image_view_unittest.cc",
"events/keyboard_driven_event_rewriter_unittest.cc",
"events/spoken_feedback_event_rewriter_unittest.cc",
"extended_desktop_unittest.cc",
"first_run/first_run_helper_unittest.cc",
"focus_cycler_unittest.cc",
......
per-file spoken_feedback_event_rewriter*=file://ui/accessibility/OWNERS
......@@ -9,10 +9,12 @@
#include "ash/display/mirror_window_controller.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/events/keyboard_driven_event_rewriter.h"
#include "ash/events/spoken_feedback_event_rewriter.h"
#include "ash/shell.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event_rewriter.h"
#include "ui/events/event_sink.h"
#include "ui/events/event_source.h"
namespace ash {
......@@ -25,6 +27,11 @@ EventRewriterController::EventRewriterController() {
std::make_unique<KeyboardDrivenEventRewriter>();
keyboard_driven_event_rewriter_ = keyboard_driven_event_rewriter.get();
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() {
......@@ -67,6 +74,17 @@ void EventRewriterController::SetArrowToTabRewritingEnabled(bool 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) {
for (const auto& rewriter : rewriters_)
host->GetEventSource()->AddEventRewriter(rewriter.get());
......
......@@ -22,6 +22,7 @@ class EventSource;
namespace ash {
class KeyboardDrivenEventRewriter;
class SpokenFeedbackEventRewriter;
// 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.
......@@ -42,6 +43,10 @@ class ASH_EXPORT EventRewriterController
// mojom::EventRewriterController:
void SetKeyboardDrivenEventRewriterEnabled(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:
void OnWindowInitialized(aura::Window* window) override {}
......@@ -51,9 +56,12 @@ class ASH_EXPORT EventRewriterController
// The |EventRewriter|s managed by this controller.
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_;
// A weak pointer to the SpokenFeedbackEventRewriter owned in |rewriters_|.
SpokenFeedbackEventRewriter* spoken_feedback_event_rewriter_;
// Bindings for the EventRewriterController mojo interface.
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, not the primary one.
ui::EventSource* source =
Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource();
if (SendEventToEventSource(source, 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_;
SpokenFeedbackEventRewriter spoken_feedback_event_rewriter_;
private:
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());
}
// Asynchronously unhandled events should be sent to subsequent rewriters.
TEST_F(SpokenFeedbackEventRewriterTest, UnhandledEventsSentToOtherRewriters) {
spoken_feedback_event_rewriter_.OnUnhandledSpokenFeedbackEvent(
std::make_unique<ui::KeyEvent>(ui::ET_KEY_PRESSED, ui::VKEY_A,
ui::EF_NONE));
EXPECT_EQ(1U, event_recorder_.recorded_event_count_);
spoken_feedback_event_rewriter_.OnUnhandledSpokenFeedbackEvent(
std::make_unique<ui::KeyEvent>(ui::ET_KEY_RELEASED, ui::VKEY_A,
ui::EF_NONE));
EXPECT_EQ(2U, event_recorder_.recorded_event_count_);
}
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 @@
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.
interface EventRewriterController {
// Enables the KeyboardDrivenEventRewriter, which is disabled by default.
......@@ -13,4 +21,12 @@ interface EventRewriterController {
// If true, Shift + Arrow keys are rewritten to Tab/Shift-Tab keys.
// This only applies when the KeyboardDrivenEventRewriter is active.
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(crbug.com/839541): ChromeVox should not repost unhandled events.
OnUnhandledSpokenFeedbackEvent(ui.mojom.Event event);
};
......@@ -240,8 +240,8 @@ source_set("chromeos") {
"accessibility/magnification_manager.h",
"accessibility/select_to_speak_event_handler.cc",
"accessibility/select_to_speak_event_handler.h",
"accessibility/spoken_feedback_event_rewriter.cc",
"accessibility/spoken_feedback_event_rewriter.h",
"accessibility/spoken_feedback_event_rewriter_delegate.cc",
"accessibility/spoken_feedback_event_rewriter_delegate.h",
"accessibility/switch_access_event_handler.cc",
"accessibility/switch_access_event_handler.h",
"app_mode/app_launch_utils.cc",
......@@ -1834,7 +1834,6 @@ source_set("unit_tests") {
"../policy/default_geolocation_policy_handler_unittest.cc",
"../ui/browser_finder_chromeos_unittest.cc",
"accessibility/select_to_speak_event_handler_unittest.cc",
"accessibility/spoken_feedback_event_rewriter_unittest.cc",
"app_mode/startup_app_launcher_unittest.cc",
"apps/intent_helper/apps_navigation_throttle_unittest.cc",
"arc/accessibility/arc_accessibility_helper_bridge_unittest.cc",
......
......@@ -2,110 +2,97 @@
// 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 "chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.h"
#include <string>
#include <utility>
#include "ash/shell.h"
#include "ash/public/interfaces/constants.mojom.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/accessibility/event_handler_common.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 "extensions/browser/event_router.h"
#include "content/public/common/service_manager_connection.h"
#include "extensions/browser/extension_host.h"
#include "ui/aura/window_tree_host.h"
#include "ui/content_accelerators/accelerator_util.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/events/event.h"
#include "ui/events/event_sink.h"
SpokenFeedbackEventRewriterDelegate::SpokenFeedbackEventRewriterDelegate() {}
bool SpokenFeedbackEventRewriterDelegate::IsSpokenFeedbackEnabled() const {
return chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled();
SpokenFeedbackEventRewriterDelegate::SpokenFeedbackEventRewriterDelegate()
: 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(
const ui::KeyEvent& key_event,
bool capture) {
SpokenFeedbackEventRewriterDelegate::~SpokenFeedbackEventRewriterDelegate() {}
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.
capture |= key_event.IsCommandDown();
capture |= key_event->IsCommandDown();
// 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
// default tab handler consumes tab even when no focusable nodes are found; it
// sets focus to Chrome and eats the event.
if (key_event.GetDomKey() == ui::DomKey::TAB)
if (key_event->GetDomKey() == ui::DomKey::TAB)
capture = false;
extensions::ExtensionHost* host = chromeos::GetAccessibilityExtensionHost(
extension_misc::kChromeVoxExtensionId);
if (!host)
return false;
// Listen for any unhandled keyboard events from ChromeVox's background page
// when capturing keys to reinject.
if (capture)
host->host_contents()->SetDelegate(this);
else
host->host_contents()->SetDelegate(nullptr);
host->host_contents()->SetDelegate(capture ? this : nullptr);
// Forward all key events to ChromeVox's background page.
chromeos::ForwardKeyToExtension(key_event, host);
// Forward the event to ChromeVox's background page.
chromeos::ForwardKeyToExtension(*key_event, host);
return capture;
if (!capture)
OnUnhandledSpokenFeedbackEvent(std::move(event));
}
void SpokenFeedbackEventRewriterDelegate::HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) {
ui::KeyEvent key_event(*static_cast<ui::KeyEvent*>(event.os_event));
ui::EventSink* sink =
ash::Shell::GetPrimaryRootWindow()->GetHost()->event_sink();
if (sink->OnEventFromSource(&key_event).dispatcher_destroyed) {
VLOG(0) << "Undispatched key " << key_event.key_code()
<< " due to destroyed dispatcher.";
bool SpokenFeedbackEventRewriterDelegate::ShouldDispatchKeyEventToChromeVox(
const ui::Event* event) const {
chromeos::AccessibilityManager* accessibility_manager =
chromeos::AccessibilityManager::Get();
if (!accessibility_manager->IsSpokenFeedbackEnabled() ||
accessibility_manager->keyboard_listener_extension_id().empty() ||
!chromeos::GetAccessibilityExtensionHost(
extension_misc::kChromeVoxExtensionId)) {
VLOG(1) << "Event sent to Spoken Feedback when disabled or unavailable";
return false;
}
}
SpokenFeedbackEventRewriter::SpokenFeedbackEventRewriter() {
delegate_.reset(new SpokenFeedbackEventRewriterDelegate());
}
SpokenFeedbackEventRewriter::~SpokenFeedbackEventRewriter() {}
if (!event || !event->IsKeyEvent()) {
NOTREACHED() << "Unexpected event sent to Spoken Feedback";
return false;
}
void SpokenFeedbackEventRewriter::SetDelegateForTest(
std::unique_ptr<SpokenFeedbackEventRewriterDelegate> delegate) {
delegate_ = std::move(delegate);
return true;
}
ui::EventRewriteStatus SpokenFeedbackEventRewriter::RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) {
if (!delegate_->IsSpokenFeedbackEnabled())
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;
void SpokenFeedbackEventRewriterDelegate::OnUnhandledSpokenFeedbackEvent(
std::unique_ptr<ui::Event> event) const {
event_rewriter_controller_ptr_->OnUnhandledSpokenFeedbackEvent(
std::move(event));
}
ui::EventRewriteStatus SpokenFeedbackEventRewriter::NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) {
return ui::EVENT_REWRITE_CONTINUE;
void SpokenFeedbackEventRewriterDelegate::HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) {
OnUnhandledSpokenFeedbackEvent(
ui::Event::Clone(*static_cast<ui::Event*>(event.os_event)));
}
......@@ -2,67 +2,46 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_SPOKEN_FEEDBACK_EVENT_REWRITER_H_
#define 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_DELEGATE_H_
#include <vector>
#include <memory>
#include "ash/public/interfaces/event_rewriter_controller.mojom.h"
#include "base/macros.h"
#include "content/public/browser/web_contents_delegate.h"
#include "ui/events/event_rewriter.h"
#include "mojo/public/cpp/bindings/binding.h"
namespace ui {
class KeyEvent;
}
// Receives requests for spoken feedback enabled state and command dispatch.
// Passes key events from Ash's EventRewriter to the ChromeVox extension code.
// Reports ChromeVox's unhandled key events back to Ash for continued dispatch.
// TODO(http://crbug.com/839541): Avoid reposting unhandled events.
class SpokenFeedbackEventRewriterDelegate
: public content::WebContentsDelegate {
: public ash::mojom::SpokenFeedbackEventRewriterDelegate,
public content::WebContentsDelegate {
public:
SpokenFeedbackEventRewriterDelegate();
~SpokenFeedbackEventRewriterDelegate() override {}
~SpokenFeedbackEventRewriterDelegate() override;
// ui::mojom::SpokenFeedbackEventRewriterDelegate:
void DispatchKeyEventToChromeVox(std::unique_ptr<ui::Event> event) override;
// Returns true when ChromeVox is enabled.
virtual bool IsSpokenFeedbackEnabled() const;
private:
// 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.
virtual bool DispatchKeyEventToChromeVox(const ui::KeyEvent& key_event,
bool capture);
// Reports unhandled key events to the EventRewriterController for dispatch.
void OnUnhandledSpokenFeedbackEvent(std::unique_ptr<ui::Event> event) const;
// WebContentsDelegate:
void HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override;
private:
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);
ash::mojom::EventRewriterControllerPtr event_rewriter_controller_ptr_;
private:
// 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_;
mojo::Binding<ash::mojom::SpokenFeedbackEventRewriterDelegate> binding_;
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 @@
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/accessibility/accessibility_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/kiosk_app_launch_error.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
......@@ -1028,12 +1028,14 @@ void ChromeBrowserMainPartsChromeos::PostBrowserStart() {
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) {
// TODO(mash): Support EventRewriterController; see crbug.com/647781
ash::EventRewriterController* 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_controller->AddEventRewriter(
std::make_unique<ui::EventRewriterChromeOS>(
......
......@@ -16,6 +16,7 @@
#include "chromeos/system/version_loader.h"
class NotificationPlatformBridge;
class SpokenFeedbackEventRewriterDelegate;
namespace lock_screen_apps {
class StateController;
......@@ -107,6 +108,10 @@ class ChromeBrowserMainPartsChromeos : public ChromeBrowserMainPartsLinux {
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_;
std::unique_ptr<arc::ArcServiceLauncher> arc_service_launcher_;
......
......@@ -130,6 +130,7 @@ component("events") {
"event_modifiers.h",
"event_processor.cc",
"event_processor.h",
"event_rewriter.cc",
"event_rewriter.h",
"event_sink.h",
"event_source.cc",
......
// Copyright 2018 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 "ui/events/event_rewriter.h"
#include "ui/events/event_source.h"
namespace ui {
EventDispatchDetails EventRewriter::SendEventToEventSource(EventSource* source,
Event* event) const {
return source->SendEventToSinkFromRewriter(event, this);
}
} // namespace ui
......@@ -7,11 +7,14 @@
#include <memory>
#include "base/macros.h"
#include "ui/events/event_dispatcher.h"
#include "ui/events/events_export.h"
namespace ui {
class Event;
class EventSource;
// Return status of EventRewriter operations; see that class below.
enum EventRewriteStatus {
......@@ -42,7 +45,8 @@ enum EventRewriteStatus {
// before being dispatched from EventSource to EventSink.
class EVENTS_EXPORT EventRewriter {
public:
virtual ~EventRewriter() {}
EventRewriter() = default;
virtual ~EventRewriter() = default;
// Potentially rewrites (replaces) an event, or requests it be discarded.
// or discards an event. If the rewriter wants to rewrite an event, and
......@@ -62,6 +66,15 @@ class EVENTS_EXPORT EventRewriter {
virtual EventRewriteStatus NextDispatchEvent(
const Event& last_event,
std::unique_ptr<Event>* new_event) = 0;
protected:
// A helper that calls a protected EventSource function, which sends the event
// to subsequent event rewriters on the source and onto its event sink.
EventDispatchDetails SendEventToEventSource(EventSource* source,
Event* event) const;
private:
DISALLOW_COPY_AND_ASSIGN(EventRewriter);
};
} // namespace ui
......
......@@ -41,6 +41,12 @@ void EventSource::RemoveEventRewriter(EventRewriter* rewriter) {
}
EventDispatchDetails EventSource::SendEventToSink(Event* event) {
return SendEventToSinkFromRewriter(event, nullptr);
}
EventDispatchDetails EventSource::SendEventToSinkFromRewriter(
Event* event,
const EventRewriter* rewriter) {
std::unique_ptr<ui::Event> event_for_rewriting_ptr;
Event* event_for_rewriting = event;
if (!rewriter_list_.empty() && IsLocatedEventWithDifferentLocations(*event)) {
......@@ -56,7 +62,13 @@ EventDispatchDetails EventSource::SendEventToSink(Event* event) {
EventRewriteStatus status = EVENT_REWRITE_CONTINUE;
EventRewriterList::const_iterator it = rewriter_list_.begin(),
end = rewriter_list_.end();
// If a rewriter reposted |event|, only send it to subsequent rewriters.
bool send_to_rewriter = rewriter == nullptr;
for (; it != end; ++it) {
if (!send_to_rewriter) {
send_to_rewriter |= (*it == rewriter);
continue;
}
status = (*it)->RewriteEvent(*event_for_rewriting, &rewritten_event);
if (status == EVENT_REWRITE_DISCARD) {
CHECK(!rewritten_event);
......
......@@ -34,15 +34,25 @@ class EVENTS_EXPORT EventSource {
void RemoveEventRewriter(EventRewriter* rewriter);
protected:
// Sends the event through all rewriters and onto the source's EventSink.
EventDispatchDetails SendEventToSink(Event* event);
// Sends the event through the rewriters and onto the source's EventSink.
// If |rewriter| is valid, |event| is only sent to the subsequent rewriters.
// This is used for asynchronous reposting of events processed by |rewriter|.
EventDispatchDetails SendEventToSinkFromRewriter(
Event* event,
const EventRewriter* rewriter);
private:
friend class EventRewriter;
friend class EventSourceTestApi;
EventDispatchDetails DeliverEventToSink(Event* event);
typedef std::vector<EventRewriter*> EventRewriterList;
EventRewriterList rewriter_list_;
DISALLOW_COPY_AND_ASSIGN(EventSource);
};
......
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