blob: c709524d415be94a279d0e3c1e651bf0304e7a22 [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_impl.h"
#include "ash/public/cpp/spoken_feedback_event_rewriter_delegate.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"
#include "ui/events/test/test_event_rewriter.h"
namespace ash {
namespace {
// A test implementation of the spoken feedback delegate interface.
class TestDelegate : public SpokenFeedbackEventRewriterDelegate {
public:
TestDelegate() = default;
~TestDelegate() override = default;
// 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_++;
}
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
class SpokenFeedbackEventRewriterTest : public ash::AshTestBase {
public:
SpokenFeedbackEventRewriterTest() = default;
void SetUp() override {
ash::AshTestBase::SetUp();
generator_ = AshTestBase::GetEventGenerator();
spoken_feedback_event_rewriter_.set_delegate(&delegate_);
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_);
spoken_feedback_event_rewriter_.set_delegate(nullptr);
generator_ = nullptr;
ash::AshTestBase::TearDown();
}
size_t delegate_recorded_event_count() {
return delegate_.recorded_event_count_;
}
size_t delegate_captured_event_count() {
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,
static_cast<size_t>(event_recorder_.events_seen()));
EXPECT_EQ(expected_delegate_count, delegate_recorded_event_count());
EXPECT_EQ(expected_captured_count, delegate_captured_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.
ui::test::TestEventRewriter 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) {
AccessibilityControllerImpl* controller =
Shell::Get()->accessibility_controller();
EXPECT_FALSE(controller->spoken_feedback_enabled());
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1, event_recorder_.events_seen());
EXPECT_EQ(0U, delegate_recorded_event_count());
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2, event_recorder_.events_seen());
EXPECT_EQ(0U, delegate_recorded_event_count());
generator_->ClickLeftButton();
EXPECT_EQ(4, event_recorder_.events_seen());
EXPECT_EQ(0U, delegate_recorded_event_count());
generator_->GestureTapAt(gfx::Point());
EXPECT_EQ(6, event_recorder_.events_seen());
EXPECT_EQ(0U, delegate_recorded_event_count());
}
// The delegate should intercept key events when spoken feedback is enabled.
TEST_F(SpokenFeedbackEventRewriterTest, KeyEventsConsumedWhenEnabled) {
AccessibilityControllerImpl* 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(1, event_recorder_.events_seen());
EXPECT_EQ(1U, delegate_recorded_event_count());
EXPECT_EQ(0U, delegate_captured_event_count());
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2, event_recorder_.events_seen());
EXPECT_EQ(2U, delegate_recorded_event_count());
EXPECT_EQ(0U, delegate_captured_event_count());
generator_->ClickLeftButton();
EXPECT_EQ(4, event_recorder_.events_seen());
EXPECT_EQ(2U, delegate_recorded_event_count());
EXPECT_EQ(0U, delegate_captured_event_count());
generator_->GestureTapAt(gfx::Point());
EXPECT_EQ(6, event_recorder_.events_seen());
EXPECT_EQ(2U, delegate_recorded_event_count());
EXPECT_EQ(0U, delegate_captured_event_count());
}
// Asynchronously unhandled events should be sent to subsequent rewriters.
TEST_F(SpokenFeedbackEventRewriterTest, UnhandledEventsSentToOtherRewriters) {
// Before it can forward unhandled events, SpokenFeedbackEventRewriter
// must have seen at least one event in the first place.
generator_->PressKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(1, event_recorder_.events_seen());
generator_->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
EXPECT_EQ(2, event_recorder_.events_seen());
spoken_feedback_event_rewriter_.OnUnhandledSpokenFeedbackEvent(
std::make_unique<ui::KeyEvent>(ui::ET_KEY_PRESSED, ui::VKEY_A,
ui::EF_NONE));
EXPECT_EQ(3, event_recorder_.events_seen());
spoken_feedback_event_rewriter_.OnUnhandledSpokenFeedbackEvent(
std::make_unique<ui::KeyEvent>(ui::ET_KEY_RELEASED, ui::VKEY_A,
ui::EF_NONE));
EXPECT_EQ(4, event_recorder_.events_seen());
}
TEST_F(SpokenFeedbackEventRewriterTest, KeysNotEatenWithChromeVoxDisabled) {
AccessibilityControllerImpl* 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(1, event_recorder_.events_seen());
generator_->PressKey(ui::VKEY_SHIFT, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN);
EXPECT_EQ(2, event_recorder_.events_seen());
// 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(3, event_recorder_.events_seen());
// 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(6, event_recorder_.events_seen());
// Try releasing more keys.
generator_->ReleaseKey(ui::VKEY_RIGHT, 0);
generator_->ReleaseKey(ui::VKEY_SHIFT, 0);
generator_->ReleaseKey(ui::VKEY_LWIN, 0);
EXPECT_EQ(9, event_recorder_.events_seen());
EXPECT_EQ(0U, delegate_recorded_event_count());
}
TEST_F(SpokenFeedbackEventRewriterTest, KeyEventsCaptured) {
AccessibilityControllerImpl* 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