| // Copyright 2013 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/accessibility/accessibility_controller_impl.h" |
| #include "ash/autoclick/autoclick_controller.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/system/accessibility/accessibility_feature_disable_dialog.h" |
| #include "ash/system/accessibility/autoclick_menu_bubble_controller.h" |
| #include "ash/system/accessibility/autoclick_menu_view.h" |
| #include "ash/system/accessibility/autoclick_scroll_bubble_controller.h" |
| #include "ash/system/accessibility/autoclick_scroll_view.h" |
| #include "ash/system/unified/unified_system_tray.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/wm/collision_detection/collision_detection_utils.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/wm_event.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "ui/aura/test/test_window_delegate.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_event_dispatcher.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/event_handler.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/views/window/dialog_client_view.h" |
| |
| namespace ash { |
| |
| namespace { |
| const int kScrollToMenuBoundsBuffer = 18; |
| } |
| |
| class MouseEventCapturer : public ui::EventHandler { |
| public: |
| MouseEventCapturer() { Reset(); } |
| ~MouseEventCapturer() override = default; |
| |
| void Reset() { |
| events_.clear(); |
| wheel_events_.clear(); |
| } |
| |
| void OnMouseEvent(ui::MouseEvent* event) override { |
| bool save_event = false; |
| bool stop_event = false; |
| // Filter out extraneous mouse events like mouse entered, exited, |
| // capture changed, etc. |
| ui::EventType type = event->type(); |
| if (type == ui::ET_MOUSE_PRESSED || type == ui::ET_MOUSE_RELEASED) { |
| // Only track left and right mouse button events, ensuring that we get |
| // left-click, right-click and double-click. |
| if (!(event->flags() & ui::EF_LEFT_MOUSE_BUTTON) && |
| (!(event->flags() & ui::EF_RIGHT_MOUSE_BUTTON))) |
| return; |
| save_event = true; |
| // Stop event propagation so we don't click on random stuff that |
| // might break test assumptions. |
| stop_event = true; |
| } else if (type == ui::ET_MOUSE_DRAGGED) { |
| save_event = true; |
| stop_event = false; |
| } else if (type == ui::ET_MOUSEWHEEL) { |
| // Save it immediately as a MouseWheelEvent. |
| wheel_events_.push_back(ui::MouseWheelEvent( |
| event->AsMouseWheelEvent()->offset(), event->location(), |
| event->root_location(), ui::EventTimeForNow(), event->flags(), |
| event->changed_button_flags())); |
| } |
| if (save_event) { |
| events_.push_back(ui::MouseEvent(event->type(), event->location(), |
| event->root_location(), |
| ui::EventTimeForNow(), event->flags(), |
| event->changed_button_flags())); |
| } |
| if (stop_event) |
| event->StopPropagation(); |
| |
| // If there is a possibility that we're in an infinite loop, we should |
| // exit early with a sensible error rather than letting the test time out. |
| ASSERT_LT(events_.size(), 100u); |
| } |
| |
| const std::vector<ui::MouseEvent>& captured_events() const { return events_; } |
| const std::vector<ui::MouseWheelEvent>& captured_mouse_wheel_events() const { |
| return wheel_events_; |
| } |
| |
| private: |
| std::vector<ui::MouseEvent> events_; |
| std::vector<ui::MouseWheelEvent> wheel_events_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MouseEventCapturer); |
| }; |
| |
| class AutoclickTest : public AshTestBase { |
| public: |
| AutoclickTest() |
| : AshTestBase(base::test::ScopedTaskEnvironment::TimeSource::MOCK_TIME) {} |
| ~AutoclickTest() override = default; |
| |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| Shell::Get()->AddPreTargetHandler(&mouse_event_capturer_); |
| GetAutoclickController()->SetAutoclickDelay(base::TimeDelta()); |
| |
| // Move mouse to deterministic location at the start of each test. |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| |
| // Make sure the display is initialized so we don't fail the test due to any |
| // input events caused from creating the display. |
| Shell::Get()->display_manager()->UpdateDisplays(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void TearDown() override { |
| Shell::Get()->RemovePreTargetHandler(&mouse_event_capturer_); |
| AshTestBase::TearDown(); |
| } |
| |
| void MoveMouseWithFlagsTo(int x, int y, ui::EventFlags flags) { |
| GetEventGenerator()->set_flags(flags); |
| GetEventGenerator()->MoveMouseTo(x, y); |
| GetEventGenerator()->set_flags(ui::EF_NONE); |
| } |
| |
| const std::vector<ui::MouseEvent>& WaitForMouseEvents() { |
| ClearMouseEvents(); |
| // TODO(katie): Consider using quit closure instead of RunUntilIdle. |
| base::RunLoop().RunUntilIdle(); |
| return GetMouseEvents(); |
| } |
| |
| void FastForwardBy(int milliseconds) { |
| scoped_task_environment_->FastForwardBy( |
| base::TimeDelta::FromMilliseconds(milliseconds)); |
| } |
| |
| AutoclickController* GetAutoclickController() { |
| return Shell::Get()->autoclick_controller(); |
| } |
| |
| // Calculates and returns full delay from the animation delay, after setting |
| // that delay on the autoclick controller. |
| int UpdateAnimationDelayAndGetFullDelay(float animation_delay) { |
| float ratio = |
| GetAutoclickController()->GetStartGestureDelayRatioForTesting(); |
| int full_delay = ceil(1.0 / ratio) * animation_delay; |
| GetAutoclickController()->SetAutoclickDelay( |
| base::TimeDelta::FromMilliseconds(full_delay)); |
| return full_delay; |
| } |
| |
| AutoclickMenuView* GetAutoclickMenuView() { |
| return GetAutoclickController() |
| ->GetMenuBubbleControllerForTesting() |
| ->menu_view_; |
| } |
| |
| AutoclickScrollView* GetAutoclickScrollView() { |
| AutoclickScrollBubbleController* controller = |
| GetAutoclickController() |
| ->GetMenuBubbleControllerForTesting() |
| ->scroll_bubble_controller_.get(); |
| return controller ? controller->scroll_view_ : nullptr; |
| } |
| |
| views::Widget* GetAutoclickBubbleWidget() { |
| return GetAutoclickController() |
| ->GetMenuBubbleControllerForTesting() |
| ->bubble_widget_; |
| } |
| |
| views::View* GetMenuButton(AutoclickMenuView::ButtonId view_id) { |
| AutoclickMenuView* menu_view = GetAutoclickMenuView(); |
| if (!menu_view) |
| return nullptr; |
| return menu_view->GetViewByID(static_cast<int>(view_id)); |
| } |
| |
| views::View* GetScrollButton(AutoclickScrollView::ButtonId view_id) { |
| AutoclickScrollView* scroll_view = GetAutoclickScrollView(); |
| if (!scroll_view) |
| return nullptr; |
| return scroll_view->GetViewByID(static_cast<int>(view_id)); |
| } |
| |
| void ClearMouseEvents() { mouse_event_capturer_.Reset(); } |
| |
| const std::vector<ui::MouseEvent>& GetMouseEvents() { |
| return mouse_event_capturer_.captured_events(); |
| } |
| |
| const std::vector<ui::MouseWheelEvent>& GetMouseWheelEvents() { |
| return mouse_event_capturer_.captured_mouse_wheel_events(); |
| } |
| |
| private: |
| MouseEventCapturer mouse_event_capturer_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AutoclickTest); |
| }; |
| |
| TEST_F(AutoclickTest, ToggleEnabled) { |
| std::vector<ui::MouseEvent> events; |
| |
| // We should not see any events initially. |
| EXPECT_FALSE(GetAutoclickController()->IsEnabled()); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Enable autoclick, and we should see a mouse pressed and |
| // a mouse released event, simulating a click. |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetEventGenerator()->MoveMouseTo(0, 0); |
| EXPECT_TRUE(GetAutoclickController()->IsEnabled()); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| |
| // We should not get any more clicks until we move the mouse. |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| GetEventGenerator()->MoveMouseTo(30, 30); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| |
| // Disable autoclick, and we should see the original behaviour. |
| GetAutoclickController()->SetEnabled(false, false /* do not show dialog */); |
| EXPECT_FALSE(GetAutoclickController()->IsEnabled()); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // After disable, autoclick should be set back to left click. |
| EXPECT_EQ(AutoclickEventType::kLeftClick, |
| Shell::Get()->accessibility_controller()->GetAutoclickEventType()); |
| } |
| |
| TEST_F(AutoclickTest, MouseMovement) { |
| std::vector<ui::MouseEvent> events; |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| |
| gfx::Point p1(0, 0); |
| gfx::Point p2(20, 20); |
| gfx::Point p3(40, 40); |
| |
| // Move mouse to p1. |
| GetEventGenerator()->MoveMouseTo(p1); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(p1.ToString(), events[0].root_location().ToString()); |
| EXPECT_EQ(p1.ToString(), events[1].root_location().ToString()); |
| |
| // Move mouse to multiple locations and finally arrive at p3. |
| GetEventGenerator()->MoveMouseTo(p2); |
| GetEventGenerator()->MoveMouseTo(p1); |
| GetEventGenerator()->MoveMouseTo(p3); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(p3.ToString(), events[0].root_location().ToString()); |
| EXPECT_EQ(p3.ToString(), events[1].root_location().ToString()); |
| } |
| |
| TEST_F(AutoclickTest, MovementThreshold) { |
| UpdateDisplay("1280x1024,800x600"); |
| base::RunLoop().RunUntilIdle(); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| EXPECT_EQ(2u, root_windows.size()); |
| |
| int animation_delay = 5; |
| |
| const struct { |
| int movement_threshold; |
| bool stabilize_click_position; |
| } kTestCases[] = { |
| {10, false}, {20, false}, {30, false}, {40, false}, {50, false}, |
| {10, true}, {20, true}, {30, true}, {40, true}, {50, true}, |
| }; |
| |
| for (const auto& test : kTestCases) { |
| GetAutoclickController()->set_stabilize_click_position( |
| test.stabilize_click_position); |
| int movement_threshold = test.movement_threshold; |
| GetAutoclickController()->SetMovementThreshold(movement_threshold); |
| |
| // Run test for the secondary display too to test fix for crbug.com/449870. |
| for (auto* root_window : root_windows) { |
| gfx::Point center = root_window->GetBoundsInScreen().CenterPoint(); |
| |
| GetAutoclickController()->SetEnabled(true, |
| false /* do not show dialog */); |
| GetEventGenerator()->MoveMouseTo(center); |
| ClearMouseEvents(); |
| EXPECT_EQ(2u, WaitForMouseEvents().size()); |
| |
| // Small mouse movements should not trigger an autoclick, i.e. movements |
| // within the radius of the movement_threshold. |
| GetEventGenerator()->MoveMouseTo( |
| center + gfx::Vector2d(std::sqrt(movement_threshold) - 1, |
| std::sqrt(movement_threshold) - 1)); |
| EXPECT_EQ(0u, WaitForMouseEvents().size()); |
| GetEventGenerator()->MoveMouseTo( |
| center + gfx::Vector2d(movement_threshold - 1, 0)); |
| EXPECT_EQ(0u, WaitForMouseEvents().size()); |
| GetEventGenerator()->MoveMouseTo( |
| center + gfx::Vector2d(0, -movement_threshold + 1)); |
| EXPECT_EQ(0u, WaitForMouseEvents().size()); |
| GetEventGenerator()->MoveMouseTo(center); |
| EXPECT_EQ(0u, WaitForMouseEvents().size()); |
| |
| // A larger mouse movement should trigger an autoclick. |
| GetEventGenerator()->MoveMouseTo( |
| center + |
| gfx::Vector2d(movement_threshold + 1, movement_threshold + 1)); |
| EXPECT_EQ(2u, WaitForMouseEvents().size()); |
| |
| // Moving outside the threshold after the gesture begins should cancel |
| // the autoclick. Update the delay so we can do events between the initial |
| // trigger of the feature and the click. |
| int full_delay = UpdateAnimationDelayAndGetFullDelay(animation_delay); |
| GetEventGenerator()->MoveMouseTo( |
| center - gfx::Vector2d(movement_threshold, movement_threshold)); |
| FastForwardBy(animation_delay + 1); |
| GetEventGenerator()->MoveMouseTo(center); |
| ClearMouseEvents(); |
| |
| // After a time, a new click will occur at the second location. The first |
| // location should never get a click. |
| FastForwardBy(full_delay * 2); |
| EXPECT_EQ(2u, GetMouseEvents().size()); |
| gfx::Rect display_bounds = display::Screen::GetScreen() |
| ->GetDisplayNearestWindow(root_window) |
| .bounds(); |
| EXPECT_EQ(center - gfx::Vector2d(display_bounds.origin().x(), |
| display_bounds.origin().y()), |
| GetMouseEvents()[0].location()); |
| |
| // Move it out of the way so the next cycle starts properly. |
| GetEventGenerator()->MoveMouseTo(gfx::Point(0, 0)); |
| GetAutoclickController()->SetAutoclickDelay(base::TimeDelta()); |
| } |
| } |
| |
| // Reset to defaults. |
| GetAutoclickController()->SetMovementThreshold(20); |
| GetAutoclickController()->set_stabilize_click_position(false); |
| } |
| |
| TEST_F(AutoclickTest, MovementWithinThresholdWhileTimerRunning) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetAutoclickController()->SetMovementThreshold(20); |
| int animation_delay = 5; |
| int full_delay = UpdateAnimationDelayAndGetFullDelay(animation_delay); |
| |
| GetAutoclickController()->set_stabilize_click_position(true); |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| FastForwardBy(animation_delay + 1); |
| |
| // Move the mouse within the threshold. It shouldn't change the eventual |
| // target of the event, or cancel the click. |
| GetEventGenerator()->MoveMouseTo(110, 110); |
| ClearMouseEvents(); |
| FastForwardBy(full_delay); |
| std::vector<ui::MouseEvent> events = GetMouseEvents(); |
| |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(gfx::Point(100, 100), events[0].location()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, events[0].flags()); |
| EXPECT_EQ(gfx::Point(100, 100), events[1].location()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, events[1].flags()); |
| |
| // When the click position is not stabilized, the mouse movement should |
| // translate into the target of the event, but not cancel the click. |
| GetAutoclickController()->set_stabilize_click_position(false); |
| GetEventGenerator()->MoveMouseTo(200, 200); |
| FastForwardBy(animation_delay + 1); |
| GetEventGenerator()->MoveMouseTo(210, 210); |
| |
| ClearMouseEvents(); |
| FastForwardBy(full_delay); |
| events = GetMouseEvents(); |
| |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(gfx::Point(210, 210), events[0].location()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, events[0].flags()); |
| EXPECT_EQ(gfx::Point(210, 210), events[1].location()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, events[1].flags()); |
| |
| // Reset delay. |
| GetAutoclickController()->SetAutoclickDelay(base::TimeDelta()); |
| } |
| |
| TEST_F(AutoclickTest, SingleKeyModifier) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| MoveMouseWithFlagsTo(20, 20, ui::EF_SHIFT_DOWN); |
| std::vector<ui::MouseEvent> events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(ui::EF_SHIFT_DOWN, events[0].flags() & ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(ui::EF_SHIFT_DOWN, events[1].flags() & ui::EF_SHIFT_DOWN); |
| } |
| |
| TEST_F(AutoclickTest, MultipleKeyModifiers) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| ui::EventFlags modifier_flags = static_cast<ui::EventFlags>( |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| MoveMouseWithFlagsTo(30, 30, modifier_flags); |
| std::vector<ui::MouseEvent> events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(modifier_flags, events[0].flags() & modifier_flags); |
| EXPECT_EQ(modifier_flags, events[1].flags() & modifier_flags); |
| } |
| |
| TEST_F(AutoclickTest, KeyModifiersReleased) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| |
| ui::EventFlags modifier_flags = static_cast<ui::EventFlags>( |
| ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN); |
| MoveMouseWithFlagsTo(12, 12, modifier_flags); |
| |
| // Simulate releasing key modifiers by sending key released events. |
| GetEventGenerator()->ReleaseKey( |
| ui::VKEY_CONTROL, |
| static_cast<ui::EventFlags>(ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN)); |
| GetEventGenerator()->ReleaseKey(ui::VKEY_SHIFT, ui::EF_ALT_DOWN); |
| |
| std::vector<ui::MouseEvent> events; |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| EXPECT_EQ(0, events[0].flags() & ui::EF_CONTROL_DOWN); |
| EXPECT_EQ(0, events[0].flags() & ui::EF_SHIFT_DOWN); |
| EXPECT_EQ(ui::EF_ALT_DOWN, events[0].flags() & ui::EF_ALT_DOWN); |
| } |
| |
| TEST_F(AutoclickTest, UserInputCancelsAutoclick) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| std::vector<ui::MouseEvent> events; |
| |
| // Pressing a normal key should cancel the autoclick. |
| GetEventGenerator()->MoveMouseTo(200, 200); |
| GetEventGenerator()->PressKey(ui::VKEY_K, ui::EF_NONE); |
| GetEventGenerator()->ReleaseKey(ui::VKEY_K, ui::EF_NONE); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Pressing a modifier key should NOT cancel the autoclick. |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| GetEventGenerator()->PressKey(ui::VKEY_SHIFT, ui::EF_SHIFT_DOWN); |
| GetEventGenerator()->ReleaseKey(ui::VKEY_SHIFT, ui::EF_NONE); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| |
| // Mouse-wheel scroll events should cancel the autoclick. |
| GetEventGenerator()->MoveMouseTo(300, 300); |
| GetEventGenerator()->MoveMouseWheel(0, 20); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Performing a gesture should cancel the autoclick. |
| GetEventGenerator()->MoveMouseTo(200, 200); |
| GetEventGenerator()->GestureTapDownAndUp(gfx::Point(100, 100)); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Test another gesture. |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| GetEventGenerator()->GestureScrollSequence( |
| gfx::Point(100, 100), gfx::Point(200, 200), |
| base::TimeDelta::FromMilliseconds(200), 3); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Test scroll events. |
| GetEventGenerator()->MoveMouseTo(200, 200); |
| GetEventGenerator()->ScrollSequence(gfx::Point(100, 100), |
| base::TimeDelta::FromMilliseconds(200), 0, |
| 100, 3, 2); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // However, just starting a scroll doesn't cancel. If you tap a touchpad on |
| // an Eve chromebook, for example, it can send an ET_SCROLL_FLING_CANCEL |
| // event, which shouldn't actually cancel autoclick. |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| GetEventGenerator()->ScrollSequence(gfx::Point(100, 100), base::TimeDelta(), |
| 0, 0, 0, 1); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| } |
| |
| TEST_F(AutoclickTest, SynthesizedMouseMovesIgnored) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| std::vector<ui::MouseEvent> events; |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(2u, events.size()); |
| |
| // Show a window and make sure the new window is under the cursor. As a |
| // result, synthesized mouse events will be dispatched to the window, but it |
| // should not trigger an autoclick. |
| aura::test::EventCountDelegate delegate; |
| std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate( |
| &delegate, 123, gfx::Rect(50, 50, 100, 100))); |
| window->Show(); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| EXPECT_EQ("1 1 0", delegate.GetMouseMotionCountsAndReset()); |
| } |
| |
| TEST_F(AutoclickTest, AutoclickChangeEventTypes) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetAutoclickController()->set_revert_to_left_click(false); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kRightClick); |
| std::vector<ui::MouseEvent> events; |
| |
| GetEventGenerator()->MoveMouseTo(30, 30); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[1].flags()); |
| |
| // Changing the event type cancels the event |
| GetEventGenerator()->MoveMouseTo(60, 60); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kLeftClick); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Changing the event type to the same thing does not cancel the event. |
| // kLeftClick type does not produce a double-click. |
| GetEventGenerator()->MoveMouseTo(90, 90); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kLeftClick); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_FALSE(ui::EF_IS_DOUBLE_CLICK & events[0].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| EXPECT_FALSE(ui::EF_IS_DOUBLE_CLICK & events[1].flags()); |
| |
| // Double-click works as expected. |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kDoubleClick); |
| GetEventGenerator()->MoveMouseTo(120, 120); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(4u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_FALSE(ui::EF_IS_DOUBLE_CLICK & events[0].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[1].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| EXPECT_FALSE(ui::EF_IS_DOUBLE_CLICK & events[1].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[2].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[2].flags()); |
| EXPECT_TRUE(ui::EF_IS_DOUBLE_CLICK & events[2].flags()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[3].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[3].flags()); |
| EXPECT_TRUE(ui::EF_IS_DOUBLE_CLICK & events[3].flags()); |
| |
| // Pause / no action does not cause events to be generated even when the |
| // mouse moves. |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kNoAction); |
| GetEventGenerator()->MoveMouseTo(120, 120); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| } |
| |
| TEST_F(AutoclickTest, AutoclickDragAndDropEvents) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetAutoclickController()->set_revert_to_left_click(false); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kDragAndDrop); |
| std::vector<ui::MouseEvent> events; |
| |
| GetEventGenerator()->MoveMouseTo(30, 30); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_PRESSED, events[0].type()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| |
| ClearMouseEvents(); |
| GetEventGenerator()->MoveMouseTo(60, 60); |
| events = GetMouseEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_DRAGGED, events[0].type()); |
| |
| // Another move creates a drag |
| ClearMouseEvents(); |
| GetEventGenerator()->MoveMouseTo(90, 90); |
| events = GetMouseEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_DRAGGED, events[0].type()); |
| |
| // Waiting in place creates the released event. |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(1u, events.size()); |
| EXPECT_EQ(ui::ET_MOUSE_RELEASED, events[0].type()); |
| } |
| |
| TEST_F(AutoclickTest, AutoclickScrollEvents) { |
| UpdateDisplay("800x600"); |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetAutoclickController()->SetAutoclickEventType(AutoclickEventType::kScroll); |
| std::vector<ui::MouseEvent> events; |
| std::vector<ui::MouseWheelEvent> wheel_events; |
| |
| // Expect that the first scroll action occurs at the middle of the screen, |
| // since the position has not been set by the user. |
| GetAutoclickController()->DoScrollAction( |
| AutoclickController::ScrollPadAction::kScrollUp); |
| events = GetMouseEvents(); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ASSERT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(ui::ET_MOUSEWHEEL, wheel_events[0].type()); |
| EXPECT_EQ(gfx::Point(400, 300), wheel_events[0].location()); |
| EXPECT_GT(wheel_events[0].y_offset(), 0); |
| ClearMouseEvents(); |
| |
| // Expect that a dwell will set the scroll location. |
| GetEventGenerator()->MoveMouseTo(90, 90); |
| base::RunLoop().RunUntilIdle(); |
| GetAutoclickController()->DoScrollAction( |
| AutoclickController::ScrollPadAction::kScrollUp); |
| events = GetMouseEvents(); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ASSERT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(ui::ET_MOUSEWHEEL, wheel_events[0].type()); |
| EXPECT_EQ(gfx::Point(90, 90), wheel_events[0].location()); |
| EXPECT_GT(wheel_events[0].y_offset(), 0); |
| ClearMouseEvents(); |
| |
| GetAutoclickController()->DoScrollAction( |
| AutoclickController::ScrollPadAction::kScrollLeft); |
| events = GetMouseEvents(); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ASSERT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(ui::ET_MOUSEWHEEL, wheel_events[0].type()); |
| EXPECT_EQ(gfx::Point(90, 90), wheel_events[0].location()); |
| EXPECT_GT(wheel_events[0].x_offset(), 0); |
| ClearMouseEvents(); |
| |
| // Try another position, and the other two types of scroll action. |
| GetEventGenerator()->MoveMouseTo(200, 200); |
| base::RunLoop().RunUntilIdle(); |
| GetAutoclickController()->DoScrollAction( |
| AutoclickController::ScrollPadAction::kScrollDown); |
| events = GetMouseEvents(); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ASSERT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(ui::ET_MOUSEWHEEL, wheel_events[0].type()); |
| EXPECT_EQ(gfx::Point(200, 200), wheel_events[0].location()); |
| EXPECT_LT(wheel_events[0].y_offset(), 0); |
| ClearMouseEvents(); |
| |
| GetAutoclickController()->DoScrollAction( |
| AutoclickController::ScrollPadAction::kScrollRight); |
| events = GetMouseEvents(); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ASSERT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(ui::ET_MOUSEWHEEL, wheel_events[0].type()); |
| EXPECT_EQ(gfx::Point(200, 200), wheel_events[0].location()); |
| EXPECT_LT(wheel_events[0].x_offset(), 0); |
| ClearMouseEvents(); |
| } |
| |
| TEST_F(AutoclickTest, AutoclickRevertsToLeftClick) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetAutoclickController()->set_revert_to_left_click(true); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kRightClick); |
| std::vector<ui::MouseEvent> events; |
| |
| GetEventGenerator()->MoveMouseTo(30, 30); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[1].flags()); |
| |
| // Another event is now left-click; we've reverted to left click. |
| GetEventGenerator()->MoveMouseTo(90, 90); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kLeftClick); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| |
| // The next event is also a left click. |
| GetEventGenerator()->MoveMouseTo(120, 120); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kLeftClick); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| |
| // Changing revert to false doesn't change that we are on left click at |
| // present. |
| GetAutoclickController()->set_revert_to_left_click(false); |
| GetEventGenerator()->MoveMouseTo(150, 150); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kLeftClick); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_LEFT_MOUSE_BUTTON & events[1].flags()); |
| |
| // But we should no longer revert to left click if the type is something else. |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kRightClick); |
| GetEventGenerator()->MoveMouseTo(180, 180); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[1].flags()); |
| |
| // Should still be right click. |
| GetEventGenerator()->MoveMouseTo(210, 210); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[1].flags()); |
| } |
| |
| TEST_F(AutoclickTest, WaitsToDrawAnimationAfterDwellBegins) { |
| int animation_delay = 5; |
| int full_delay = UpdateAnimationDelayAndGetFullDelay(animation_delay); |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| std::vector<ui::MouseEvent> events; |
| |
| // Start a dwell at (210, 210). |
| GetEventGenerator()->MoveMouseTo(210, 210); |
| |
| // The center should change to (205, 205) if the adjustment is made before |
| // the animation starts. |
| FastForwardBy(animation_delay - 1); |
| GetEventGenerator()->MoveMouseTo(205, 205); |
| |
| // Now wait the full delay to ensure the click has happened, then check |
| // the result. |
| FastForwardBy(full_delay); |
| events = GetMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(gfx::Point(205, 205), events[0].location()); |
| |
| // Start another dwell at (100, 100). |
| ClearMouseEvents(); |
| GetEventGenerator()->MoveMouseTo(100, 100); |
| |
| // Move the mouse a little to (105, 105), which should become the center. |
| FastForwardBy(animation_delay - 1); |
| GetEventGenerator()->MoveMouseTo(105, 105); |
| |
| // Moving the mouse during the animation changes the center point. |
| FastForwardBy(animation_delay); |
| GetEventGenerator()->MoveMouseTo(110, 110); |
| |
| // Now wait until the click. It should be at the center point from before |
| // the animation started. |
| FastForwardBy(full_delay); |
| events = GetMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(gfx::Point(110, 110), events[0].location()); |
| |
| // Turn off stabilize_click_position and try again, the position should update |
| // with the cursor's new position until the click occurs. |
| GetAutoclickController()->set_stabilize_click_position(true); |
| ClearMouseEvents(); |
| GetEventGenerator()->MoveMouseTo(200, 200); |
| |
| // (205, 205) will become the center of the animation. |
| FastForwardBy(animation_delay - 1); |
| GetEventGenerator()->MoveMouseTo(205, 205); |
| |
| // Fast forward until the animation would have started. Now moving the mouse |
| // a little does not change the center point because we have stabilize on. |
| FastForwardBy(animation_delay); |
| GetEventGenerator()->MoveMouseTo(210, 210); |
| FastForwardBy(full_delay); |
| events = GetMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_EQ(gfx::Point(205, 205), events[0].location()); |
| |
| // Reset state. |
| GetAutoclickController()->set_stabilize_click_position(false); |
| } |
| |
| TEST_F(AutoclickTest, DoesActionOnBubbleWhenInDifferentModes) { |
| AccessibilityControllerImpl* accessibility_controller = |
| Shell::Get()->accessibility_controller(); |
| // Enable autoclick from the accessibility controller so that the bubble is |
| // constructed too. |
| accessibility_controller->SetAutoclickEnabled(true); |
| GetAutoclickController()->set_revert_to_left_click(false); |
| std::vector<ui::MouseEvent> events; |
| |
| // Test at different screen sizes and densities because the fake click on |
| // the button involves coordinating between dips and pixels. Try two different |
| // positions to ensure offsets are calculated correctly. |
| const struct { |
| const std::string display_spec; |
| float scale; |
| AutoclickMenuPosition position; |
| } kTestCases[] = { |
| {"800x600", 1.0f, AutoclickMenuPosition::kBottomRight}, |
| {"1024x800*2.0", 2.0f, AutoclickMenuPosition::kBottomRight}, |
| {"800x600", 1.0f, AutoclickMenuPosition::kTopLeft}, |
| {"1024x800*2.0", 2.0f, AutoclickMenuPosition::kTopLeft}, |
| }; |
| for (const auto& test : kTestCases) { |
| UpdateDisplay(test.display_spec); |
| accessibility_controller->SetAutoclickMenuPosition(test.position); |
| accessibility_controller->SetAutoclickEventType( |
| AutoclickEventType::kRightClick); |
| |
| AutoclickMenuView* menu = GetAutoclickMenuView(); |
| ASSERT_TRUE(menu); |
| |
| // Outside of the bubble, a right-click still occurs. |
| // Move to a central position which will not have any menu but will still be |
| // on-screen. |
| GetEventGenerator()->MoveMouseTo(200 * test.scale, 200 * test.scale); |
| events = WaitForMouseEvents(); |
| ASSERT_EQ(2u, events.size()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[0].flags()); |
| EXPECT_TRUE(ui::EF_RIGHT_MOUSE_BUTTON & events[1].flags()); |
| |
| // Over the bubble, we get no real click, although the autoclick event |
| // type does get changed properly over a button. |
| gfx::Point button_location = gfx::ScaleToRoundedPoint( |
| GetMenuButton(AutoclickMenuView::ButtonId::kDoubleClick) |
| ->GetBoundsInScreen() |
| .CenterPoint(), |
| test.scale); |
| GetEventGenerator()->MoveMouseTo(button_location); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| // But the event type did change with a the hover on the button. |
| EXPECT_EQ(AutoclickEventType::kDoubleClick, |
| accessibility_controller->GetAutoclickEventType()); |
| |
| // Change to a pause action type. |
| accessibility_controller->SetAutoclickEventType( |
| AutoclickEventType::kNoAction); |
| |
| // Outside the bubble, no action occurs. |
| GetEventGenerator()->MoveMouseTo(200 * test.scale, 200 * test.scale); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // If we move over the bubble but not over any button than no real click |
| // occurs. There is no button at the top of the bubble. |
| button_location = gfx::ScaleToRoundedPoint( |
| GetAutoclickMenuView()->GetBoundsInScreen().top_center() + |
| gfx::Vector2d(0, 1), |
| test.scale); |
| GetEventGenerator()->MoveMouseTo(button_location); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| // The event type did not change because we were not over any button. |
| EXPECT_EQ(AutoclickEventType::kNoAction, |
| accessibility_controller->GetAutoclickEventType()); |
| |
| // Leaving the bubble we are still paused. |
| GetEventGenerator()->MoveMouseTo(200 * test.scale, 200 * test.scale); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Moving over another button takes an action. |
| button_location = gfx::ScaleToRoundedPoint( |
| GetMenuButton(AutoclickMenuView::ButtonId::kLeftClick) |
| ->GetBoundsInScreen() |
| .CenterPoint(), |
| test.scale); |
| GetEventGenerator()->MoveMouseTo(button_location); |
| events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| EXPECT_EQ(AutoclickEventType::kLeftClick, |
| accessibility_controller->GetAutoclickEventType()); |
| } |
| } |
| |
| TEST_F(AutoclickTest, |
| StartsGestureOnBubbleButDoesNotClickIfMouseMovedWhenPaused) { |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| GetAutoclickController()->set_revert_to_left_click(false); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kNoAction); |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kBottomRight); |
| |
| int animation_delay = 5; |
| int full_delay = UpdateAnimationDelayAndGetFullDelay(animation_delay); |
| |
| std::vector<ui::MouseEvent> events; |
| AutoclickMenuView* menu = GetAutoclickMenuView(); |
| ASSERT_TRUE(menu); |
| |
| // Start a dwell over the bubble. |
| GetEventGenerator()->MoveMouseTo(menu->GetBoundsInScreen().origin()); |
| |
| // Move back off the bubble before anything happens. |
| FastForwardBy(animation_delay - 1); |
| GetEventGenerator()->MoveMouseTo(30, 30); |
| |
| // Now wait the full delay to ensure pause could have happened. |
| FastForwardBy(full_delay); |
| events = GetMouseEvents(); |
| ASSERT_EQ(0u, events.size()); |
| |
| // This time, dwell over the bubble long enough for the animation to begin. |
| // No action should occur if we move off during the dwell. |
| GetEventGenerator()->MoveMouseTo(menu->GetBoundsInScreen().origin()); |
| |
| // Move back off the bubble after the animation begins, but before a click |
| // would occur. |
| FastForwardBy(animation_delay + 1); |
| GetEventGenerator()->MoveMouseTo(30, 30); |
| |
| // Now wait the full delay to ensure pause could have happened. |
| FastForwardBy(full_delay); |
| events = GetMouseEvents(); |
| ASSERT_EQ(0u, events.size()); |
| } |
| |
| // The autoclick tray shouldn't stop the shelf from auto-hiding. |
| TEST_F(AutoclickTest, ShelfAutohidesWithAutoclickBubble) { |
| Shelf* shelf = GetPrimaryShelf(); |
| |
| // Create a visible window so auto-hide behavior is enforced. |
| std::unique_ptr<views::Widget> widget = |
| CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(), |
| gfx::Rect(0, 0, 200, 200), true /* show */); |
| |
| // Turn on auto-hide for the shelf. |
| shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); |
| EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState()); |
| EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState()); |
| |
| // Enable autoclick. The shelf should remain invisible. |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| AutoclickMenuView* menu = GetAutoclickMenuView(); |
| ASSERT_TRUE(menu); |
| EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState()); |
| EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState()); |
| } |
| |
| TEST_F(AutoclickTest, BubbleMovesWithShelfPositionChange) { |
| UpdateDisplay("800x600"); |
| int screen_width = 800; |
| int screen_height = 600; |
| |
| // Create a visible window so WMEvents occur. |
| std::unique_ptr<views::Widget> widget = |
| CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(), |
| gfx::Rect(0, 0, 200, 200), true /* show */); |
| |
| // Set up autoclick and the shelf. |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kBottomRight); |
| Shelf* shelf = GetPrimaryShelf(); |
| shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); |
| EXPECT_EQ(shelf->GetVisibilityState(), SHELF_VISIBLE); |
| AutoclickMenuView* menu = GetAutoclickMenuView(); |
| ASSERT_TRUE(menu); |
| |
| shelf->SetAlignment(SHELF_ALIGNMENT_BOTTOM); |
| // The menu should be positioned above the shelf, not overlapping. |
| EXPECT_EQ(menu->GetBoundsInScreen().bottom_right().y(), |
| screen_height - shelf->GetIdealBounds().height() - |
| kCollisionWindowWorkAreaInsetsDp); |
| // And all the way to the right. |
| EXPECT_EQ(menu->GetBoundsInScreen().bottom_right().x(), |
| screen_width - kCollisionWindowWorkAreaInsetsDp); |
| |
| shelf->SetAlignment(SHELF_ALIGNMENT_LEFT); |
| // The menu should move to the bottom of the screen. |
| EXPECT_EQ(menu->GetBoundsInScreen().bottom_right().y(), |
| screen_height - kCollisionWindowWorkAreaInsetsDp); |
| // Still be at the far right. |
| EXPECT_EQ(menu->GetBoundsInScreen().bottom_right().x(), |
| screen_width - kCollisionWindowWorkAreaInsetsDp); |
| |
| shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT); |
| // The menu should stay at the bottom of the screen. |
| EXPECT_EQ(menu->GetBoundsInScreen().bottom_right().y(), |
| screen_height - kCollisionWindowWorkAreaInsetsDp); |
| // And should be offset from the far right by the shelf width. |
| EXPECT_EQ(menu->GetBoundsInScreen().bottom_right().x(), |
| screen_width - kCollisionWindowWorkAreaInsetsDp - |
| shelf->GetIdealBounds().width()); |
| |
| // Reset state. |
| shelf->SetAlignment(SHELF_ALIGNMENT_BOTTOM); |
| } |
| |
| TEST_F(AutoclickTest, AvoidsShelfBubble) { |
| const struct { |
| session_manager::SessionState session_state; |
| } kTestCases[]{ |
| {session_manager::SessionState::OOBE}, |
| {session_manager::SessionState::LOGIN_PRIMARY}, |
| {session_manager::SessionState::ACTIVE}, |
| {session_manager::SessionState::LOCKED}, |
| }; |
| |
| for (auto test : kTestCases) { |
| GetSessionControllerClient()->SetSessionState(test.session_state); |
| // Set up autoclick and the shelf. |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kBottomRight); |
| auto* unified_system_tray = GetPrimaryUnifiedSystemTray(); |
| EXPECT_FALSE(unified_system_tray->IsBubbleShown()); |
| AutoclickMenuView* menu = GetAutoclickMenuView(); |
| ASSERT_TRUE(menu); |
| gfx::Rect menu_bounds = menu->GetBoundsInScreen(); |
| |
| unified_system_tray->ShowBubble(true /* show_by_click */); |
| gfx::Rect new_menu_bounds = menu->GetBoundsInScreen(); |
| // Y is unchanged when the bubble shows. |
| EXPECT_TRUE(abs(menu_bounds.y() - new_menu_bounds.y()) < 5); |
| // X is pushed over by at least the bubble's bounds. |
| EXPECT_TRUE(menu_bounds.x() - |
| unified_system_tray->GetBubbleBoundsInScreen().width() > |
| new_menu_bounds.x()); |
| |
| unified_system_tray->CloseBubble(); |
| new_menu_bounds = menu->GetBoundsInScreen(); |
| EXPECT_EQ(menu_bounds, new_menu_bounds); |
| } |
| } |
| |
| TEST_F(AutoclickTest, ConfirmationDialogShownWhenDisablingFeature) { |
| // Enable and disable with the AccessibilityController to get real use-case |
| // of the dialog. |
| |
| // No dialog shown at start-up. |
| EXPECT_FALSE(GetAutoclickController()->GetDisableDialogForTesting()); |
| |
| // No dialog shown when enabling the feature. |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| EXPECT_FALSE(GetAutoclickController()->GetDisableDialogForTesting()); |
| |
| // A dialog should be shown when disabling the feature. |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(false); |
| AccessibilityFeatureDisableDialog* dialog = |
| GetAutoclickController()->GetDisableDialogForTesting(); |
| EXPECT_TRUE(dialog); |
| |
| // Canceling the dialog will cause the feature to continue to be enabled. |
| dialog->GetDialogClientView()->CancelWindow(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetAutoclickController()->GetDisableDialogForTesting()); |
| EXPECT_TRUE(Shell::Get()->accessibility_controller()->autoclick_enabled()); |
| EXPECT_TRUE(GetAutoclickController()->IsEnabled()); |
| |
| // Try to disable it again, and this time accept the dialog to actually |
| // disable the feature. |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(false); |
| dialog = GetAutoclickController()->GetDisableDialogForTesting(); |
| EXPECT_TRUE(dialog); |
| dialog->GetDialogClientView()->AcceptWindow(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetAutoclickController()->GetDisableDialogForTesting()); |
| EXPECT_FALSE(Shell::Get()->accessibility_controller()->autoclick_enabled()); |
| EXPECT_FALSE(GetAutoclickController()->IsEnabled()); |
| } |
| |
| TEST_F(AutoclickTest, HidesBubbleInFullscreenWhenCursorHides) { |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager(); |
| cursor_manager->SetCursor(ui::CursorType::kPointer); |
| |
| const struct { |
| const std::string display_spec; |
| gfx::Rect widget_position; |
| } kTestCases[] = { |
| {"800x600", gfx::Rect(0, 0, 200, 200)}, |
| {"800x600,800x600", gfx::Rect(0, 0, 200, 200)}, |
| {"800x600,800x600", gfx::Rect(1000, 0, 200, 200)}, |
| }; |
| for (const auto& test : kTestCases) { |
| SCOPED_TRACE(test.display_spec); |
| UpdateDisplay(test.display_spec); |
| |
| std::unique_ptr<views::Widget> widget = |
| CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(), |
| test.widget_position, /*show=*/true); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Move the mouse over the widget, so it's on the same screen as the widget. |
| GetEventGenerator()->MoveMouseTo(test.widget_position.origin()); |
| |
| // When the widget is not fullscreen, hiding the cursor does not cause |
| // the bubble to be hidden. |
| cursor_manager->HideCursor(); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| cursor_manager->ShowCursor(); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Bubble is visible in fullscreen mode because the mouse cursor is visible. |
| widget->SetFullscreen(true); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Bubble is hidden when the cursor is hidden in fullscreen mode, and shown |
| // when the cursor is shown. |
| cursor_manager->HideCursor(); |
| EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible()); |
| cursor_manager->ShowCursor(); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Changing the type to another visible type doesn't cause the bubble to |
| // hide. |
| cursor_manager->SetCursor(ui::CursorType::kHand); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Changing the type to an kNone causes the bubble to hide. |
| cursor_manager->SetCursor(ui::CursorType::kNone); |
| EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Hiding and showing don't re-show the bubble because the type is still |
| // kNone. |
| cursor_manager->HideCursor(); |
| EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible()); |
| cursor_manager->ShowCursor(); |
| EXPECT_FALSE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // The bubble is shown when the cursor is a visible type again. |
| cursor_manager->SetCursor(ui::CursorType::kPointer); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| } |
| } |
| |
| TEST_F(AutoclickTest, DoesNotHideBubbleWhenNotOverFullscreenWindow) { |
| UpdateDisplay("800x600,800x600"); |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager(); |
| cursor_manager->SetCursor(ui::CursorType::kPointer); |
| |
| std::unique_ptr<views::Widget> widget = |
| CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(), |
| gfx::Rect(1000, 0, 200, 200), true); |
| |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| |
| // Move the mouse over the other display. |
| GetEventGenerator()->MoveMouseTo(gfx::Point(10, 10)); |
| |
| // When the widget is fullscreen, hiding the cursor does not hide the bubble |
| // because the cursor is on a different display. |
| widget->SetFullscreen(true); |
| cursor_manager->HideCursor(); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| } |
| |
| TEST_F(AutoclickTest, DoesNotHideBubbleWhenOverInactiveFullscreenWindow) { |
| Shell::Get()->accessibility_controller()->SetAutoclickEnabled(true); |
| ::wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager(); |
| cursor_manager->SetCursor(ui::CursorType::kPointer); |
| |
| std::unique_ptr<views::Widget> widget = |
| CreateTestWidget(nullptr, desks_util::GetActiveDeskContainerId(), |
| gfx::Rect(0, 0, 200, 200), true); |
| GetEventGenerator()->MoveMouseTo(gfx::Point(10, 10)); |
| widget->SetFullscreen(true); |
| EXPECT_TRUE(widget->IsActive()); |
| views::Widget* popup_widget = views::Widget::CreateWindowWithContextAndBounds( |
| nullptr, CurrentContext(), gfx::Rect(200, 200, 200, 200)); |
| popup_widget->Show(); |
| |
| cursor_manager->HideCursor(); |
| EXPECT_FALSE(widget->IsActive()); |
| EXPECT_TRUE(popup_widget->IsActive()); |
| EXPECT_TRUE(GetAutoclickBubbleWidget()->IsVisible()); |
| } |
| |
| TEST_F(AutoclickTest, ScrollClosesWhenHoveredOverScrollButton) { |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| GetAutoclickController()->SetAutoclickEventType( |
| AutoclickEventType::kLeftClick); |
| EXPECT_FALSE(GetAutoclickScrollView()); |
| |
| // Enable scroll. |
| GetAutoclickController()->SetAutoclickEventType(AutoclickEventType::kScroll); |
| ASSERT_TRUE(GetAutoclickScrollView()); |
| views::View* close_button = |
| GetScrollButton(AutoclickScrollView::ButtonId::kCloseScroll); |
| ASSERT_TRUE(close_button); |
| |
| gfx::Point button_location = close_button->GetBoundsInScreen().CenterPoint(); |
| GetEventGenerator()->MoveMouseTo(button_location); |
| // No mouse event. |
| std::vector<ui::MouseEvent> events = WaitForMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| |
| // Reset to left click and scroll view is gone. |
| EXPECT_EQ(AutoclickEventType::kLeftClick, |
| Shell::Get()->accessibility_controller()->GetAutoclickEventType()); |
| EXPECT_FALSE(GetAutoclickScrollView()); |
| } |
| |
| TEST_F(AutoclickTest, ScrollOccursWhenHoveredOverScrollButtons) { |
| UpdateDisplay("800x600"); |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| |
| // Enable scroll. |
| GetAutoclickController()->SetAutoclickEventType(AutoclickEventType::kScroll); |
| ASSERT_TRUE(GetAutoclickScrollView()); |
| |
| // TODO: Do for all four buttons, wait a few scrolls each. |
| const struct { |
| AutoclickScrollView::ButtonId button_id; |
| int scroll_x; |
| int scroll_y; |
| } kTestCases[] = { |
| {AutoclickScrollView::ButtonId::kScrollUp, 0, 10}, |
| {AutoclickScrollView::ButtonId::kScrollDown, 0, -10}, |
| {AutoclickScrollView::ButtonId::kScrollLeft, 10, 0}, |
| {AutoclickScrollView::ButtonId::kScrollRight, -10, 0}, |
| }; |
| for (auto& test : kTestCases) { |
| views::View* button = GetScrollButton(test.button_id); |
| ASSERT_TRUE(button); |
| |
| gfx::Point button_location = button->GetBoundsInScreen().CenterPoint(); |
| GetEventGenerator()->MoveMouseTo(button_location); |
| // No mouse event during hover, no wheel events yet. |
| FastForwardBy(AutoclickScrollView::kAutoclickScrollDelayMs - 1); |
| std::vector<ui::MouseEvent> events = GetMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| std::vector<ui::MouseWheelEvent> wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(0u, wheel_events.size()); |
| |
| // But we should get a scroll event after kAutoclickScrollDelayMs. |
| FastForwardBy(2); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(gfx::Point(400, 300), wheel_events[0].location()); |
| EXPECT_EQ(test.scroll_x, wheel_events[0].x_offset()); |
| EXPECT_EQ(test.scroll_y, wheel_events[0].y_offset()); |
| // No other events expected. |
| events = GetMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ClearMouseEvents(); |
| |
| // Wait until another kAutoclickScrollDelayMs has elapsed and expect another |
| // scroll to have occurred. |
| FastForwardBy(AutoclickScrollView::kAutoclickScrollDelayMs); |
| wheel_events = GetMouseWheelEvents(); |
| EXPECT_EQ(1u, wheel_events.size()); |
| EXPECT_EQ(gfx::Point(400, 300), wheel_events[0].location()); |
| EXPECT_EQ(test.scroll_x, wheel_events[0].x_offset()); |
| EXPECT_EQ(test.scroll_y, wheel_events[0].y_offset()); |
| // No other events expected. |
| events = GetMouseEvents(); |
| EXPECT_EQ(0u, events.size()); |
| ClearMouseEvents(); |
| } |
| } |
| |
| TEST_F(AutoclickTest, ScrollMenuBubblePostioning) { |
| UpdateDisplay("800x600"); |
| GetAutoclickController()->SetEnabled(true, false /* do not show dialog */); |
| |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kBottomRight); |
| GetAutoclickController()->SetAutoclickEventType(AutoclickEventType::kScroll); |
| |
| ASSERT_TRUE(GetAutoclickScrollView()); |
| |
| // Set the bounds to be the entire window. |
| gfx::Rect display_bounds = gfx::Rect(0, 0, 800, 600); |
| GetAutoclickController()->OnAutoclickScrollableBoundsFound(display_bounds); |
| |
| // The scroll bubble should start near the autoclick menu. |
| gfx::Rect scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen(); |
| gfx::Rect menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen(); |
| EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds), |
| kScrollToMenuBoundsBuffer); |
| |
| // Moving the autoclick menu around the screen moves the scroll bubble too. |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kBottomLeft); |
| scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen(); |
| menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen(); |
| EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds), |
| kScrollToMenuBoundsBuffer); |
| |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kTopLeft); |
| scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen(); |
| menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen(); |
| EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds), |
| kScrollToMenuBoundsBuffer); |
| |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kTopRight); |
| scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen(); |
| menu_bounds = GetAutoclickMenuView()->GetBoundsInScreen(); |
| EXPECT_LT(menu_bounds.ManhattanInternalDistance(scroll_bounds), |
| kScrollToMenuBoundsBuffer); |
| |
| // However, if we dwell somewhere else, the autoclick scroll menu will now |
| // move out of the corner and near that point when the display bounds are |
| // found. |
| gfx::Point scroll_point = gfx::Point(0, 0); |
| GetEventGenerator()->MoveMouseTo(scroll_point); |
| base::RunLoop().RunUntilIdle(); |
| GetAutoclickController()->OnAutoclickScrollableBoundsFound(display_bounds); |
| scroll_bounds = GetAutoclickScrollView()->GetBoundsInScreen(); |
| EXPECT_GT(menu_bounds.ManhattanInternalDistance(scroll_bounds), |
| kScrollToMenuBoundsBuffer); |
| |
| // Moving the bubble menu now does not change the scroll bubble's position, |
| // it remains near its point. |
| Shell::Get()->accessibility_controller()->SetAutoclickMenuPosition( |
| AutoclickMenuPosition::kBottomRight); |
| EXPECT_EQ(GetAutoclickScrollView()->GetBoundsInScreen(), scroll_bounds); |
| } |
| |
| } // namespace ash |