blob: 61dd637d98d673a7f3b18852aa27d4fa5799fb7f [file] [log] [blame]
// 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;
// Count of captured events sent to the delegate.
size_t captured_event_count_ = 0;
private:
// SpokenFeedbackEventRewriterDelegate:
void DispatchKeyEventToChromeVox(std::unique_ptr<ui::Event> event,
bool capture) override {
recorded_event_count_++;
if (capture)
captured_event_count_++;
}
void DispatchMouseEventToChromeVox(
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_;
}
size_t GetDelegateCapturedEventCount() {
spoken_feedback_event_rewriter_.get_delegate_for_testing()
->FlushForTesting();
return delegate_.captured_event_count_;
}
void SetDelegateCaptureAllKeys(bool value) {
spoken_feedback_event_rewriter_.set_capture_all_keys(value);
}
void ExpectCounts(size_t expected_recorded_count,
size_t expected_delegate_count,
size_t expected_captured_count) {
EXPECT_EQ(expected_recorded_count, event_recorder_.recorded_event_count_);
EXPECT_EQ(expected_delegate_count, GetDelegateRecordedEventCount());
EXPECT_EQ(expected_captured_count, GetDelegateCapturedEventCount());
}
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->spoken_feedback_enabled());
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->spoken_feedback_enabled());
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1U, event_recorder_.recorded_event_count_);
EXPECT_EQ(1U, GetDelegateRecordedEventCount());
EXPECT_EQ(0U, GetDelegateCapturedEventCount());
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2U, event_recorder_.recorded_event_count_);
EXPECT_EQ(2U, GetDelegateRecordedEventCount());
EXPECT_EQ(0U, GetDelegateCapturedEventCount());
generator_->ClickLeftButton();
EXPECT_EQ(4U, event_recorder_.recorded_event_count_);
EXPECT_EQ(2U, GetDelegateRecordedEventCount());
EXPECT_EQ(0U, GetDelegateCapturedEventCount());
generator_->GestureTapAt(gfx::Point());
EXPECT_EQ(6U, event_recorder_.recorded_event_count_);
EXPECT_EQ(2U, GetDelegateRecordedEventCount());
EXPECT_EQ(0U, GetDelegateCapturedEventCount());
}
// 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->spoken_feedback_enabled());
// 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());
}
TEST_F(SpokenFeedbackEventRewriterTest, KeyEventsCaptured) {
AccessibilityController* controller =
Shell::Get()->accessibility_controller();
controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
EXPECT_TRUE(controller->spoken_feedback_enabled());
// Initialize expected counts as variables for easier maintaiblity.
size_t recorded_count = 0;
size_t delegate_count = 0;
size_t captured_count = 0;
// Anything with Search gets captured.
generator_->PressKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
generator_->ReleaseKey(ui::VKEY_LWIN, ui::EF_COMMAND_DOWN);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Tab never gets captured.
generator_->PressKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator_->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
// A client requested capture of all keys.
SetDelegateCaptureAllKeys(true);
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(recorded_count, ++delegate_count, ++captured_count);
// Tab never gets captured even with explicit client request for all keys.
generator_->PressKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator_->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
// A client requested to not capture all keys.
SetDelegateCaptureAllKeys(false);
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
ExpectCounts(++recorded_count, ++delegate_count, captured_count);
}
} // namespace
} // namespace ash