| // 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 "components/exo/keyboard.h" |
| |
| #include "ash/accelerators/accelerator_controller_impl.h" |
| #include "ash/accessibility/accessibility_controller_impl.h" |
| #include "ash/constants/app_types.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/public/cpp/external_arc/overlay/arc_overlay_manager.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_helper.h" |
| #include "ash/test/test_widget_builder.h" |
| #include "ash/test/test_window_builder.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "ash/wm/desks/desks_test_util.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "base/run_loop.h" |
| #include "components/exo/buffer.h" |
| #include "components/exo/keyboard_delegate.h" |
| #include "components/exo/keyboard_device_configuration_delegate.h" |
| #include "components/exo/keyboard_modifiers.h" |
| #include "components/exo/keyboard_observer.h" |
| #include "components/exo/seat.h" |
| #include "components/exo/shell_surface.h" |
| #include "components/exo/surface.h" |
| #include "components/exo/test/exo_test_base.h" |
| #include "components/exo/test/exo_test_helper.h" |
| #include "components/exo/test/shell_surface_builder.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/focus_client.h" |
| #include "ui/base/accelerators/test_accelerator_target.h" |
| #include "ui/base/ime/dummy_text_input_client.h" |
| #include "ui/events/devices/device_data_manager.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/events/types/event_type.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| |
| namespace exo { |
| namespace { |
| |
| // XKB mod masks for the default keymap. |
| constexpr uint32_t kShiftMask = 1 << 0; |
| constexpr uint32_t kControlMask = 1 << 2; |
| constexpr uint32_t kAltMask = 1 << 3; |
| constexpr uint32_t kNumLockMask = 1 << 4; |
| |
| using KeyboardTest = test::ExoTestBase; |
| |
| class MockKeyboardDelegate : public KeyboardDelegate { |
| public: |
| MockKeyboardDelegate() = default; |
| |
| // Overridden from KeyboardDelegate: |
| MOCK_METHOD(bool, CanAcceptKeyboardEventsForSurface, (Surface*), (const)); |
| MOCK_METHOD(void, |
| OnKeyboardEnter, |
| (Surface*, (const base::flat_map<ui::DomCode, KeyState>&))); |
| MOCK_METHOD(void, OnKeyboardLeave, (Surface*)); |
| MOCK_METHOD(uint32_t, OnKeyboardKey, (base::TimeTicks, ui::DomCode, bool)); |
| MOCK_METHOD(void, OnKeyboardModifiers, (const KeyboardModifiers&)); |
| MOCK_METHOD(void, |
| OnKeyRepeatSettingsChanged, |
| (bool, base::TimeDelta, base::TimeDelta)); |
| MOCK_METHOD(void, OnKeyboardLayoutUpdated, (base::StringPiece)); |
| }; |
| using NiceMockKeyboardDelegate = ::testing::NiceMock<MockKeyboardDelegate>; |
| |
| class MockKeyboardDeviceConfigurationDelegate |
| : public KeyboardDeviceConfigurationDelegate { |
| public: |
| MockKeyboardDeviceConfigurationDelegate() = default; |
| |
| // Overridden from KeyboardDeviceConfigurationDelegate: |
| MOCK_METHOD(void, OnKeyboardTypeChanged, (bool)); |
| }; |
| |
| class MockKeyboardObserver : public KeyboardObserver { |
| public: |
| MockKeyboardObserver() = default; |
| |
| // Overridden from KeyboardObserver: |
| MOCK_METHOD(void, OnKeyboardDestroying, (Keyboard*)); |
| MOCK_METHOD(void, OnKeyboardKey, (base::TimeTicks, ui::DomCode, bool)); |
| }; |
| using NiceMockKeyboardObserver = ::testing::NiceMock<MockKeyboardObserver>; |
| |
| class TestShellSurface : public ShellSurface { |
| public: |
| explicit TestShellSurface(Surface* surface) : ShellSurface(surface) {} |
| |
| MOCK_METHOD(bool, AcceleratorPressed, (const ui::Accelerator& accelerator)); |
| }; |
| |
| // This event handler moves the focus to the given window when receiving a key |
| // event. |
| class TestEventHandler : public ui::EventHandler { |
| public: |
| explicit TestEventHandler(aura::Window* focus_window) |
| : focus_window_(focus_window) {} |
| TestEventHandler(const TestEventHandler&) = delete; |
| TestEventHandler& operator=(const TestEventHandler&) = delete; |
| |
| void OnKeyEvent(ui::KeyEvent* event) override { |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()) |
| ->FocusWindow(focus_window_); |
| } |
| |
| aura::Window* focus_window_; |
| }; |
| |
| // Verifies that switching desks via alt-tab doesn't prevent Seat from receiving |
| // key events. https://crbug.com/1008574. |
| TEST_F(KeyboardTest, CorrectSeatPressedKeysOnSwitchingDesks) { |
| Seat seat; |
| Keyboard keyboard(std::make_unique<NiceMockKeyboardDelegate>(), &seat); |
| |
| // Create 2 desks. |
| auto* desks_controller = ash::DesksController::Get(); |
| desks_controller->NewDesk(ash::DesksCreationRemovalSource::kButton); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| ash::Desk* desk_1 = desks_controller->desks()[0].get(); |
| const ash::Desk* desk_2 = desks_controller->desks()[1].get(); |
| // Desk 1 has a normal window. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| |
| // Desk 2 has an exo surface window. |
| ash::ActivateDesk(desk_2); |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| // Go back to desk 1, and trigger an alt-tab (releasing alt first). This would |
| // trigger activating the exo surface window on desk 2, which would lead to a |
| // desk switch animation. During the animation, expect that Seat gets all the |
| // keys in `OnKeyEvent()`, and the |pressed_keys_| map is correctly updated. |
| ash::ActivateDesk(desk_1); |
| auto displatch_key_event = [&](ui::EventType type, ui::KeyboardCode key_code, |
| ui::DomCode code, int flags) { |
| ui::KeyEvent key_event{type, key_code, code, flags}; |
| seat.WillProcessEvent(&key_event); |
| GetEventGenerator()->Dispatch(&key_event); |
| |
| EXPECT_EQ(type != ui::ET_KEY_RELEASED, seat.pressed_keys().count(code)); |
| |
| seat.DidProcessEvent(&key_event); |
| }; |
| |
| ash::DeskSwitchAnimationWaiter waiter; |
| displatch_key_event(ui::ET_KEY_PRESSED, ui::VKEY_MENU, ui::DomCode::ALT_LEFT, |
| /*flags=*/0); |
| displatch_key_event(ui::ET_KEY_PRESSED, ui::VKEY_TAB, ui::DomCode::TAB, |
| /*flags=*/ui::EF_ALT_DOWN); |
| displatch_key_event(ui::ET_KEY_RELEASED, ui::VKEY_MENU, ui::DomCode::ALT_LEFT, |
| /*flags=*/0); |
| displatch_key_event(ui::ET_KEY_RELEASED, ui::VKEY_TAB, ui::DomCode::TAB, |
| /*flags=*/0); |
| |
| EXPECT_TRUE(seat.pressed_keys().empty()); |
| EXPECT_EQ(desk_2, desks_controller->GetTargetActiveDesk()); |
| waiter.Wait(); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardEnter) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| Seat seat; |
| // Pressing key before Keyboard instance is created and surface has |
| // received focus. |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_A); |
| generator.PressKey(ui::VKEY_A, ui::EF_SHIFT_DOWN); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(surface->window()); |
| |
| // Keyboard should try to set initial focus to surface. |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(false)); |
| Keyboard keyboard(std::move(delegate), &seat); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Set up expectation for the key release. |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kShiftMask | kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter( |
| surface.get(), |
| base::flat_map<ui::DomCode, KeyState>( |
| {{ui::DomCode::US_A, KeyState{ui::DomCode::US_A, false}}}))); |
| focus_client->FocusWindow(nullptr); |
| focus_client->FocusWindow(surface->window()); |
| // Surface should maintain keyboard focus when moved to top-level window. |
| focus_client->FocusWindow(surface->window()->GetToplevelWindow()); |
| |
| // Release key after surface lost focus. |
| focus_client->FocusWindow(nullptr); |
| generator.ReleaseKey(ui::VKEY_A, ui::EF_SHIFT_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Key should no longer be pressed when focus returns. |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kShiftMask | kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()->GetToplevelWindow()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardLeave) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| Seat seat; |
| auto keyboard = std::make_unique<Keyboard>(std::move(delegate), &seat); |
| ON_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillByDefault(testing::Return(true)); |
| |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLeave(surface.get())); |
| focus_client->FocusWindow(nullptr); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLeave(surface.get())); |
| shell_surface.reset(); |
| surface.reset(); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardKey) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| NiceMockKeyboardObserver observer; |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| keyboard.AddObserver(&observer); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| // This should only generate a press event for KEY_A. |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_A, true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_A, true)); |
| } |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_A); |
| generator.PressKey(ui::VKEY_A, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // This should not generate another press event for KEY_A. |
| generator.PressKey(ui::VKEY_A, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // This should only generate a single release event for KEY_A. |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_A, false)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_A, false)); |
| } |
| generator.ReleaseKey(ui::VKEY_A, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Test key event rewriting. In this case, ARROW_DOWN is rewritten to KEY_END |
| // as a result of ALT being pressed. |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::END, true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::END, true)); |
| } |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kAltMask | kNumLockMask, 0, 0, 0})); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::ARROW_DOWN); |
| generator.PressKey(ui::VKEY_END, ui::EF_ALT_DOWN); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // This should generate a release event for KEY_END as that is the key |
| // associated with the key press. |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::END, false)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::END, false)); |
| } |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| generator.ReleaseKey(ui::VKEY_DOWN, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Press accelerator after surface lost focus. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLeave(surface.get())); |
| focus_client->FocusWindow(nullptr); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_W); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Key should be pressed when focus returns. |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kControlMask | kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter( |
| surface.get(), |
| base::flat_map<ui::DomCode, KeyState>( |
| {{ui::DomCode::US_W, KeyState{ui::DomCode::US_W, false}}}))); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Releasing accelerator when surface has focus should generate event. |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_W, false)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_W, false)); |
| } |
| generator.ReleaseKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Key events should be ignored when the focused window is not an |
| // exo::Surface. |
| std::unique_ptr<aura::Window> window = |
| ash::ChildTestWindowBuilder(shell_surface->GetWidget()->GetNativeWindow(), |
| gfx::Rect(buffer_size)) |
| .Build(); |
| // Moving the focus away will reset the focused surface. |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .Times(0); |
| focus_client->FocusWindow(window.get()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| EXPECT_FALSE(seat.GetFocusedSurface()); |
| EXPECT_FALSE(keyboard.focused_surface_for_testing()); |
| |
| EXPECT_CALL(observer, |
| OnKeyboardKey(testing::_, ui::DomCode::ARROW_LEFT, true)) |
| .Times(0); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::ARROW_LEFT, true)) |
| .Times(0); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::ARROW_LEFT); |
| generator.PressKey(ui::VKEY_LEFT, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(observer, |
| OnKeyboardKey(testing::_, ui::DomCode::ARROW_LEFT, false)) |
| .Times(0); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::ARROW_LEFT, false)) |
| .Times(0); |
| generator.ReleaseKey(ui::VKEY_LEFT, 0); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardKey_NotSendKeyIfConsumedByIme) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| NiceMockKeyboardObserver observer; |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| keyboard.AddObserver(&observer); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| views::Widget* widget = |
| views::Widget::GetTopLevelWidgetForNativeView(surface->window()); |
| ui::InputMethod* input_method = widget->GetInputMethod(); |
| ui::DummyTextInputClient client{ui::TEXT_INPUT_TYPE_TEXT}; |
| input_method->SetFocusedTextInputClient(&client); |
| |
| // If a text field is focused, a pressed key event is not sent to a client |
| // because a key event should be consumed by the IME. |
| // However, the observer should receive OnKeyboardKey, always. |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_A, true)); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardKey(testing::_, ui::DomCode::US_A, true)) |
| .Times(0); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_A); |
| generator.PressKey(ui::VKEY_A, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // TODO(yhanada): The below EXPECT_CALL fails because exo::Keyboard currently |
| // sends a key release event for the keys which exo::Keyboard sent a pressed |
| // event for. It might causes a never-ending key repeat in the client. |
| // EXPECT_CALL(delegate, OnKeyboardKey(testing::_, ui::DomCode::US_A, false)); |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_A, false)); |
| generator.ReleaseKey(ui::VKEY_A, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| // Any key event should be sent to a client if a key event skips IME. |
| surface->window()->SetProperty(aura::client::kSkipImeProcessing, true); |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_C, true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_C, true)); |
| } |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_C); |
| generator.PressKey(ui::VKEY_C, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_C, false)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_C, false)); |
| } |
| generator.ReleaseKey(ui::VKEY_C, 0); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| input_method->SetFocusedTextInputClient(nullptr); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardKey_KeyboardInhibit) { |
| std::unique_ptr<Surface> surface(new Surface()); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| // Set lacros attribute now for testing. This can be removed, when |
| // all clients are migrated into this model. |
| surface->window()->SetProperty(aura::client::kAppType, |
| static_cast<int>(ash::AppType::LACROS)); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| // Register accelerator to be triggered. |
| ui::TestAcceleratorTarget accelerator_target; |
| { |
| ui::Accelerator accelerator(ui::VKEY_P, |
| ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| ash::AcceleratorControllerImpl* controller = |
| ash::Shell::Get()->accelerator_controller(); |
| controller->Register({accelerator}, &accelerator_target); |
| } |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| NiceMockKeyboardObserver observer; |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| keyboard.AddObserver(&observer); |
| keyboard.SetNeedKeyboardKeyAcks(true); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| // This should only generate a press event for KEY_P. |
| accelerator_target.ResetCounts(); |
| EXPECT_CALL(observer, |
| OnKeyboardKey(testing::_, ui::DomCode::US_P, testing::_)) |
| .Times(0); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_P, testing::_)) |
| .Times(0); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_P); |
| generator.PressKey(ui::VKEY_P, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(1, accelerator_target.accelerator_count()); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Set keyboard-shortcut-inhibited, so the key event should be sent to app. |
| surface->SetKeyboardShortcutsInhibited(true); |
| accelerator_target.ResetCounts(); |
| { |
| testing::InSequence s; |
| EXPECT_CALL(observer, OnKeyboardKey(testing::_, ui::DomCode::US_P, true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_P, true)); |
| } |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_P); |
| generator.PressKey(ui::VKEY_P, ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(0, accelerator_target.accelerator_count()); |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, FocusWithArcOverlay) { |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| // Just allow any surface to receive focus. |
| EXPECT_CALL(*delegate, CanAcceptKeyboardEventsForSurface(::testing::_)) |
| .WillRepeatedly(testing::Return(true)); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| |
| // TODO(oshima): Create a TestExoWindowBuilder. |
| class TestPropertyResolver : public exo::WMHelper::AppPropertyResolver { |
| public: |
| TestPropertyResolver() = default; |
| ~TestPropertyResolver() override = default; |
| void PopulateProperties( |
| const Params& params, |
| ui::PropertyHandler& out_properties_container) override { |
| out_properties_container.SetProperty( |
| aura::client::kAppType, static_cast<int>(ash::AppType::ARC_APP)); |
| } |
| }; |
| WMHelper::GetInstance()->RegisterAppPropertyResolver( |
| std::make_unique<TestPropertyResolver>()); |
| |
| ash::ArcOverlayManager arc_overlay_manager_; |
| |
| auto* widget1 = ash::TestWidgetBuilder() |
| .SetBounds(gfx::Rect(200, 200)) |
| .BuildOwnedByNativeWidget(); |
| views::Textfield* textfield1 = new views::Textfield(); |
| widget1->GetContentsView()->AddChildView(textfield1); |
| textfield1->SetBounds(0, 0, 100, 100); |
| |
| auto* widget2 = ash::TestWidgetBuilder() |
| .SetBounds(gfx::Rect(200, 200)) |
| .BuildOwnedByNativeWidget(); |
| |
| auto hold = arc_overlay_manager_.RegisterHostWindow( |
| "test", widget1->GetNativeWindow()); |
| |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->SetClientSurfaceId("billing_id:test"); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| EXPECT_TRUE(shell_surface->GetWidget()); |
| |
| // The overlay should have the focus when created. |
| EXPECT_EQ(keyboard.focused_surface_for_testing(), surface.get()); |
| |
| widget2->Activate(); |
| EXPECT_FALSE(keyboard.focused_surface_for_testing()); |
| |
| // Activating the host widget should set the focus back to the overlay. |
| widget1->Activate(); |
| EXPECT_EQ(keyboard.focused_surface_for_testing(), surface.get()); |
| |
| constexpr char kFocusedViewClassName[] = "OverlayNativeViewHost"; |
| EXPECT_STREQ(kFocusedViewClassName, |
| widget1->GetFocusManager()->GetFocusedView()->GetClassName()); |
| |
| // Tabbing should not move the focus away from the overlay. |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| generator.PressKey(ui::VKEY_TAB, 0); |
| |
| EXPECT_STREQ(kFocusedViewClassName, |
| widget1->GetFocusManager()->GetFocusedView()->GetClassName()); |
| EXPECT_EQ(keyboard.focused_surface_for_testing(), surface.get()); |
| |
| hold.RunAndReset(); |
| widget1->CloseNow(); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardModifiers) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| // This should generate a modifier event. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_A, true)); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kShiftMask | kNumLockMask, 0, 0, 0})); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_A); |
| generator.PressKey(ui::VKEY_A, ui::EF_SHIFT_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // This should generate another modifier event. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_B, true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{ |
| kShiftMask | kAltMask | kNumLockMask, 0, 0, 0})); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_B); |
| generator.PressKey(ui::VKEY_B, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // This should generate a third modifier event. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_B, false)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| generator.ReleaseKey(ui::VKEY_B, 0); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardTypeChanged) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| ui::DeviceHotplugEventObserver* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| ASSERT_TRUE(device_data_manager != nullptr); |
| // Make sure that DeviceDataManager has one external keyboard... |
| const std::vector<ui::InputDevice> keyboards{ |
| ui::InputDevice(2, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard")}; |
| device_data_manager->OnKeyboardDevicesUpdated(keyboards); |
| // and a touch screen. |
| const std::vector<ui::TouchscreenDevice> touch_screen{ |
| ui::TouchscreenDevice(3, ui::InputDeviceType::INPUT_DEVICE_INTERNAL, |
| "touch", gfx::Size(600, 400), 1)}; |
| device_data_manager->OnTouchscreenDevicesUpdated(touch_screen); |
| |
| ash::TabletModeController* tablet_mode_controller = |
| ash::Shell::Get()->tablet_mode_controller(); |
| tablet_mode_controller->SetEnabledForTest(true); |
| |
| Seat seat; |
| auto keyboard = std::make_unique<Keyboard>( |
| std::make_unique<NiceMockKeyboardDelegate>(), &seat); |
| |
| MockKeyboardDeviceConfigurationDelegate configuration_delegate; |
| |
| EXPECT_CALL(configuration_delegate, OnKeyboardTypeChanged(true)); |
| keyboard->SetDeviceConfigurationDelegate(&configuration_delegate); |
| EXPECT_TRUE(keyboard->HasDeviceConfigurationDelegate()); |
| testing::Mock::VerifyAndClearExpectations(&configuration_delegate); |
| |
| // Removing all keyboard devices in tablet mode calls |
| // OnKeyboardTypeChanged() with false. |
| EXPECT_CALL(configuration_delegate, OnKeyboardTypeChanged(false)); |
| device_data_manager->OnKeyboardDevicesUpdated( |
| std::vector<ui::InputDevice>({})); |
| testing::Mock::VerifyAndClearExpectations(&configuration_delegate); |
| |
| // Re-adding keyboards calls OnKeyboardTypeChanged() with true. |
| EXPECT_CALL(configuration_delegate, OnKeyboardTypeChanged(true)); |
| device_data_manager->OnKeyboardDevicesUpdated(keyboards); |
| testing::Mock::VerifyAndClearExpectations(&configuration_delegate); |
| |
| keyboard.reset(); |
| |
| tablet_mode_controller->SetEnabledForTest(false); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardTypeChanged_AccessibilityKeyboard) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| ui::DeviceHotplugEventObserver* device_data_manager = |
| ui::DeviceDataManager::GetInstance(); |
| ASSERT_TRUE(device_data_manager != nullptr); |
| // Make sure that DeviceDataManager has one external keyboard. |
| const std::vector<ui::InputDevice> keyboards{ |
| ui::InputDevice(2, ui::InputDeviceType::INPUT_DEVICE_USB, "keyboard")}; |
| device_data_manager->OnKeyboardDevicesUpdated(keyboards); |
| |
| Seat seat; |
| Keyboard keyboard(std::make_unique<NiceMockKeyboardDelegate>(), &seat); |
| MockKeyboardDeviceConfigurationDelegate configuration_delegate; |
| |
| EXPECT_CALL(configuration_delegate, OnKeyboardTypeChanged(true)); |
| keyboard.SetDeviceConfigurationDelegate(&configuration_delegate); |
| EXPECT_TRUE(keyboard.HasDeviceConfigurationDelegate()); |
| testing::Mock::VerifyAndClearExpectations(&configuration_delegate); |
| |
| ash::AccessibilityControllerImpl* accessibility_controller = |
| ash::Shell::Get()->accessibility_controller(); |
| |
| // Enable a11y keyboard calls OnKeyboardTypeChanged() with false. |
| EXPECT_CALL(configuration_delegate, OnKeyboardTypeChanged(false)); |
| accessibility_controller->virtual_keyboard().SetEnabled(true); |
| testing::Mock::VerifyAndClearExpectations(&configuration_delegate); |
| |
| // Disable a11y keyboard calls OnKeyboardTypeChanged() with true. |
| EXPECT_CALL(configuration_delegate, OnKeyboardTypeChanged(true)); |
| accessibility_controller->virtual_keyboard().SetEnabled(false); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(&configuration_delegate); |
| } |
| |
| constexpr base::TimeDelta kDelta50Ms = base::Milliseconds(50); |
| constexpr base::TimeDelta kDelta500Ms = base::Milliseconds(500); |
| constexpr base::TimeDelta kDelta1000Ms = base::Milliseconds(1000); |
| |
| TEST_F(KeyboardTest, KeyRepeatSettingsLoadDefaults) { |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| EXPECT_CALL(*delegate, |
| OnKeyRepeatSettingsChanged(true, kDelta500Ms, kDelta50Ms)); |
| |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| } |
| |
| TEST_F(KeyboardTest, KeyRepeatSettingsLoadInitially) { |
| std::string email = "user0@tray"; |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatEnabled, base::Value(true)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatDelay, base::Value(1000)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatInterval, base::Value(1000)); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(true, kDelta1000Ms, kDelta1000Ms)); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, KeyRepeatSettingsUpdateAtRuntime) { |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| // Initially load defaults. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(testing::_, testing::_, testing::_)); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Make sure that setting prefs triggers the corresponding delegate calls. |
| const std::string email = "user0@tray"; |
| |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(false, testing::_, testing::_)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatEnabled, base::Value(false)); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(false, kDelta1000Ms, testing::_)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatDelay, base::Value(1000)); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(false, kDelta1000Ms, kDelta1000Ms)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatInterval, base::Value(1000)); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, KeyRepeatSettingsIgnoredForNonActiveUser) { |
| // Simulate two users, with the first user as active. |
| CreateUserSessions(2); |
| |
| // Key repeat settings should be sent exactly once, for the default values. |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(true, kDelta500Ms, kDelta50Ms)); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Set prefs for non-active user; no calls should result. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(testing::_, testing::_, testing::_)) |
| .Times(0); |
| const std::string email = "user1@tray"; |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatEnabled, base::Value(true)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatDelay, base::Value(1000)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatInterval, base::Value(1000)); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, KeyRepeatSettingsUpdateOnProfileChange) { |
| // Simulate two users, with the first user as active. |
| CreateUserSessions(2); |
| |
| // Second user has different preferences. |
| std::string email = "user1@tray"; |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatEnabled, base::Value(true)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatDelay, base::Value(1000)); |
| SetUserPref(email, ash::prefs::kXkbAutoRepeatInterval, base::Value(1000)); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| // Initially, load default prefs for first user. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(true, kDelta500Ms, kDelta50Ms)); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Switching user should load new prefs. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyRepeatSettingsChanged(true, kDelta1000Ms, kDelta1000Ms)); |
| SimulateUserLogin(email, user_manager::UserType::USER_TYPE_REGULAR); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, KeyboardLayout) { |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| // Initially, update to the current keyboard layout. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLayoutUpdated(testing::_)); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Updating the keyboard layout should trigger the delegate call. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLayoutUpdated(testing::_)); |
| keyboard.OnKeyboardLayoutNameChanged("ja-jp"); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, KeyboardObserver) { |
| // Declare before the keyboard so the mock verification happens |
| // after the keyboard destruction. |
| MockKeyboardObserver observer1; |
| MockKeyboardObserver observer2; |
| |
| Seat seat; |
| Keyboard keyboard(std::make_unique<NiceMockKeyboardDelegate>(), &seat); |
| |
| keyboard.AddObserver(&observer1); |
| keyboard.AddObserver(&observer2); |
| EXPECT_TRUE(keyboard.HasObserver(&observer1)); |
| EXPECT_TRUE(keyboard.HasObserver(&observer2)); |
| testing::Mock::VerifyAndClearExpectations(&observer1); |
| testing::Mock::VerifyAndClearExpectations(&observer2); |
| |
| keyboard.RemoveObserver(&observer1); |
| EXPECT_FALSE(keyboard.HasObserver(&observer1)); |
| EXPECT_TRUE(keyboard.HasObserver(&observer2)); |
| testing::Mock::VerifyAndClearExpectations(&observer1); |
| testing::Mock::VerifyAndClearExpectations(&observer2); |
| |
| // Called from the destructor of Keyboard. |
| EXPECT_CALL(observer1, OnKeyboardDestroying(&keyboard)).Times(0); |
| EXPECT_CALL(observer2, OnKeyboardDestroying(&keyboard)); |
| } |
| |
| TEST_F(KeyboardTest, NeedKeyboardKeyAcks) { |
| std::unique_ptr<Surface> surface(new Surface); |
| std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get())); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| Seat seat; |
| Keyboard keyboard(std::make_unique<NiceMockKeyboardDelegate>(), &seat); |
| |
| EXPECT_FALSE(keyboard.AreKeyboardKeyAcksNeeded()); |
| keyboard.SetNeedKeyboardKeyAcks(true); |
| EXPECT_TRUE(keyboard.AreKeyboardKeyAcksNeeded()); |
| keyboard.SetNeedKeyboardKeyAcks(false); |
| EXPECT_FALSE(keyboard.AreKeyboardKeyAcksNeeded()); |
| } |
| |
| TEST_F(KeyboardTest, AckKeyboardKey) { |
| std::unique_ptr<Surface> surface(new Surface); |
| auto shell_surface = std::make_unique<TestShellSurface>(surface.get()); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // If we don't set NeedKeyboardAckKeys to true, accelerators are always passed |
| // to ShellSurface. |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| // Press KEY_W with Ctrl. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kControlMask | kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL(*shell_surface.get(), AcceleratorPressed(ui::Accelerator( |
| ui::VKEY_W, ui::EF_CONTROL_DOWN, |
| ui::Accelerator::KeyState::PRESSED))) |
| .WillOnce(testing::Return(true)); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_W); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| |
| // Release KEY_W. |
| generator.ReleaseKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| testing::Mock::VerifyAndClearExpectations(shell_surface.get()); |
| |
| // If we set NeedKeyboardAckKeys to true, only unhandled accelerators are |
| // passed to ShellSurface. |
| keyboard.SetNeedKeyboardKeyAcks(true); |
| |
| // Press KEY_W with Ctrl. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardKey(testing::_, ui::DomCode::US_W, true)) |
| .WillOnce(testing::Return(1)); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Send ack for the key press. |
| EXPECT_CALL(*shell_surface.get(), AcceleratorPressed(ui::Accelerator( |
| ui::VKEY_W, ui::EF_CONTROL_DOWN, |
| ui::Accelerator::KeyState::PRESSED))) |
| .WillOnce(testing::Return(true)); |
| keyboard.AckKeyboardKey(1, false /* handled */); |
| testing::Mock::VerifyAndClearExpectations(shell_surface.get()); |
| |
| // Release KEY_W. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_W, false)) |
| .WillOnce(testing::Return(2)); |
| generator.ReleaseKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Send ack for the key release. |
| keyboard.AckKeyboardKey(2, false /* handled */); |
| |
| // Press KEY_W with Ctrl again. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardKey(testing::_, ui::DomCode::US_W, true)) |
| .WillOnce(testing::Return(3)); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Send ack for the key press. |
| // AcceleratorPressed is not called when the accelerator is already handled. |
| keyboard.AckKeyboardKey(3, true /* handled */); |
| |
| // A repeat key event should not be sent to the client and also should not |
| // invoke the accelerator. |
| EXPECT_CALL(*shell_surface.get(), AcceleratorPressed(ui::Accelerator( |
| ui::VKEY_W, ui::EF_CONTROL_DOWN, |
| ui::Accelerator::KeyState::PRESSED))) |
| .Times(0); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_IS_REPEAT); |
| testing::Mock::VerifyAndClearExpectations(shell_surface.get()); |
| |
| // Another key press event while holding the key is also ignored and should |
| // not invoke the accelerator. |
| EXPECT_CALL(*shell_surface.get(), AcceleratorPressed(ui::Accelerator( |
| ui::VKEY_W, ui::EF_CONTROL_DOWN, |
| ui::Accelerator::KeyState::PRESSED))) |
| .Times(0); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(shell_surface.get()); |
| |
| // Release the key and reset modifier_flags. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_W, false)); |
| generator.ReleaseKey(ui::VKEY_W, 0); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| TEST_F(KeyboardTest, AckKeyboardKeyMoveFocus) { |
| std::unique_ptr<Surface> surface(new Surface); |
| auto shell_surface = std::make_unique<TestShellSurface>(surface.get()); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| keyboard.SetNeedKeyboardKeyAcks(true); |
| |
| // Press KEY_W with Ctrl. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kControlMask | kNumLockMask, 0, 0, 0})) |
| .Times(1); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardKey(testing::_, ui::DomCode::US_W, true)) |
| .WillOnce(testing::Return(1)); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_W); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Move focus from the window |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLeave(surface.get())); |
| focus_client->FocusWindow(nullptr); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Send ack for the key press. |AcceleratorPressed()| should not be called. |
| keyboard.AckKeyboardKey(1, false /* handled */); |
| } |
| |
| TEST_F(KeyboardTest, AckKeyboardKeyExpired) { |
| std::unique_ptr<Surface> surface(new Surface); |
| auto shell_surface = std::make_unique<TestShellSurface>(surface.get()); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| keyboard.SetNeedKeyboardKeyAcks(true); |
| |
| // Press KEY_W with Ctrl. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kControlMask | kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardKey(testing::_, ui::DomCode::US_W, true)) |
| .WillOnce(testing::Return(1)); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_W); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Keyboard processes pending events as if it is handled when it expires, |
| // so |AcceleratorPressed()| should not be called. |
| EXPECT_CALL(*shell_surface.get(), AcceleratorPressed(ui::Accelerator( |
| ui::VKEY_W, ui::EF_CONTROL_DOWN, |
| ui::Accelerator::KeyState::PRESSED))) |
| .Times(0); |
| |
| // Wait until |ProcessExpiredPendingKeyAcks| is fired. |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(1000)); |
| run_loop.Run(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Send ack for the key press as if it was not handled. In the normal case, |
| // |AcceleratorPressed()| should be called, but since the timeout passed, the |
| // key should have been treated as handled already and removed from the |
| // pending_key_acks_ map. Since the event is no longer in the map, |
| // |AcceleratorPressed()| should not be called. |
| keyboard.AckKeyboardKey(1, false /* handled */); |
| |
| // Release the key and reset modifier_flags. |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardKey(testing::_, ui::DomCode::US_W, false)); |
| generator.ReleaseKey(ui::VKEY_W, 0); |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| } |
| |
| // Test for crbug.com/753539. If action for an accelerator moves the focus to |
| // another window, it causes clearing the map of pending key acks in Keyboard. |
| // We can't assume that an iterator of the map is valid after processing an |
| // accelerator. |
| class TestShellSurfaceWithMovingFocusAccelerator : public ShellSurface { |
| public: |
| explicit TestShellSurfaceWithMovingFocusAccelerator(Surface* surface) |
| : ShellSurface(surface) {} |
| |
| bool AcceleratorPressed(const ui::Accelerator& accelerator) override { |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| return true; |
| } |
| }; |
| |
| TEST_F(KeyboardTest, AckKeyboardKeyExpiredWithMovingFocusAccelerator) { |
| std::unique_ptr<Surface> surface(new Surface); |
| auto shell_surface = |
| std::make_unique<TestShellSurfaceWithMovingFocusAccelerator>( |
| surface.get()); |
| gfx::Size buffer_size(10, 10); |
| std::unique_ptr<Buffer> buffer( |
| new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size))); |
| surface->Attach(buffer.get()); |
| surface->Commit(); |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface.get())) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface.get(), base::flat_map<ui::DomCode, KeyState>())); |
| focus_client->FocusWindow(surface->window()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| keyboard.SetNeedKeyboardKeyAcks(true); |
| |
| // Press KEY_W with Ctrl. |
| EXPECT_CALL(*delegate_ptr, OnKeyboardModifiers(KeyboardModifiers{ |
| kControlMask | kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL(*delegate_ptr, OnKeyboardKey(testing::_, ui::DomCode::US_W, true)) |
| .WillOnce(testing::Return(1)); |
| seat.set_physical_code_for_currently_processing_event_for_testing( |
| ui::DomCode::US_W); |
| generator.PressKey(ui::VKEY_W, ui::EF_CONTROL_DOWN); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| EXPECT_CALL(*delegate_ptr, OnKeyboardLeave(surface.get())); |
| |
| // Send ack as unhandled. This will call |AcceleratorPressed| and move the |
| // focus. |
| keyboard.AckKeyboardKey(1, false /* handled */); |
| |
| // Wait until |ProcessExpiredPendingKeyAcks| is fired. |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(1000)); |
| run_loop.Run(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify before destroying keyboard to make sure the expected call |
| // is made on the methods above, rather than in the destructor. |
| testing::Mock::VerifyAndClearExpectations(&delegate); |
| } |
| |
| TEST_F(KeyboardTest, OnKeyboardKey_ChangeFocusInPreTargetHandler) { |
| auto shell_surface = test::ShellSurfaceBuilder({10, 10}).BuildShellSurface(); |
| auto* surface = shell_surface->surface_for_testing(); |
| auto normal_window = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| TestEventHandler handler{shell_surface->GetWidget()->GetNativeView()}; |
| |
| aura::client::FocusClient* focus_client = |
| aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow()); |
| focus_client->FocusWindow(nullptr); |
| |
| auto delegate = std::make_unique<NiceMockKeyboardDelegate>(); |
| auto* delegate_ptr = delegate.get(); |
| NiceMockKeyboardObserver observer; |
| Seat seat; |
| Keyboard keyboard(std::move(delegate), &seat); |
| keyboard.AddObserver(&observer); |
| |
| // Focus the non-exo window. |
| focus_client->FocusWindow(normal_window.get()); |
| |
| ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow()); |
| |
| // Keyboard should not get a key event sent to the non-exo window. |
| generator.PressKey(ui::VKEY_A, 0); |
| generator.ReleaseKey(ui::VKEY_A, 0); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| // Sending a key event causes a focus change. |
| // It calls OnKeyboardEnter, but OnKeyboardKey should not be called because |
| // the event's target is |normal_window|. |
| wm_helper()->AddPreTargetHandler(&handler); |
| |
| EXPECT_CALL(*delegate_ptr, CanAcceptKeyboardEventsForSurface(surface)) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*delegate_ptr, |
| OnKeyboardModifiers(KeyboardModifiers{kNumLockMask, 0, 0, 0})); |
| EXPECT_CALL( |
| *delegate_ptr, |
| OnKeyboardEnter(surface, base::flat_map<ui::DomCode, KeyState>())); |
| |
| generator.PressKey(ui::VKEY_A, 0); |
| EXPECT_EQ(shell_surface->GetWidget()->GetNativeView(), |
| focus_client->GetFocusedWindow()); |
| testing::Mock::VerifyAndClearExpectations(delegate_ptr); |
| |
| wm_helper()->RemovePreTargetHandler(&handler); |
| } |
| |
| } // namespace |
| } // namespace exo |