| // 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/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) { |
| AccessibilityController* 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) { |
| 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(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) { |
| 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(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) { |
| 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 |